diff --git a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js index 0d7a7e2db..0c2dd7c88 100644 --- a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js +++ b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js @@ -118,6 +118,7 @@ class ReactThirteenAdapter extends EnzymeAdapter { assertDomAvailable('mount'); const domNode = options.attachTo || global.document.createElement('div'); let instance = null; + const adapter = this; return { render(el, context, callback) { if (instance === null) { @@ -128,7 +129,7 @@ class ReactThirteenAdapter extends EnzymeAdapter { context, ...(ref && { ref }), }; - const ReactWrapperComponent = createMountWrapper(el, options); + const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter }); const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps); instance = React.render(wrappedEl, domNode); if (typeof callback === 'function') { diff --git a/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js b/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js index d87e25f19..41a1625ac 100644 --- a/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js +++ b/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js @@ -91,6 +91,7 @@ class ReactFourteenAdapter extends EnzymeAdapter { assertDomAvailable('mount'); const domNode = options.attachTo || global.document.createElement('div'); let instance = null; + const adapter = this; return { render(el, context, callback) { if (instance === null) { @@ -101,7 +102,7 @@ class ReactFourteenAdapter extends EnzymeAdapter { context, ...(ref && { ref }), }; - const ReactWrapperComponent = createMountWrapper(el, options); + const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter }); const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps); instance = ReactDOM.render(wrappedEl, domNode); if (typeof callback === 'function') { diff --git a/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js b/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js index 472932d36..81897c7ae 100644 --- a/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js +++ b/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js @@ -124,6 +124,7 @@ class ReactFifteenFourAdapter extends EnzymeAdapter { assertDomAvailable('mount'); const domNode = options.attachTo || global.document.createElement('div'); let instance = null; + const adapter = this; return { render(el, context, callback) { if (instance === null) { @@ -134,7 +135,7 @@ class ReactFifteenFourAdapter extends EnzymeAdapter { context, ...(ref && { ref }), }; - const ReactWrapperComponent = createMountWrapper(el, options); + const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter }); const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps); instance = ReactDOM.render(wrappedEl, domNode); if (typeof callback === 'function') { diff --git a/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js b/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js index db557717f..0c9deaed7 100644 --- a/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js +++ b/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js @@ -124,6 +124,7 @@ class ReactFifteenAdapter extends EnzymeAdapter { assertDomAvailable('mount'); const domNode = options.attachTo || global.document.createElement('div'); let instance = null; + const adapter = this; return { render(el, context, callback) { if (instance === null) { @@ -134,7 +135,7 @@ class ReactFifteenAdapter extends EnzymeAdapter { context, ...(ref && { ref }), }; - const ReactWrapperComponent = createMountWrapper(el, options); + const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter }); const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps); instance = ReactDOM.render(wrappedEl, domNode); if (typeof callback === 'function') { diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index b603a2cfb..6d6ff18b6 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -47,6 +47,7 @@ const HostText = 6; const Mode = 11; const ContextConsumerType = 12; const ContextProviderType = 13; +const ForwardRefType = 14; function nodeAndSiblingsArray(nodeWithSibling) { const array = []; @@ -111,6 +112,7 @@ function toTree(vnode) { instance: null, rendered: childrenToTree(node.child), }; + case HostComponent: { // 5 let renderedNodes = flatten(nodeAndSiblingsArray(node.child).map(toTree)); if (renderedNodes.length === 0) { @@ -133,6 +135,17 @@ function toTree(vnode) { case ContextProviderType: // 13 case ContextConsumerType: // 12 return childrenToTree(node.child); + case ForwardRefType: { + return { + nodeType: 'function', + type: node.type, + props: { ...node.memoizedProps }, + key: ensureKeyOrUndefined(node.key), + ref: node.ref, + instance: null, + rendered: childrenToTree(node.child), + }; + } default: throw new Error(`Enzyme Internal Error: unknown node with tag ${node.tag}`); } @@ -195,6 +208,7 @@ class ReactSixteenAdapter extends EnzymeAdapter { const { attachTo, hydrateIn } = options; const domNode = hydrateIn || attachTo || global.document.createElement('div'); let instance = null; + const adapter = this; return { render(el, context, callback) { if (instance === null) { @@ -205,7 +219,7 @@ class ReactSixteenAdapter extends EnzymeAdapter { context, ...(ref && { ref }), }; - const ReactWrapperComponent = createMountWrapper(el, options); + const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter }); const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps); instance = hydrateIn ? ReactDOM.hydrate(wrappedEl, domNode) diff --git a/packages/enzyme-adapter-utils/src/createMountWrapper.jsx b/packages/enzyme-adapter-utils/src/createMountWrapper.jsx index 9fa7774b2..42757f949 100644 --- a/packages/enzyme-adapter-utils/src/createMountWrapper.jsx +++ b/packages/enzyme-adapter-utils/src/createMountWrapper.jsx @@ -3,6 +3,31 @@ import PropTypes from 'prop-types'; /* eslint react/forbid-prop-types: 0 */ +const stringOrFunction = PropTypes.oneOfType([PropTypes.func, PropTypes.string]); +const makeValidElementType = (adapter) => { + function validElementType(props, propName, ...args) { + if (!adapter.isValidElementType) { + return stringOrFunction(props, propName, ...args); + } + const propValue = props[propName]; + if (propValue == null || adapter.isValidElementType(propValue)) { + return null; + } + return new TypeError(`${propName} must be a valid element type!`); + } + validElementType.isRequired = function validElementTypeRequired(props, propName, ...args) { + if (!adapter.isValidElementType) { + return stringOrFunction.isRequired(props, propName, ...args); + } + const propValue = props[propName]; // eslint-disable-line react/destructuring-assignment + if (adapter.isValidElementType(propValue)) { + return null; + } + return new TypeError(`${propName} must be a valid element type!`); + }; + return validElementType; +}; + /** * This is a utility component to wrap around the nodes we are * passing in to `mount()`. Theoretically, you could do everything @@ -12,6 +37,8 @@ import PropTypes from 'prop-types'; * pass new props in. */ export default function createMountWrapper(node, options = {}) { + const { adapter } = options; + class WrapperComponent extends React.Component { constructor(...args) { super(...args); @@ -62,7 +89,7 @@ export default function createMountWrapper(node, options = {}) { } } WrapperComponent.propTypes = { - Component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired, + Component: makeValidElementType(adapter).isRequired, props: PropTypes.object.isRequired, context: PropTypes.object, }; diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index 4592068bb..835044aa4 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -22,6 +22,7 @@ import { createContext, createPortal, Fragment, + forwardRef, } from './_helpers/react-compat'; import { describeWithDOM, @@ -209,6 +210,51 @@ describeWithDOM('mount', () => { expect(wrapper.find('span').text()).to.equal('foo'); }); + describeIf(is('>= 16.3'), 'forwarded ref Components', () => { + it('should mount without complaint', () => { + const warningStub = sinon.stub(console, 'error'); + + const SomeComponent = forwardRef((props, ref) => ( +
+ )); + + mount(