diff --git a/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js b/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js index f7d90e451..9957e89da 100644 --- a/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js +++ b/packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js @@ -112,7 +112,17 @@ function toTree(vnode) { case HostRoot: // 3 return childrenToTree(node.child); case HostPortal: { // 4 - return childrenToTree(node.child); + const { stateNode: { containerInfo } } = node; + const props = { containerInfo }; + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(node.key), + ref: node.ref, + instance: null, + rendered: childrenToTree(node.child), + }; } case ClassComponent: return { diff --git a/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js b/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js index 722fbe693..446658f4c 100644 --- a/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js +++ b/packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js @@ -113,7 +113,17 @@ function toTree(vnode) { case HostRoot: // 3 return childrenToTree(node.child); case HostPortal: { // 4 - return childrenToTree(node.child); + const { stateNode: { containerInfo } } = node; + const props = { containerInfo }; + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(node.key), + ref: node.ref, + instance: null, + rendered: childrenToTree(node.child), + }; } case ClassComponent: return { diff --git a/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js b/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js index 3c7a07e7a..5a4557153 100644 --- a/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js +++ b/packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js @@ -118,7 +118,17 @@ function toTree(vnode) { case HostRoot: // 3 return childrenToTree(node.child); case HostPortal: { // 4 - return childrenToTree(node.child); + const { stateNode: { containerInfo } } = node; + const props = { containerInfo }; + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(node.key), + ref: node.ref, + instance: null, + rendered: childrenToTree(node.child), + }; } case ClassComponent: return { diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index b2e1ca1f3..b9b0f5016 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -118,7 +118,17 @@ function toTree(vnode) { case HostRoot: // 3 return childrenToTree(node.child); case HostPortal: { // 4 - return childrenToTree(node.child); + const { stateNode: { containerInfo } } = node; + const props = { containerInfo }; + return { + nodeType: 'portal', + type: Portal, + props, + key: ensureKeyOrUndefined(node.key), + ref: node.ref, + instance: null, + rendered: childrenToTree(node.child), + }; } case ClassComponent: return { diff --git a/packages/enzyme-adapter-utils/package.json b/packages/enzyme-adapter-utils/package.json index 81acdbdee..969b82646 100644 --- a/packages/enzyme-adapter-utils/package.json +++ b/packages/enzyme-adapter-utils/package.json @@ -36,7 +36,8 @@ "dependencies": { "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", - "prop-types": "^15.6.2" + "prop-types": "^15.6.2", + "react-is": "^16.4.2" }, "peerDependencies": { "react": "0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0" diff --git a/packages/enzyme-adapter-utils/src/Utils.js b/packages/enzyme-adapter-utils/src/Utils.js index 5c33d0da3..73a7990ba 100644 --- a/packages/enzyme-adapter-utils/src/Utils.js +++ b/packages/enzyme-adapter-utils/src/Utils.js @@ -1,4 +1,7 @@ import functionName from 'function.prototype.name'; +import { + Portal, +} from 'react-is'; import createMountWrapper from './createMountWrapper'; import createRenderWrapper from './createRenderWrapper'; @@ -107,6 +110,10 @@ export function displayNameOfNode(node) { if (!type) return null; + if (type === Portal) { + return 'Portal'; + } + return type.displayName || (typeof type === 'function' ? functionName(type) : type.name || type); } @@ -117,6 +124,9 @@ export function nodeTypeFromType(type) { if (type && type.prototype && type.prototype.isReactComponent) { return 'class'; } + if (type && type === Portal) { + return 'portal'; + } return 'function'; } diff --git a/packages/enzyme-test-suite/test/Adapter-spec.jsx b/packages/enzyme-test-suite/test/Adapter-spec.jsx index d988b9b1b..727361b1c 100644 --- a/packages/enzyme-test-suite/test/Adapter-spec.jsx +++ b/packages/enzyme-test-suite/test/Adapter-spec.jsx @@ -4,6 +4,9 @@ import jsdom from 'jsdom'; import { get } from 'enzyme/build/configuration'; import { configure, shallow } from 'enzyme'; import inspect from 'object-inspect'; +import { + Portal, +} from 'react-is'; import './_helpers/setupAdapters'; import Adapter from './_helpers/adapter'; @@ -226,13 +229,23 @@ describe('Adapter', () => { ref: null, instance: null, rendered: { - nodeType: 'host', - type: 'div', - props: { className: 'Foo' }, + nodeType: 'portal', + type: Portal, + props: { + containerInfo: document.body, + }, key: undefined, ref: null, instance: null, - rendered: ['Hello World!'], + rendered: { + nodeType: 'host', + type: 'div', + props: { className: 'Foo' }, + key: undefined, + ref: null, + instance: null, + rendered: ['Hello World!'], + }, }, })); }); diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index 19473ba15..4f2a08a89 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-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 { @@ -426,6 +429,103 @@ describeWithDOM('mount', () => { }); }); + describeIf(is('>= 16'), 'portals', () => { + it('should show portals in mount debug tree', () => { + const containerDiv = global.document.createElement('div'); + const Foo = () => ( +
+ {createPortal( +
InPortal
, + containerDiv, + )} +
+ ); + + const wrapper = mount(); + expect(wrapper.debug()).to.equal(` +
+ +
+ InPortal +
+
+
+
`); + }); + + it('should show portal container in debug tree', () => { + const containerDiv = global.document.createElement('div'); + containerDiv.setAttribute('data-foo', 'bar'); + const Foo = () => ( +
+ {createPortal( +
InPortal
, + containerDiv, + )} +
+ ); + + const wrapper = mount(); + expect(wrapper.debug({ verbose: true })).to.equal(` +
+ ...
}> +
+ InPortal +
+ + +
`); + }); + + it('should show nested portal children in debug tree', () => { + const Bar = () => null; + + const containerDiv = global.document.createElement('div'); + const Foo = () => ( +
+ {createPortal( +
+
+ +
+
, + containerDiv, + )} +
+ ); + + const wrapper = mount(); + expect(wrapper.debug()).to.equal(` +
+ +
+
+ +
+
+
+
+
`); + }); + + it('should have top level portals in debug tree', () => { + const containerDiv = global.document.createElement('div'); + const Foo = () => createPortal( +
InPortal
, + containerDiv, + ); + + const wrapper = mount(); + expect(wrapper.debug()).to.equal(` + +
+ InPortal +
+
+
`); + }); + }); + describe('.contains(node)', () => { it('should allow matches on the root node', () => { const a =
; @@ -1260,6 +1360,25 @@ describeWithDOM('mount', () => { expect(wrapper.children()).to.have.lengthOf(1); }); }); + + itIf(is('>= 16'), 'should find mounted portals by name', () => { + const containerDiv = global.document.createElement('div'); + const Foo = () => ( +
+ {createPortal( +
InPortal
, + containerDiv, + )} +
+ ); + + const wrapper = mount(); + expect(wrapper.find('Portal').debug()).to.equal(` +
+ InPortal +
+
`); + }); }); describe('.findWhere(predicate)', () => { @@ -1582,6 +1701,26 @@ describeWithDOM('mount', () => { wrapper.findWhere(spy); expect(spy).to.have.property('callCount', 2); }); + + itIf(is('>= 16'), 'should find mounted portals by react-is Portal type', () => { + const containerDiv = global.document.createElement('div'); + const Foo = () => ( +
+ {createPortal( +
InPortal
, + containerDiv, + )} +
+ ); + + const wrapper = mount(); + expect(wrapper.findWhere(node => node.type() === Portal).debug()) + .to.equal(` +
+ InPortal +
+
`); + }); }); describe('.setProps(newProps[, callback])', () => {