@samitouri / QOS-React / commits / 8b83ea02f5

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;