diff --git a/packages/enzyme-adapter-react-16/package.json b/packages/enzyme-adapter-react-16/package.json index 8994ca7be..3604ba78d 100644 --- a/packages/enzyme-adapter-react-16/package.json +++ b/packages/enzyme-adapter-react-16/package.json @@ -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": { diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index 8c880e1be..ba4ab4b58 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -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 = []; @@ -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, @@ -133,7 +125,7 @@ function toTree(vnode) { rendered: childrenToTree(node.child), }; } - case ClassComponent: + case FiberTags.ClassComponent: return { nodeType: 'class', type: node.type, @@ -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, @@ -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]; @@ -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, @@ -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; diff --git a/packages/enzyme-adapter-react-16/src/detectFiberTags.js b/packages/enzyme-adapter-react-16/src/detectFiberTags.js new file mode 100644 index 000000000..57c037a91 --- /dev/null +++ b/packages/enzyme-adapter-react-16/src/detectFiberTags.js @@ -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, + }; +}; diff --git a/packages/enzyme-adapter-react-16/src/findCurrentFiberUsingSlowPath.js b/packages/enzyme-adapter-react-16/src/findCurrentFiberUsingSlowPath.js new file mode 100644 index 000000000..e8d33f608 --- /dev/null +++ b/packages/enzyme-adapter-react-16/src/findCurrentFiberUsingSlowPath.js @@ -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;