Fix fragment handling in toTree() (#12154)
Dan Abramov committed
Feb 5, 2018 at 16:55 UTC
8b83ea02f5abc7bf526b7e104a2cb73b034df5c2
2 files changed
+99
-20
packages/react-test-renderer/src/ReactTestRenderer.js
+46
-19
@@ -19,6 +19,7 @@ import {
19
FunctionalComponent,
20
ClassComponent,
21
HostComponent,
22
+ HostPortal,
23
HostText,
24
HostRoot,
25
} from 'shared/ReactTypeOfWork';
@@ -286,19 +287,46 @@ function toJSON(inst: Instance | TextInstance): ReactTestRendererNode {
287
}
288
}
289
289
-function nodeAndSiblingsTrees(nodeWithSibling: ?Fiber) {
290
+function childrenToTree(node) {
291
+ if (!node) {
292
+ return null;
293
+ }
294
+ const children = nodeAndSiblingsArray(node);
295
+ if (children.length === 0) {
296
+ return null;
297
+ } else if (children.length === 1) {
298
+ return toTree(children[0]);
299
+ }
300
+ return flatten(children.map(toTree));
301
+}
302
+
303
+function nodeAndSiblingsArray(nodeWithSibling) {
304
const array = [];
305
let node = nodeWithSibling;
306
while (node != null) {
307
array.push(node);
308
node = node.sibling;
309
}
296
- const trees = array.map(toTree);
297
- return trees.length ? trees : null;
310
+ return array;
311
}
312
300
-function hasSiblings(node: ?Fiber) {
301
- return node && node.sibling;
313
+function flatten(arr) {
314
+ const result = [];
315
+ const stack = [{i: 0, array: arr}];
316
+ while (stack.length) {
317
+ const n = stack.pop();
318
+ while (n.i < n.array.length) {
319
+ const el = n.array[n.i];
320
+ n.i += 1;
321
+ if (Array.isArray(el)) {
322
+ stack.push(n);
323
+ stack.push({i: 0, array: el});
324
+ break;
325
+ }
326
+ result.push(el);
327
+ }
328
+ }
329
+ return result;
330
}
331
332
function toTree(node: ?Fiber) {
@@ -306,40 +334,39 @@ function toTree(node: ?Fiber) {
334
return null;
335
}
336
switch (node.tag) {
309
- case HostRoot: // 3
310
- return toTree(node.child);
337
+ case HostRoot:
338
+ return childrenToTree(node.child);
339
+ case HostPortal:
340
+ return childrenToTree(node.child);
341
case ClassComponent:
342
return {
343
nodeType: 'component',
344
type: node.type,
345
props: {...node.memoizedProps},
346
instance: node.stateNode,
317
- rendered: hasSiblings(node.child)
318
- ? nodeAndSiblingsTrees(node.child)
319
- : toTree(node.child),
347
+ rendered: childrenToTree(node.child),
348
};
321
- case FunctionalComponent: // 1
349
+ case FunctionalComponent:
350
return {
351
nodeType: 'component',
352
type: node.type,
353
props: {...node.memoizedProps},
354
instance: null,
327
- rendered: hasSiblings(node.child)
328
- ? nodeAndSiblingsTrees(node.child)
329
- : toTree(node.child),
355
+ rendered: childrenToTree(node.child),
356
};
331
- case HostComponent: // 5
357
+ case HostComponent: {
358
return {
359
nodeType: 'host',
360
type: node.type,
361
props: {...node.memoizedProps},
362
instance: null, // TODO: use createNodeMock here somehow?
337
- rendered: nodeAndSiblingsTrees(node.child),
363
+ rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)),
364
};
339
- case HostText: // 6
365
+ }
366
+ case HostText:
367
return node.stateNode.text;
341
- case Fragment: // 10
342
- return toTree(node.child);
368
+ case Fragment:
369
+ return childrenToTree(node.child);
370
default:
371
invariant(
372
false,
packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js
+53
-1
@@ -615,7 +615,7 @@ describe('ReactTestRenderer', () => {
615
);
616
});
617
618
- it('toTree() handles complicated tree of fragments', () => {
618
+ it('toTree() handles complicated tree of arrays', () => {
619
class Foo extends React.Component {
620
render() {
621
return this.props.children;
@@ -693,6 +693,58 @@ describe('ReactTestRenderer', () => {
693
);
694
});
695
696
+ it('toTree() handles complicated tree of fragments', () => {
697
+ const renderer = ReactTestRenderer.create(
698
+ <React.Fragment>
699
+ <React.Fragment>
700
+ <div>One</div>
701
+ <div>Two</div>
702
+ <React.Fragment>
703
+ <div>Three</div>
704
+ </React.Fragment>
705
+ </React.Fragment>
706
+ <div>Four</div>
707
+ </React.Fragment>,
708
+ );
709
+
710
+ const tree = renderer.toTree();
711
+
712
+ cleanNodeOrArray(tree);
713
+
714
+ expect(prettyFormat(tree)).toEqual(
715
+ prettyFormat([
716
+ {
717
+ type: 'div',
718
+ nodeType: 'host',
719
+ props: {},
720
+ instance: null,
721
+ rendered: ['One'],
722
+ },
723
+ {
724
+ type: 'div',
725
+ nodeType: 'host',
726
+ props: {},
727
+ instance: null,
728
+ rendered: ['Two'],
729
+ },
730
+ {
731
+ type: 'div',
732
+ nodeType: 'host',
733
+ props: {},
734
+ instance: null,
735
+ rendered: ['Three'],
736
+ },
737
+ {
738
+ type: 'div',
739
+ nodeType: 'host',
740
+ props: {},
741
+ instance: null,
742
+ rendered: ['Four'],
743
+ },
744
+ ]),
745
+ );
746
+ });
747
+
748
it('root instance and createNodeMock ref return the same value', () => {
749
const createNodeMock = ref => ({node: ref});
750
let refInst = null;