Skip to content

Commit

Permalink
Allow multiple root children in test renderer traversal API (#13017)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Jun 11, 2018
1 parent d480782 commit 30bc8ef
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 34 deletions.
91 changes: 57 additions & 34 deletions packages/react-test-renderer/src/ReactTestRenderer.js
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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");
}
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);
}
return wrapFiber(root.current.child);
},
}: Object),
);
Expand Down
Expand Up @@ -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);
});
});

0 comments on commit 30bc8ef

Please sign in to comment.