Skip to content

Commit

Permalink
Merge pull request #1169 from neoziro/react-16-updated-props
Browse files Browse the repository at this point in the history
Fix node resolving in React 16 adapter
  • Loading branch information
lelandrichardson authored Sep 30, 2017
2 parents 2b9a4db + 8490f35 commit 6b0d1f1
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 4 deletions.
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

0 comments on commit 6b0d1f1

Please sign in to comment.