Allow multiple root children in test renderer traversal API (#13017)

This commit is contained in:
Dan Abramov 2018-06-11 20:03:51 +01:00 committed by GitHub
parent d480782c41
commit 30bc8ef792
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 101 additions and 34 deletions

View File

@ -200,8 +200,45 @@ const validWrapperTypes = new Set([
ClassComponent,
HostComponent,
ForwardRef,
// Normally skipped, but used when there's more than one root child.
HostRoot,
]);
function getChildren(parent: Fiber) {
const children = [];
const startingNode = parent;
let node: Fiber = startingNode;
if (node.child === null) {
return children;
}
node.child.return = node;
node = node.child;
outer: while (true) {
let descend = false;
if (validWrapperTypes.has(node.tag)) {
children.push(wrapFiber(node));
} else if (node.tag === HostText) {
children.push('' + node.memoizedProps);
} else {
descend = true;
}
if (descend && node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
while (node.sibling === null) {
if (node.return === startingNode) {
break outer;
}
node = (node.return: any);
}
(node.sibling: any).return = node.return;
node = (node.sibling: any);
}
return children;
}
class ReactTestInstance {
_fiber: Fiber;
@ -246,6 +283,13 @@ class ReactTestInstance {
let parent = this._fiber.return;
while (parent !== null) {
if (validWrapperTypes.has(parent.tag)) {
if (parent.tag === HostRoot) {
// Special case: we only "materialize" instances for roots
// if they have more than a single child. So we'll check that now.
if (getChildren(parent).length < 2) {
return null;
}
}
return wrapFiber(parent);
}
parent = parent.return;
@ -254,38 +298,7 @@ class ReactTestInstance {
}
get children(): Array<ReactTestInstance | string> {
const children = [];
const startingNode = this._currentFiber();
let node: Fiber = startingNode;
if (node.child === null) {
return children;
}
node.child.return = node;
node = node.child;
outer: while (true) {
let descend = false;
if (validWrapperTypes.has(node.tag)) {
children.push(wrapFiber(node));
} else if (node.tag === HostText) {
children.push('' + node.memoizedProps);
} else {
descend = true;
}
if (descend && node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
while (node.sibling === null) {
if (node.return === startingNode) {
break outer;
}
node = (node.return: any);
}
(node.sibling: any).return = node.return;
node = (node.sibling: any);
}
return children;
return getChildren(this._currentFiber());
}
// Custom search functions
@ -469,10 +482,20 @@ const ReactTestRendererFiber = {
configurable: true,
enumerable: true,
get: function() {
if (root === null || root.current.child === null) {
if (root === null) {
throw new Error("Can't access .root on unmounted test renderer");
}
return wrapFiber(root.current.child);
const children = getChildren(root.current);
if (children.length === 0) {
throw new Error("Can't access .root on unmounted test renderer");
} else if (children.length === 1) {
// Normally, we skip the root and just give you the child.
return children[0];
} else {
// However, we give you the root if there's more than one root child.
// We could make this the behavior for all cases but it would be a breaking change.
return wrapFiber(root.current);
}
},
}: Object),
);

View File

@ -199,4 +199,48 @@ describe('ReactTestRendererTraversal', () => {
expect(nestedViews[1].parent).toBe(expectedParent);
expect(nestedViews[2].parent).toBe(expectedParent);
});
it('can have special nodes as roots', () => {
const FR = React.forwardRef(props => <section {...props} />);
expect(
ReactTestRenderer.create(
<FR>
<div />
<div />
</FR>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<React.Fragment>
<div />
<div />
</React.Fragment>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<React.Fragment key="foo">
<div />
<div />
</React.Fragment>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<React.StrictMode>
<div />
<div />
</React.StrictMode>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<Context.Provider>
<div />
<div />
</Context.Provider>,
).root.findAllByType('div').length,
).toBe(2);
});
});