Skip to content
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
1 change: 0 additions & 1 deletion packages/enzyme-adapter-react-16/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"object.values": "^1.0.4",
"prop-types": "^15.6.2",
"react-is": "^16.4.2",
"react-reconciler": "^0.7.0",
"react-test-renderer": "^16.0.0-0"
},
"peerDependencies": {
Expand Down
44 changes: 20 additions & 24 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,11 @@ import {
propsWithKeysAndRef,
ensureKeyOrUndefined,
} from 'enzyme-adapter-utils';
import { findCurrentFiberUsingSlowPath } from 'react-reconciler/reflection';

const HostRoot = 3;
const ClassComponent = 2;
const FragmentType = 10;
const FunctionalComponent = 1;
const HostPortal = 4;
const HostComponent = 5;
const HostText = 6;
const Mode = 11;
const ContextConsumerType = 12;
const ContextProviderType = 13;
const ForwardRefType = 14;
import findCurrentFiberUsingSlowPath from './findCurrentFiberUsingSlowPath';
import detectFiberTags from './detectFiberTags';

// Lazily populated if DOM is available.
let FiberTags = null;

function nodeAndSiblingsArray(nodeWithSibling) {
const array = [];
Expand Down Expand Up @@ -115,9 +107,9 @@ function toTree(vnode) {
// somewhere else. Should talk to sebastian about this perhaps
const node = findCurrentFiberUsingSlowPath(vnode);
switch (node.tag) {
case HostRoot: // 3
case FiberTags.HostRoot:
return childrenToTree(node.child);
case HostPortal: { // 4
case FiberTags.HostPortal: {
const {
stateNode: { containerInfo },
memoizedProps: children,
Expand All @@ -133,7 +125,7 @@ function toTree(vnode) {
rendered: childrenToTree(node.child),
};
}
case ClassComponent:
case FiberTags.ClassComponent:
return {
nodeType: 'class',
type: node.type,
Expand All @@ -143,7 +135,7 @@ function toTree(vnode) {
instance: node.stateNode,
rendered: childrenToTree(node.child),
};
case FunctionalComponent: // 1
case FiberTags.FunctionalComponent:
return {
nodeType: 'function',
type: node.type,
Expand All @@ -154,7 +146,7 @@ function toTree(vnode) {
rendered: childrenToTree(node.child),
};

case HostComponent: { // 5
case FiberTags.HostComponent: {
let renderedNodes = flatten(nodeAndSiblingsArray(node.child).map(toTree));
if (renderedNodes.length === 0) {
renderedNodes = [node.memoizedProps.children];
Expand All @@ -169,14 +161,14 @@ function toTree(vnode) {
rendered: renderedNodes,
};
}
case HostText: // 6
case FiberTags.HostText:
return node.memoizedProps;
case FragmentType: // 10
case Mode: // 11
case ContextProviderType: // 13
case ContextConsumerType: // 12
case FiberTags.Fragment:
case FiberTags.Mode:
case FiberTags.ContextProvider:
case FiberTags.ContextConsumer:
return childrenToTree(node.child);
case ForwardRefType: {
case FiberTags.ForwardRef: {
return {
nodeType: 'function',
type: node.type,
Expand Down Expand Up @@ -255,6 +247,10 @@ class ReactSixteenAdapter extends EnzymeAdapter {

createMountRenderer(options) {
assertDomAvailable('mount');
if (FiberTags === null) {
// Requires DOM.
FiberTags = detectFiberTags();
}
const { attachTo, hydrateIn } = options;
const domNode = hydrateIn || attachTo || global.document.createElement('div');
let instance = null;
Expand Down
63 changes: 63 additions & 0 deletions packages/enzyme-adapter-react-16/src/detectFiberTags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import ReactDOM from 'react-dom';

function getFiber(element) {
const container = global.document.createElement('div');
let inst = null;
class Tester extends React.Component {
render() {
inst = this;
return element;
}
}
ReactDOM.render(React.createElement(Tester), container);
return inst._reactInternalFiber.child;
}

module.exports = function detectFiberTags() {
const supportsMode = typeof React.StrictMode !== 'undefined';
const supportsContext = typeof React.createContext !== 'undefined';
const supportsForwardRef = typeof React.forwardRef !== 'undefined';

function Fn() {
return null;
}
// eslint-disable-next-line react/prefer-stateless-function
class Cls extends React.Component {
render() {
return null;
}
}
let Ctx = null;
let FwdRef = null;
if (supportsContext) {
Ctx = React.createContext();
}
if (supportsForwardRef) {
// React will warn if we don't have both arguments.
// eslint-disable-next-line no-unused-vars
FwdRef = React.forwardRef((props, ref) => null);
}

return {
HostRoot: getFiber('test').return.return.tag, // Go two levels above to find the root
ClassComponent: getFiber(React.createElement(Cls)).tag,
Fragment: getFiber([['nested']]).tag,
FunctionalComponent: getFiber(React.createElement(Fn)).tag,
HostPortal: getFiber(ReactDOM.createPortal(null, global.document.createElement('div'))).tag,
HostComponent: getFiber(React.createElement('span')).tag,
HostText: getFiber('text').tag,
Mode: supportsMode
? getFiber(React.createElement(React.StrictMode)).tag
: -1,
ContextConsumer: supportsContext
? getFiber(React.createElement(Ctx.Consumer, null, () => null)).tag
: -1,
ContextProvider: supportsContext
? getFiber(React.createElement(Ctx.Provider, { value: null })).tag
: -1,
ForwardRef: supportsForwardRef
? getFiber(React.createElement(FwdRef)).tag
: -1,
};
};
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;
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;
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;
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);
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;