Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support array-like iterable children in v3 #1187

Merged
merged 4 commits into from
Oct 27, 2017

Conversation

kmcq
Copy link
Contributor

@kmcq kmcq commented Sep 28, 2017

What this is

Fixes #1181.

This adds support for non-Array, but still iterable, children, such as Immutable-js Lists, Sets, etc., which is supported by React:

const Component = () => {
  const listOfThings = Immutable.List([1, 2, 3]);
  return <div>{listOfThings.map(thing => <span>{thing}</span>)}</div>;
};

The React PR that originally added this functionality is here: facebook/react#2376.

The code

  • Check to see if children is iterable, and if so, use Array.from to convert it to an array.

I like this approach because it compartmentalizes the knowledge that children are sometimes things other than arrays.

@@ -0,0 +1,65 @@
const ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
Copy link
Contributor Author

@kmcq kmcq Sep 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that enzyme has this elsewhere, but in a different package. If there's a good way to share this across lerna packages, please let me know. I haven't used lerna before 😄

@@ -81,43 +88,6 @@ export function assertDomAvailable(feature) {
}
}

export function nodeTypeFromType(type) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to its own file so that elementToTree could still use it

@kmcq kmcq changed the title Allow for non-Array, but Iterable, children [WIP] Allow for non-Array, but Iterable, children Sep 28, 2017
@kmcq
Copy link
Contributor Author

kmcq commented Sep 28, 2017

After searching around the codebase some more, I see that the elementToTree that I patched is not the only elementToTree (v13 has its own) and also there are potentially other places that would have to change for this to work, for instance childrenToArray in enzyme/Utils. I'm also not convinced that the tests I wrote are the best way to test that this works (I left them failing).

I'm going to pause on this for now to see if any maintainers have ideas about how best to tackle this.

@lelandrichardson
Copy link
Collaborator

Interesting! I didn't know React had this capability.

@kmcq
Copy link
Contributor Author

kmcq commented Oct 5, 2017

fyi I plan on coming back to this next week!

@kmcq kmcq changed the title [WIP] Allow for non-Array, but Iterable, children Support Immutable.js array-like children in v3 Oct 11, 2017
@kmcq
Copy link
Contributor Author

kmcq commented Oct 11, 2017

@lelandrichardson I have changed this to now be about adding Immutable.js support. Please see the updated description and code and let me know if you have any questions!

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic iterables makes sense to me; that's in the language.

A .toArray function is arbitrary and non-standard; unless React supports this explicitly, I do not think we should support it in enzyme.

@kmcq
Copy link
Contributor Author

kmcq commented Oct 11, 2017

Hi @ljharb thanks for the review. I have made this more generic -- I did some more research and found out that all iterables support Array.from. This seems more in line with the language spec. Please read again and let me know :)

@kmcq kmcq changed the title Support Immutable.js array-like children in v3 Support array-like iterable children in v3 Oct 11, 2017
Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks much better!

