Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix node resolving in React 16 adapter #1169

Merged
merged 4 commits into from
Sep 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
createMountWrapper,
propsWithKeysAndRef,
} from 'enzyme-adapter-utils';
import findCurrentFiberUsingSlowPath from './findCurrentFiberUsingSlowPath';

const HostRoot = 3;
const ClassComponent = 2;
Expand Down Expand Up @@ -63,15 +64,15 @@ function toTree(vnode) {
// TODO(lmr): I'm not really sure I understand whether or not this is what
// i should be doing, or if this is a hack for something i'm doing wrong
// somewhere else. Should talk to sebastian about this perhaps
const node = vnode.alternate !== null ? vnode.alternate : vnode;
const node = findCurrentFiberUsingSlowPath(vnode);
switch (node.tag) {
case HostRoot: // 3
return toTree(node.child);
case ClassComponent:
return {
nodeType: 'class',
type: node.type,
props: { ...vnode.memoizedProps },
props: { ...node.memoizedProps },
key: node.key,
ref: node.ref,
instance: node.stateNode,
Expand All @@ -83,7 +84,7 @@ function toTree(vnode) {
return {
nodeType: 'function',
type: node.type,
props: { ...vnode.memoizedProps },
props: { ...node.memoizedProps },
key: node.key,
ref: node.ref,
instance: null,
Expand Down
104 changes: 104 additions & 0 deletions packages/enzyme-adapter-react-16/src/findCurrentFiberUsingSlowPath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Extracted from https://github.com/facebook/react/blob/7bdf93b17a35a5d8fcf0ceae0bf48ed5e6b16688/src/renderers/shared/fiber/ReactFiberTreeReflection.js#L104-L228
function findCurrentFiberUsingSlowPath(fiber) {
const alternate = fiber.alternate;
if (!alternate) {
return fiber;
}
// If we have two possible branches, we'll walk backwards up to the root
// to see what path the root points to. On the way we may hit one of the
// special cases and we'll deal with them.
let a = fiber;
let b = alternate;
while (true) { // eslint-disable-line
const parentA = a.return;
const parentB = parentA ? parentA.alternate : null;
if (!parentA || !parentB) {
// We're at the root.
break;
}

// If both copies of the parent fiber point to the same child, we can
// assume that the child is current. This happens when we bailout on low
// priority: the bailed out fiber's child reuses the current child.
if (parentA.child === parentB.child) {
let child = parentA.child;
while (child) {
if (child === a) {
// We've determined that A is the current branch.
return fiber;
}
if (child === b) {
// We've determined that B is the current branch.
return alternate;
}
child = child.sibling;
}
// We should never have an alternate for any mounting node. So the only
// way this could possibly happen is if this was unmounted, if at all.
throw new Error('Unable to find node on an unmounted component.');
}

if (a.return !== b.return) {
// The return pointer of A and the return pointer of B point to different
// fibers. We assume that return pointers never criss-cross, so A must
// belong to the child set of A.return, and B must belong to the child
// set of B.return.
a = parentA;
b = parentB;
} else {
// The return pointers point to the same fiber. We'll have to use the
// default, slow path: scan the child sets of each parent alternate to see
// which child belongs to which set.
//
// Search parent A's child set
let didFindChild = false;
let child = parentA.child;
while (child) {
if (child === a) {
didFindChild = true;
a = parentA;
b = parentB;
break;
}
if (child === b) {
didFindChild = true;
b = parentA;
a = parentB;
break;
}
child = child.sibling;
}
if (!didFindChild) {
// Search parent B's child set
child = parentB.child;
while (child) {
if (child === a) {
didFindChild = true;
a = parentB;
b = parentA;
break;
}
if (child === b) {
didFindChild = true;
b = parentB;
a = parentA;
break;
}
child = child.sibling;
}
if (!didFindChild) {
throw new Error('Child was not found in either parent set. This indicates a bug ' +
'in React related to the return pointer. Please file an issue.');
}
}
}
}
if (a.stateNode.current === a) {
// We've determined that A is the current branch.
return fiber;
}
// Otherwise B has to be current branch.
return alternate;
}

module.exports = findCurrentFiberUsingSlowPath;
39 changes: 38 additions & 1 deletion packages/enzyme-test-suite/test/Adapter-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('Adapter', () => {
hydratedTreeMatchesUnhydrated(<Four />);
});

it('treats mixed children correctlyf', () => {
it('treats mixed children correctly', () => {
class Foo extends React.Component {
render() {
return (
Expand Down Expand Up @@ -492,6 +492,43 @@ describe('Adapter', () => {
});
});

it('render node with updated props', () => {
class Dummy extends React.Component {
render() {
return null;
}
}

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

increment() {
this.setState({ count: this.state.count + 1 });
}

render() {
return <Dummy {...this.state} />;
}
}

const options = { mode: 'mount' };
const renderer = adapter.createRenderer(options);

renderer.render(<Counter />);

let tree = renderer.getNode();
expect(tree.rendered.props.count).to.equal(0);
tree.instance.increment();
tree = renderer.getNode();
expect(tree.rendered.props.count).to.equal(1);
tree.instance.increment();
tree = renderer.getNode();
expect(tree.rendered.props.count).to.equal(2);
});

it('renders basic shallow as well', () => {
// eslint-disable-next-line react/require-render-return
class Bar extends React.Component {
Expand Down