Skip to content

Commit

Permalink
Fix node determinism in React v16 adapter
Browse files Browse the repository at this point in the history
Fix #1163
  • Loading branch information
gregberge committed Sep 29, 2017
1 parent 5051104 commit 4a3762a
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 3 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
103 changes: 103 additions & 0 deletions packages/enzyme-adapter-react-16/src/findCurrentFiberUsingSlowPath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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;

0 comments on commit 4a3762a

Please sign in to comment.