Skip to content

Commit 159ef1b

Browse files
authored
Fix fragment handling in toTree() (#12154)
1 parent 838ee54 commit 159ef1b

File tree

2 files changed

+99
-20
lines changed

2 files changed

+99
-20
lines changed

src/ReactTestRenderer.js

+46-19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
FunctionalComponent,
2020
ClassComponent,
2121
HostComponent,
22+
HostPortal,
2223
HostText,
2324
HostRoot,
2425
} from 'shared/ReactTypeOfWork';
@@ -286,60 +287,86 @@ function toJSON(inst: Instance | TextInstance): ReactTestRendererNode {
286287
}
287288
}
288289

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) {
290304
const array = [];
291305
let node = nodeWithSibling;
292306
while (node != null) {
293307
array.push(node);
294308
node = node.sibling;
295309
}
296-
const trees = array.map(toTree);
297-
return trees.length ? trees : null;
310+
return array;
298311
}
299312

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;
302330
}
303331

304332
function toTree(node: ?Fiber) {
305333
if (node == null) {
306334
return null;
307335
}
308336
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);
311341
case ClassComponent:
312342
return {
313343
nodeType: 'component',
314344
type: node.type,
315345
props: {...node.memoizedProps},
316346
instance: node.stateNode,
317-
rendered: hasSiblings(node.child)
318-
? nodeAndSiblingsTrees(node.child)
319-
: toTree(node.child),
347+
rendered: childrenToTree(node.child),
320348
};
321-
case FunctionalComponent: // 1
349+
case FunctionalComponent:
322350
return {
323351
nodeType: 'component',
324352
type: node.type,
325353
props: {...node.memoizedProps},
326354
instance: null,
327-
rendered: hasSiblings(node.child)
328-
? nodeAndSiblingsTrees(node.child)
329-
: toTree(node.child),
355+
rendered: childrenToTree(node.child),
330356
};
331-
case HostComponent: // 5
357+
case HostComponent: {
332358
return {
333359
nodeType: 'host',
334360
type: node.type,
335361
props: {...node.memoizedProps},
336362
instance: null, // TODO: use createNodeMock here somehow?
337-
rendered: nodeAndSiblingsTrees(node.child),
363+
rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)),
338364
};
339-
case HostText: // 6
365+
}
366+
case HostText:
340367
return node.stateNode.text;
341-
case Fragment: // 10
342-
return toTree(node.child);
368+
case Fragment:
369+
return childrenToTree(node.child);
343370
default:
344371
invariant(
345372
false,

src/__tests__/ReactTestRenderer-test.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ describe('ReactTestRenderer', () => {
615615
);
616616
});
617617

618-
it('toTree() handles complicated tree of fragments', () => {
618+
it('toTree() handles complicated tree of arrays', () => {
619619
class Foo extends React.Component {
620620
render() {
621621
return this.props.children;
@@ -693,6 +693,58 @@ describe('ReactTestRenderer', () => {
693693
);
694694
});
695695

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+
696748
it('root instance and createNodeMock ref return the same value', () => {
697749
const createNodeMock = ref => ({node: ref});
698750
let refInst = null;

0 commit comments

Comments
 (0)