Skip to content

Commit 30bc8ef

Browse files
authored
Allow multiple root children in test renderer traversal API (#13017)
1 parent d480782 commit 30bc8ef

File tree

2 files changed

+101
-34
lines changed

2 files changed

+101
-34
lines changed

packages/react-test-renderer/src/ReactTestRenderer.js

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,45 @@ const validWrapperTypes = new Set([
200200
ClassComponent,
201201
HostComponent,
202202
ForwardRef,
203+
// Normally skipped, but used when there's more than one root child.
204+
HostRoot,
203205
]);
204206

207+
function getChildren(parent: Fiber) {
208+
const children = [];
209+
const startingNode = parent;
210+
let node: Fiber = startingNode;
211+
if (node.child === null) {
212+
return children;
213+
}
214+
node.child.return = node;
215+
node = node.child;
216+
outer: while (true) {
217+
let descend = false;
218+
if (validWrapperTypes.has(node.tag)) {
219+
children.push(wrapFiber(node));
220+
} else if (node.tag === HostText) {
221+
children.push('' + node.memoizedProps);
222+
} else {
223+
descend = true;
224+
}
225+
if (descend && node.child !== null) {
226+
node.child.return = node;
227+
node = node.child;
228+
continue;
229+
}
230+
while (node.sibling === null) {
231+
if (node.return === startingNode) {
232+
break outer;
233+
}
234+
node = (node.return: any);
235+
}
236+
(node.sibling: any).return = node.return;
237+
node = (node.sibling: any);
238+
}
239+
return children;
240+
}
241+
205242
class ReactTestInstance {
206243
_fiber: Fiber;
207244

@@ -246,6 +283,13 @@ class ReactTestInstance {
246283
let parent = this._fiber.return;
247284
while (parent !== null) {
248285
if (validWrapperTypes.has(parent.tag)) {
286+
if (parent.tag === HostRoot) {
287+
// Special case: we only "materialize" instances for roots
288+
// if they have more than a single child. So we'll check that now.
289+
if (getChildren(parent).length < 2) {
290+
return null;
291+
}
292+
}
249293
return wrapFiber(parent);
250294
}
251295
parent = parent.return;
@@ -254,38 +298,7 @@ class ReactTestInstance {
254298
}
255299

256300
get children(): Array<ReactTestInstance | string> {
257-
const children = [];
258-
const startingNode = this._currentFiber();
259-
let node: Fiber = startingNode;
260-
if (node.child === null) {
261-
return children;
262-
}
263-
node.child.return = node;
264-
node = node.child;
265-
outer: while (true) {
266-
let descend = false;
267-
if (validWrapperTypes.has(node.tag)) {
268-
children.push(wrapFiber(node));
269-
} else if (node.tag === HostText) {
270-
children.push('' + node.memoizedProps);
271-
} else {
272-
descend = true;
273-
}
274-
if (descend && node.child !== null) {
275-
node.child.return = node;
276-
node = node.child;
277-
continue;
278-
}
279-
while (node.sibling === null) {
280-
if (node.return === startingNode) {
281-
break outer;
282-
}
283-
node = (node.return: any);
284-
}
285-
(node.sibling: any).return = node.return;
286-
node = (node.sibling: any);
287-
}
288-
return children;
301+
return getChildren(this._currentFiber());
289302
}
290303

291304
// Custom search functions
@@ -469,10 +482,20 @@ const ReactTestRendererFiber = {
469482
configurable: true,
470483
enumerable: true,
471484
get: function() {
472-
if (root === null || root.current.child === null) {
485+
if (root === null) {
486+
throw new Error("Can't access .root on unmounted test renderer");
487+
}
488+
const children = getChildren(root.current);
489+
if (children.length === 0) {
473490
throw new Error("Can't access .root on unmounted test renderer");
491+
} else if (children.length === 1) {
492+
// Normally, we skip the root and just give you the child.
493+
return children[0];
494+
} else {
495+
// However, we give you the root if there's more than one root child.
496+
// We could make this the behavior for all cases but it would be a breaking change.
497+
return wrapFiber(root.current);
474498
}
475-
return wrapFiber(root.current.child);
476499
},
477500
}: Object),
478501
);

packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,48 @@ describe('ReactTestRendererTraversal', () => {
199199
expect(nestedViews[1].parent).toBe(expectedParent);
200200
expect(nestedViews[2].parent).toBe(expectedParent);
201201
});
202+
203+
it('can have special nodes as roots', () => {
204+
const FR = React.forwardRef(props => <section {...props} />);
205+
expect(
206+
ReactTestRenderer.create(
207+
<FR>
208+
<div />
209+
<div />
210+
</FR>,
211+
).root.findAllByType('div').length,
212+
).toBe(2);
213+
expect(
214+
ReactTestRenderer.create(
215+
<React.Fragment>
216+
<div />
217+
<div />
218+
</React.Fragment>,
219+
).root.findAllByType('div').length,
220+
).toBe(2);
221+
expect(
222+
ReactTestRenderer.create(
223+
<React.Fragment key="foo">
224+
<div />
225+
<div />
226+
</React.Fragment>,
227+
).root.findAllByType('div').length,
228+
).toBe(2);
229+
expect(
230+
ReactTestRenderer.create(
231+
<React.StrictMode>
232+
<div />
233+
<div />
234+
</React.StrictMode>,
235+
).root.findAllByType('div').length,
236+
).toBe(2);
237+
expect(
238+
ReactTestRenderer.create(
239+
<Context.Provider>
240+
<div />
241+
<div />
242+
</Context.Provider>,
243+
).root.findAllByType('div').length,
244+
).toBe(2);
245+
});
202246
});

0 commit comments

Comments
 (0)