diff --git a/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js b/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js index 5058dae4f..f7d90e451 100644 --- a/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js +++ b/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js @@ -9,6 +9,7 @@ import ShallowRenderer from 'react-test-renderer/shallow'; import TestUtils from 'react-dom/test-utils'; import { isElement, + isPortal, isValidElementType, Fragment, Portal, @@ -16,8 +17,8 @@ import { import { EnzymeAdapter } from 'enzyme'; import { displayNameOfNode, - elementToTree, - nodeTypeFromType, + elementToTree as utilElementToTree, + nodeTypeFromType as utilNodeTypeFromType, mapNativeEventNames, propFromEvent, assertDomAvailable, @@ -72,6 +73,33 @@ function flatten(arr) { return result; } +function nodeTypeFromType(type) { + if (type === Portal) { + return 'portal'; + } + + return utilNodeTypeFromType(type); +} + +function elementToTree(el) { + if (!isPortal(el)) { + return utilElementToTree(el, elementToTree); + } + + const { children, containerInfo } = el; + const props = { children, containerInfo }; + + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(el.key), + ref: el.ref, + instance: null, + rendered: elementToTree(el.children), + }; +} + function toTree(vnode) { if (vnode == null) { return null; @@ -280,7 +308,7 @@ class ReactSixteenOneAdapter extends EnzymeAdapter { ref: cachedNode.ref, instance: renderer._instance, rendered: Array.isArray(output) - ? flatten(output).map(elementToTree) + ? flatten(output).map(el => elementToTree(el)) : elementToTree(output), }; }, diff --git a/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js b/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js index 7e67a0fe0..722fbe693 100644 --- a/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js +++ b/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js @@ -9,6 +9,7 @@ import ShallowRenderer from 'react-test-renderer/shallow'; import TestUtils from 'react-dom/test-utils'; import { isElement, + isPortal, isValidElementType, Fragment, Portal, @@ -17,8 +18,8 @@ import { EnzymeAdapter } from 'enzyme'; import { typeOfNode } from 'enzyme/build/Utils'; import { displayNameOfNode, - elementToTree, - nodeTypeFromType, + elementToTree as utilElementToTree, + nodeTypeFromType as utilNodeTypeFromType, mapNativeEventNames, propFromEvent, assertDomAvailable, @@ -73,6 +74,33 @@ function flatten(arr) { return result; } +function nodeTypeFromType(type) { + if (type === Portal) { + return 'portal'; + } + + return utilNodeTypeFromType(type); +} + +function elementToTree(el) { + if (!isPortal(el)) { + return utilElementToTree(el, elementToTree); + } + + const { children, containerInfo } = el; + const props = { children, containerInfo }; + + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(el.key), + ref: el.ref, + instance: null, + rendered: elementToTree(el.children), + }; +} + function toTree(vnode) { if (vnode == null) { return null; @@ -282,7 +310,7 @@ class ReactSixteenTwoAdapter extends EnzymeAdapter { ref: cachedNode.ref, instance: renderer._instance, rendered: Array.isArray(output) - ? flatten(output).map(elementToTree) + ? flatten(output).map(el => elementToTree(el)) : elementToTree(output), }; }, diff --git a/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js b/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js index bff713326..3c7a07e7a 100644 --- a/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js +++ b/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js @@ -10,6 +10,7 @@ import ShallowRenderer from 'react-test-renderer/shallow'; import TestUtils from 'react-dom/test-utils'; import { isElement, + isPortal, isValidElementType, AsyncMode, Fragment, @@ -24,8 +25,8 @@ import { EnzymeAdapter } from 'enzyme'; import { typeOfNode } from 'enzyme/build/Utils'; import { displayNameOfNode, - elementToTree, - nodeTypeFromType, + elementToTree as utilElementToTree, + nodeTypeFromType as utilNodeTypeFromType, mapNativeEventNames, propFromEvent, assertDomAvailable, @@ -78,6 +79,33 @@ function flatten(arr) { return result; } +function nodeTypeFromType(type) { + if (type === Portal) { + return 'portal'; + } + + return utilNodeTypeFromType(type); +} + +function elementToTree(el) { + if (!isPortal(el)) { + return utilElementToTree(el, elementToTree); + } + + const { children, containerInfo } = el; + const props = { children, containerInfo }; + + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(el.key), + ref: el.ref, + instance: null, + rendered: elementToTree(el.children), + }; +} + function toTree(vnode) { if (vnode == null) { return null; @@ -300,7 +328,7 @@ class ReactSixteenThreeAdapter extends EnzymeAdapter { ref: cachedNode.ref, instance: renderer._instance, rendered: Array.isArray(output) - ? flatten(output).map(elementToTree) + ? flatten(output).map(el => elementToTree(el)) : elementToTree(output), }; }, diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index 0baabffbc..b2e1ca1f3 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -10,6 +10,7 @@ import ShallowRenderer from 'react-test-renderer/shallow'; import TestUtils from 'react-dom/test-utils'; import { isElement, + isPortal, isValidElementType, AsyncMode, Fragment, @@ -24,8 +25,8 @@ import { EnzymeAdapter } from 'enzyme'; import { typeOfNode } from 'enzyme/build/Utils'; import { displayNameOfNode, - elementToTree, - nodeTypeFromType, + elementToTree as utilElementToTree, + nodeTypeFromType as utilNodeTypeFromType, mapNativeEventNames, propFromEvent, assertDomAvailable, @@ -78,6 +79,33 @@ function flatten(arr) { return result; } +function nodeTypeFromType(type) { + if (type === Portal) { + return 'portal'; + } + + return utilNodeTypeFromType(type); +} + +function elementToTree(el) { + if (!isPortal(el)) { + return utilElementToTree(el, elementToTree); + } + + const { children, containerInfo } = el; + const props = { children, containerInfo }; + + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(el.key), + ref: el.ref, + instance: null, + rendered: elementToTree(el.children), + }; +} + function toTree(vnode) { if (vnode == null) { return null; @@ -303,7 +331,7 @@ class ReactSixteenAdapter extends EnzymeAdapter { ref: cachedNode.ref, instance: renderer._instance, rendered: Array.isArray(output) - ? flatten(output).map(elementToTree) + ? flatten(output).map(el => elementToTree(el)) : elementToTree(output), }; }, diff --git a/packages/enzyme-adapter-utils/src/Utils.js b/packages/enzyme-adapter-utils/src/Utils.js index 89fb1a2c9..5c33d0da3 100644 --- a/packages/enzyme-adapter-utils/src/Utils.js +++ b/packages/enzyme-adapter-utils/src/Utils.js @@ -180,7 +180,11 @@ export function ensureKeyOrUndefined(key) { return key || (key === '' ? '' : undefined); } -export function elementToTree(el) { +export function elementToTree(el, recurse = elementToTree) { + if (typeof recurse !== 'function' && arguments.length === 3) { + // special case for backwards compat for `.map(elementToTree)` + recurse = elementToTree; // eslint-disable-line no-param-reassign + } if (el === null || typeof el !== 'object' || !('type' in el)) { return el; } @@ -193,9 +197,9 @@ export function elementToTree(el) { const { children } = props; let rendered = null; if (isArrayLike(children)) { - rendered = flatten(children).map(elementToTree); + rendered = flatten(children).map(x => recurse(x)); } else if (typeof children !== 'undefined') { - rendered = elementToTree(children); + rendered = recurse(children); } return { nodeType: nodeTypeFromType(type), diff --git a/packages/enzyme-test-suite/package.json b/packages/enzyme-test-suite/package.json index 885d696fe..d9b18f3b4 100644 --- a/packages/enzyme-test-suite/package.json +++ b/packages/enzyme-test-suite/package.json @@ -49,6 +49,7 @@ "eslint-config-airbnb": "^17.1.0", "eslint-plugin-import": "^2.14.0", "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-react": "^7.11.1" + "eslint-plugin-react": "^7.11.1", + "react-is": "^16.4.2" } } diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index d4286d9eb..a7387e2c9 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -15,6 +15,9 @@ import { sym, } from 'enzyme/build/Utils'; import getAdapter from 'enzyme/build/getAdapter'; +import { + Portal, +} from 'react-is'; import './_helpers/setupAdapters'; import { @@ -272,6 +275,92 @@ describe('shallow', () => { }); }); + describeIf(is('>= 16'), 'portals', () => { + it('should show portals in shallow debug tree', () => { + const Foo = () => ( +
+ {createPortal( +
InPortal
, + { nodeType: 1 }, + )} +
+ ); + + const wrapper = shallow(); + expect(wrapper.debug()).to.equal(`
+ +
+ InPortal +
+
+
`); + }); + + it('should show portal container in shallow debug tree', () => { + const Foo = () => ( +
+ {createPortal( +
InPortal
, + { nodeType: 1 }, + )} +
+ ); + + const wrapper = shallow(); + expect(wrapper.debug({ verbose: true })).to.equal(`
+ +
+ InPortal +
+
+
`); + }); + + it('should show nested portal children in shallow debug tree', () => { + const Bar = () => null; + + const Foo = () => ( +
+ {createPortal( +
+
+ +
+
, + { nodeType: 1 }, + )} +
+ ); + + const wrapper = shallow(); + expect(wrapper.debug()).to.equal(`
+ +
+
+ +
+
+
+
`); + }); + + it('should have top level portals in debug tree', () => { + const Foo = () => ( + createPortal( +
InPortal
, + { nodeType: 1 }, + ) + ); + + const wrapper = shallow(); + expect(wrapper.debug()).to.equal(` +
+ InPortal +
+
`); + }); + }); + describe('.contains(node)', () => { it('should allow matches on the root node', () => { const a =
; @@ -1074,6 +1163,42 @@ describe('shallow', () => { expect(wrapper.children()).to.have.lengthOf(0); }); }); + + itIf(is('>= 16'), 'should find portals by name', () => { + const Foo = () => ( +
+ {createPortal( +
InPortal
, + { nodeType: 1 }, + )} +
+ ); + + const wrapper = shallow(); + + expect(wrapper.find('Portal')).to.have.lengthOf(1); + }); + + itIf(is('>= 16'), 'should find elements through portals', () => { + const Foo = () => ( +
+ {createPortal( +
+

Successful Portal!

+ +
, + { nodeType: 1 }, + )} +
+ ); + + + const wrapper = shallow(); + + expect(wrapper.find('h1')).to.have.lengthOf(1); + + expect(wrapper.find('span')).to.have.lengthOf(1); + }); }); describe('.findWhere(predicate)', () => { @@ -1368,6 +1493,21 @@ describe('shallow', () => { wrapper.findWhere(spy); expect(spy).to.have.property('callCount', 2); }); + + itIf(is('>= 16'), 'should find portals by react-is Portal type', () => { + const Foo = () => ( +
+ {createPortal( +
InPortal
, + { nodeType: 1 }, + )} +
+ ); + + const wrapper = shallow(); + + expect(wrapper.findWhere(node => node.type() === Portal)).to.have.lengthOf(1); + }); }); describe('.setProps(newProps)', () => {