@@ -23,6 +24,8 @@ export default function elementToTree(el) {
let rendered = null;
if (Array.isArray(children)) {
rendered = flatten(children, true).map(elementToTree);
} else if (isIterable(children) && typeof children !== 'string') {
rendered = flatten(Array.from(children), true).map(elementToTree);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we use babel, this could also be flatten([...children], true)

return (
typeof Symbol === 'function' &&
typeof Symbol.iterator === 'symbol' &&
obj != null &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd probably move the obj check higher, since that's faster then checking Symbols

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, I wasn't aware of that! Thanks!

@@ -104,6 +113,8 @@ export function elementToTree(el) {
let rendered = null;
if (Array.isArray(children)) {
rendered = flatten(children, true).map(elementToTree);
} else if (isIterable(children) && typeof children !== 'string') {
rendered = flatten(Array.from(children), true).map(elementToTree);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly, flatten([...children], true)

</div>,
);
treeForEach(node, spy);
expect(spy.callCount).to.equal(3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also use expect(spy.calls).to.deep.equal in both places, and assert that it's called with the right items?

It'd also be great to test with a Map as well as a Set

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes for sure, I like that. The tree objects are pretty complex, how do you feel about just checking part of it, say keys? Like this:

it('should handle Map children', () => {
  const spy = sinon.spy();
  const twoDivMap = new Map([
    [<div key="a" />],
    [<div key="b" />],
  ]);
  const node = $(
    <div key="root">
      {twoDivMap}
    </div>,
  );
  treeForEach(node, spy);
  expect(spy.callCount).to.equal(3);
  const keys = spy.args.map(arg => arg[0].key);
  expect(keys).to.deep.equal(['root', 'a', 'b']);
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah nevermind, even better I can just use $ and check the whole thing:

it('should handle Map children', () => {
  const spy = sinon.spy();
  const twoDivMap = new Map([
    [<div key="a" />],
    [<div key="b" />],
  ]);
  const divA = $(<div key="a" />);
  const divB = $(<div key="b" />);
  const node = $(
    <div key="root">
      {twoDivMap}
    </div>,
  );
  treeForEach(node, spy);
  expect(spy.callCount).to.equal(3);
  const nodes = spy.args.map(arg => arg[0]);
  expect(nodes).to.deep.equal([node, divA, divB]);
});

I'll get this committed soon!

@kmcq kmcq force-pushed the iterable-children branch 2 times, most recently from 2c90486 to 2d43e65 Compare October 11, 2017 20:55
@kmcq
Copy link
Contributor Author

kmcq commented Oct 11, 2017

Alrighty! @ljharb travis is happy and I think I answered your comments. Can you please review again?

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Although it does seem like you've only added iterable support in the react 13 adapter - the 14, 15, 15.4, and 16 adapters would need it as well.

@kmcq
Copy link
Contributor Author

kmcq commented Oct 11, 2017

Thanks! Actually the v13 adapter is special-cased. The others all use the enzyme-adapter-utils version of elementToTree, which is part of this!

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for clarifying :-)

@stevenmusumeche
Copy link

Any idea when this might be released?

@ljharb
Copy link
Member

ljharb commented Oct 21, 2017

@stevenmusumeche when a few more collaborators have reviewed it.

@kmcq
Copy link
Contributor Author

kmcq commented Oct 27, 2017

would it be helpful if I periodically rebase this? otherwise I can just do it once we're ready

@stevenmusumeche
Copy link

I hate to be a pest, but is there a way to move the review up in priority for the other collaborators? The code and tests look great to me, but I am not intimately familiar with the codebase.

It is something that worked before in v2 and doesn't in v3 and is preventing quite a few companies from upgrading to React 16.

Copy link
Collaborator

@aweary aweary left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it took so long! I'm taking some time away from OSS right now. This looks good though 👍

@ljharb
Copy link
Member

ljharb commented Oct 27, 2017

@kmcq please do rebase it, yes :-)

@kmcq
Copy link
Contributor Author

kmcq commented Oct 27, 2017

rebased!

@ljharb ljharb merged commit a1d076d into enzymejs:master Oct 27, 2017
@stevenmusumeche
Copy link

Thank you all so much for what you do.

@kmcq kmcq deleted the iterable-children branch October 28, 2017 00:02
@kmcq
Copy link
Contributor Author

kmcq commented Oct 28, 2017

Thanks @ljharb!

I appreciate everyone's reviews. This was my first time contributing to a library like this and you all made it fun and I learned some things too. Cheers!

@kamui
Copy link

kamui commented Oct 28, 2017

Thanks @kmcq!

Any ETA on when a new version will be cut? This was the only thing blocking us from upgrading to react 16. Thanks!

@Inucoder
Copy link

Inucoder commented Nov 1, 2017

Looking forward for this to be released! Great work guys!

@ljharb
Copy link
Member

ljharb commented Nov 1, 2017

@kamui no ETA yet. I'm shooting for this week, ASAP.

@ljharb
Copy link
Member

ljharb commented Nov 2, 2017

All adapters, as well as enzyme-adapter-utils, have been bumped.

@kmcq
Copy link
Contributor Author

kmcq commented Nov 2, 2017

Hey @ljharb, sorry to bug you but it looks like the React 15 adapter's release didn't make it to NPM: https://www.npmjs.com/package/enzyme-adapter-react-15. I checked and the rest look good 👍 Is that something that you need to handle or is that one just lagging behind? Thanks!

Edit: I should point out that this is not a big issue, nor should it block anyone, since the change was in enzyme-adapter-utils@1.1.0, which that above adapter lists as having a dependency of ^1.0.0. The old adapter will pull in the new util package.

Edit2: It's there now! Thanks so much!

@Inucoder
Copy link

Inucoder commented Nov 3, 2017

Hey guys, I'm running enzyme 3.1.0, enzyme-adapter-react-15 1.0.4, enzyme-adapter-utils 1.1.0 along with Karma.

'use strict';

import Enzyme          from 'enzyme';
import Immutable     from 'immutable';
import Adapter         from 'enzyme-adapter-react-15';
import {isArrayLike} from 'enzyme-adapter-utils';

Enzyme.configure({adapter: new Adapter()});

describe('Enzyme 3', () => {
  it('should play nice with Immutable.List children', () => {
    const children = Immutable.fromJS([1, 2, 3]);

    expect(isArrayLike(children))
      .toBe(true);
  });
});

Above unit test doesn't pass, since isArrayLike doesn't think Immutable.List is array-like, to be more specific isIterable from enzyme-adapter-utils returns false for an Immutable List instance. I also can see you are testing against ES6 Map and Set in your test suite, but support for Immutable children is missing.

I could add Immutable as a dependency of enzyme-adapter-utils to extend isArrayLike for this use case. Or if you think about a better way to achieve it, I could give it a shot, add some tests and submit a PR.

I think having support for Immutable children makes sense because:

  1. Was supported in v2.
  2. It's supported by React itself.

I'm looking forward to collaborate, keep the great work!

@ljharb
Copy link
Member

ljharb commented Nov 3, 2017

@kcmq thanks, your comment reminded me to publish it :-) all set.

@Inucoder Immutable.List should be an iterable, even if not array-like - so I'd expect it should work. Are you using the latest version of immutable?

@Inucoder
Copy link

Inucoder commented Nov 3, 2017

@ljharb, the test I shared was with Immutable 3.8.2, which is the latest stable release. Just pulled 4.0.0-rc.9 and test still not passing. I can add support for it based on how React checks for it facebook/react@c07ea0b. Let me know your thoughts.

@Inucoder
Copy link

Inucoder commented Nov 3, 2017

@ljharb, I found that the issue was because we are running our unit tests on PhantomJS, and ES6 Symbol API is missing there, and might be the case for old browsers. Tried to run the test on recent Chrome version and works just fine.

We could change

function isIterable(obj) {
  return (
    obj != null &&
    typeof Symbol === 'function' &&
    typeof Symbol.iterator === 'symbol' &&
    typeof obj[Symbol.iterator] === 'function'
  );
}

in the enzyme-adapter-utils, to

function isIterable(obj) {
  return (
    obj != null && (
      (
        typeof Symbol === 'function' &&
        typeof Symbol.iterator === 'symbol' &&
        typeof obj[Symbol.iterator] === 'function'
      ) ||
      obj['@@iterator']
    )
  );
}

that's exactly what React does.

@ljharb
Copy link
Member

ljharb commented Nov 4, 2017

Hmm - @@iterator is a fallback that only a few versions of Firefox officially supported; I'm hesitant to support it in enzyme; although since React unfortunately chose to support it, we might have to.

Can you confirm whether React supported this behavior down to 0.13? Also, can you file a separate issue about this?

@Inucoder
Copy link

Inucoder commented Nov 5, 2017

For sure @ljharb, I'll take a look into that.

@ljharb
Copy link
Member

ljharb commented Dec 22, 2017

Linking to #1334 as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants