From 9745de0bf25e826186be07e7846f4ecd7c685592 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 1/7] [New] Debug: `debugNode` now returns `[function]` for function children --- .../enzyme-test-suite/test/Debug-spec.jsx | 30 +++++++++++++++++++ packages/enzyme/src/Debug.js | 4 +++ 2 files changed, 34 insertions(+) diff --git a/packages/enzyme-test-suite/test/Debug-spec.jsx b/packages/enzyme-test-suite/test/Debug-spec.jsx index fc9c586a3..f665bc5a2 100644 --- a/packages/enzyme-test-suite/test/Debug-spec.jsx +++ b/packages/enzyme-test-suite/test/Debug-spec.jsx @@ -748,5 +748,35 @@ describe('debug', () => { ` )); }); + + it('handles function children', () => { + class Abomination extends React.Component { + render() { + /* eslint no-unused-vars: 0, func-names: 0 */ + return ( +
+ {function Foo() { /* hi */ }} + {} + {arrow => arrow('function')} + {[1, 2, NaN]} + {function (anonymous) {}} +
+ ); + } + } + + const wrapper = shallow(); + expect(wrapper.debug()).to.equal(( + `
+ [function Foo] + + [function] + 1 + 2 + NaN + [function] +
` + )); + }); }); }); diff --git a/packages/enzyme/src/Debug.js b/packages/enzyme/src/Debug.js index 27ccb372a..0d5974e84 100644 --- a/packages/enzyme/src/Debug.js +++ b/packages/enzyme/src/Debug.js @@ -66,6 +66,10 @@ function indentChildren(childrenStrs, indentLength) { export function debugNode(node, indentLength = 2, options = {}) { if (typeof node === 'string' || typeof node === 'number') return escape(node); + if (typeof node === 'function') { + const name = functionName(node); + return `[function${name ? ` ${name}` : ''}]`; + } if (!node) return ''; const childrenStrs = compact(childrenOfNode(node).map(n => debugNode(n, indentLength, options))); From 0d9b482e67f543ac8c9cef69449065951a150f60 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 2/7] [enzyme-adapter-utils] [New] add `displayNameOfNode` --- packages/enzyme-adapter-utils/package.json | 1 + packages/enzyme-adapter-utils/src/Utils.js | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/enzyme-adapter-utils/package.json b/packages/enzyme-adapter-utils/package.json index 363a13fe8..3baa36dce 100644 --- a/packages/enzyme-adapter-utils/package.json +++ b/packages/enzyme-adapter-utils/package.json @@ -35,6 +35,7 @@ "license": "MIT", "dependencies": { "lodash": "^4.17.4", + "function.prototype.name": "^1.0.3", "object.assign": "^4.1.0", "prop-types": "^15.6.0" }, diff --git a/packages/enzyme-adapter-utils/src/Utils.js b/packages/enzyme-adapter-utils/src/Utils.js index 491e9fcc5..6c2f731ea 100644 --- a/packages/enzyme-adapter-utils/src/Utils.js +++ b/packages/enzyme-adapter-utils/src/Utils.js @@ -1,3 +1,4 @@ +import functionName from 'function.prototype.name'; import createMountWrapper from './createMountWrapper'; import createRenderWrapper from './createRenderWrapper'; @@ -86,6 +87,16 @@ export function assertDomAvailable(feature) { } } +export function displayNameOfNode(node) { + if (!node) return null; + + const { type } = node; + + if (!type) return null; + + return type.displayName || (typeof type === 'function' ? functionName(type) : type.name || type); +} + export function nodeTypeFromType(type) { if (typeof type === 'string') { return 'host'; From 7c69f1cc2b91e780fef6f97a166db7ebb77b42c7 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 3/7] [enzyme-adapter-react-{13,14,15.4,15,16] Add `displayNameOfNode`, `isValidElementType` --- .../src/ReactThirteenAdapter.js | 9 +++ .../src/ReactFourteenAdapter.js | 11 +++- .../src/ReactFifteenFourAdapter.js | 11 +++- .../src/ReactFifteenAdapter.js | 11 +++- packages/enzyme-adapter-react-16/package.json | 1 + .../src/ReactSixteenAdapter.js | 58 ++++++++++++++++--- 6 files changed, 91 insertions(+), 10 deletions(-) diff --git a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js index f9c461498..5594f284a 100644 --- a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js +++ b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js @@ -6,6 +6,7 @@ import ReactContext from 'react/lib/ReactContext'; import values from 'object.values'; import { EnzymeAdapter } from 'enzyme'; import { + displayNameOfNode, propFromEvent, withSetStateAllowed, assertDomAvailable, @@ -248,6 +249,10 @@ class ReactThirteenAdapter extends EnzymeAdapter { return React.createElement(node.type, propsWithKeysAndRef(node)); } + displayNameOfNode(node) { + return displayNameOfNode(node); + } + elementToNode(element) { return elementToTree(element); } @@ -260,6 +265,10 @@ class ReactThirteenAdapter extends EnzymeAdapter { return React.isValidElement(element); } + isValidElementType(object) { + return typeof object === 'string' || typeof object === 'function'; + } + createElement(...args) { return React.createElement(...args); } diff --git a/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js b/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js index 98a2694be..eb29900e6 100644 --- a/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js +++ b/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js @@ -5,9 +5,10 @@ import ReactDOMServer from 'react-dom/server'; // eslint-disable-next-line import/no-unresolved, import/extensions import TestUtils from 'react-addons-test-utils'; import values from 'object.values'; -import { isElement } from 'react-is'; +import { isElement, isValidElementType } from 'react-is'; import { EnzymeAdapter } from 'enzyme'; import { + displayNameOfNode, elementToTree, mapNativeEventNames, propFromEvent, @@ -228,10 +229,18 @@ class ReactFourteenAdapter extends EnzymeAdapter { return ReactDOM.findDOMNode(node.instance); } + displayNameOfNode(node) { + return displayNameOfNode(node); + } + isValidElement(element) { return isElement(element); } + isValidElementType(object) { + return isValidElementType(object); + } + createElement(...args) { return React.createElement(...args); } diff --git a/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js b/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js index 93ca3fbae..45bb8ebd2 100644 --- a/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js +++ b/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js @@ -5,9 +5,10 @@ import ReactDOMServer from 'react-dom/server'; // eslint-disable-next-line import/no-unresolved, import/extensions import TestUtils from 'react-addons-test-utils'; import values from 'object.values'; -import { isElement } from 'react-is'; +import { isElement, isValidElementType } from 'react-is'; import { EnzymeAdapter } from 'enzyme'; import { + displayNameOfNode, elementToTree, mapNativeEventNames, propFromEvent, @@ -259,10 +260,18 @@ class ReactFifteenFourAdapter extends EnzymeAdapter { return ReactDOM.findDOMNode(node.instance); } + displayNameOfNode(node) { + return displayNameOfNode(node); + } + isValidElement(element) { return isElement(element); } + isValidElementType(object) { + return isValidElementType(object); + } + createElement(...args) { return React.createElement(...args); } diff --git a/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js b/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js index 73b709497..18e1ada32 100644 --- a/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js +++ b/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js @@ -7,9 +7,10 @@ import TestUtils from 'react-dom/test-utils'; // eslint-disable-next-line import/no-unresolved, import/extensions import ShallowRenderer from 'react-test-renderer/shallow'; import values from 'object.values'; -import { isElement } from 'react-is'; +import { isElement, isValidElementType } from 'react-is'; import { EnzymeAdapter } from 'enzyme'; import { + displayNameOfNode, elementToTree, mapNativeEventNames, propFromEvent, @@ -259,10 +260,18 @@ class ReactFifteenAdapter extends EnzymeAdapter { return ReactDOM.findDOMNode(node.instance); } + displayNameOfNode(node) { + return displayNameOfNode(node); + } + isValidElement(element) { return isElement(element); } + isValidElementType(object) { + return isValidElementType(object); + } + createElement(...args) { return React.createElement(...args); } diff --git a/packages/enzyme-adapter-react-16/package.json b/packages/enzyme-adapter-react-16/package.json index 7bbb076da..37c22c5ae 100644 --- a/packages/enzyme-adapter-react-16/package.json +++ b/packages/enzyme-adapter-react-16/package.json @@ -35,6 +35,7 @@ "license": "MIT", "dependencies": { "enzyme-adapter-utils": "^1.3.0", + "function.prototype.name": "^1.0.3", "lodash": "^4.17.4", "object.assign": "^4.1.0", "object.values": "^1.0.4", diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index 68ba2c1d6..bd43e0dbb 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -1,4 +1,5 @@ /* eslint no-use-before-define: 0 */ +import functionName from 'function.prototype.name'; import React from 'react'; import ReactDOM from 'react-dom'; // eslint-disable-next-line import/no-unresolved, import/extensions @@ -7,9 +8,21 @@ import ReactDOMServer from 'react-dom/server'; import ShallowRenderer from 'react-test-renderer/shallow'; // eslint-disable-next-line import/no-unresolved, import/extensions import TestUtils from 'react-dom/test-utils'; -import { isElement } from 'react-is'; +import { + isElement, + isValidElementType, + AsyncMode, + Fragment, + ContextConsumer, + ContextProvider, + StrictMode, + ForwardRef, + Profiler, + Portal, +} from 'react-is'; import { EnzymeAdapter } from 'enzyme'; import { + displayNameOfNode, elementToTree, nodeTypeFromType, mapNativeEventNames, @@ -25,14 +38,14 @@ import { findCurrentFiberUsingSlowPath } from 'react-reconciler/reflection'; const HostRoot = 3; const ClassComponent = 2; -const Fragment = 10; +const FragmentType = 10; const FunctionalComponent = 1; const HostPortal = 4; const HostComponent = 5; const HostText = 6; const Mode = 11; -const ContextConsumer = 12; -const ContextProvider = 13; +const ContextConsumerType = 12; +const ContextProviderType = 13; function nodeAndSiblingsArray(nodeWithSibling) { const array = []; @@ -113,10 +126,10 @@ function toTree(vnode) { } case HostText: // 6 return node.memoizedProps; - case Fragment: // 10 + case FragmentType: // 10 case Mode: // 11 - case ContextProvider: // 13 - case ContextConsumer: // 12 + case ContextProviderType: // 13 + case ContextConsumerType: // 12 return childrenToTree(node.child); default: throw new Error(`Enzyme Internal Error: unknown node with tag ${node.tag}`); @@ -332,10 +345,41 @@ class ReactSixteenAdapter extends EnzymeAdapter { return nodeToHostNode(node); } + displayNameOfNode(node) { + if (!node) return null; + const { type, $$typeof } = node; + + switch (type || $$typeof) { + case AsyncMode: return 'AsyncMode'; + case Fragment: return 'Fragment'; + case StrictMode: return 'StrictMode'; + case Profiler: return 'Profiler'; + + default: { + const $$typeofType = type && type.$$typeof; + + switch ($$typeofType) { + case ContextConsumer: return 'ContextConsumer'; + case ContextProvider: return 'ContextProvider'; + case ForwardRef: { + const name = type.render.displayName || functionName(type.render); + return name ? `ForwardRef(${name})` : 'ForwardRef'; + } + case Portal: return 'Portal'; + default: return displayNameOfNode(node); + } + } + } + } + isValidElement(element) { return isElement(element); } + isValidElementType(object) { + return isValidElementType(object); + } + createElement(...args) { return React.createElement(...args); } From 0c5807629554612a92f671b3cd1e55d319bcaf2e Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 4/7] =?UTF-8?q?[New]=20`Debug`:=20`typeName`=20now=20calls?= =?UTF-8?q?=20the=20adapter=E2=80=99s=20`displayNameOfNode`=20if=20availab?= =?UTF-8?q?le?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/enzyme/src/Debug.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/enzyme/src/Debug.js b/packages/enzyme/src/Debug.js index 0d5974e84..83d5bfa1f 100644 --- a/packages/enzyme/src/Debug.js +++ b/packages/enzyme/src/Debug.js @@ -12,10 +12,15 @@ import { propsOfNode, childrenOfNode, } from './RSTTraversal'; +import { getAdapter } from './Utils'; const booleanValue = Function.bind.call(Function.call, Boolean.prototype.valueOf); export function typeName(node) { + const adapter = getAdapter(); + if (adapter.displayNameOfNode) { + return getAdapter().displayNameOfNode(node) || 'Component'; + } return typeof node.type === 'function' ? (node.type.displayName || functionName(node.type) || 'Component') : node.type; From 3fa33a3864917e33207ebe94d0174d50f300aa12 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 5/7] =?UTF-8?q?[New]=20`mount`/`shallow`:=20`.name()`:=20c?= =?UTF-8?q?all=20into=20adapter=E2=80=99s=20`displayNameOfNode`,=20if=20pr?= =?UTF-8?q?esent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/enzyme/src/ReactWrapper.js | 5 ++++- packages/enzyme/src/ShallowWrapper.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/enzyme/src/ReactWrapper.js b/packages/enzyme/src/ReactWrapper.js index e1f2acf4b..d609b7bfd 100644 --- a/packages/enzyme/src/ReactWrapper.js +++ b/packages/enzyme/src/ReactWrapper.js @@ -728,7 +728,10 @@ class ReactWrapper { * @returns {String} */ name() { - return this.single('name', n => displayNameOfNode(n)); + const adapter = getAdapter(this[OPTIONS]); + return this.single('name', n => ( + adapter.displayNameOfNode ? adapter.displayNameOfNode(n) : displayNameOfNode(n) + )); } /** diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index 11ae1e906..b0d9900b4 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -934,7 +934,10 @@ class ShallowWrapper { * @returns {String} */ name() { - return this.single('name', displayNameOfNode); + const adapter = getAdapter(this[OPTIONS]); + return this.single('name', n => ( + adapter.displayNameOfNode ? adapter.displayNameOfNode(n) : displayNameOfNode(n) + )); } /** From 40c2f5cce932173b944f1dc1a9f2b763d776d5c8 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 6/7] Add additional type checks to adapters --- packages/enzyme/src/Utils.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/enzyme/src/Utils.js b/packages/enzyme/src/Utils.js index 644bf0b1a..4f122e51b 100644 --- a/packages/enzyme/src/Utils.js +++ b/packages/enzyme/src/Utils.js @@ -43,10 +43,18 @@ export function typeOfNode(node) { export function nodeHasType(node, type) { if (!type || !node) return false; + + const adapter = getAdapter(); + if (adapter.displayNameOfNode) { + const displayName = adapter.displayNameOfNode(node); + if (displayName === type) return true; + } + if (!node.type) return false; if (typeof node.type === 'string') return node.type === type; - return (typeof node.type === 'function' ? - functionName(node.type) === type : node.type.name === type) || node.type.displayName === type; + return ( + typeof node.type === 'function' ? functionName(node.type) === type : node.type.name === type + ) || node.type.displayName === type; } function internalChildrenCompare(a, b, lenComp, isLoose) { @@ -215,6 +223,7 @@ export function AND(fns) { return x => fnsReversed.every(fn => fn(x)); } +// TODO: semver-major: remove export function displayNameOfNode(node) { if (!node) return null; From 848040691e2a0b0ffaa7f3cc553de50916da907a Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 2 Jul 2018 10:22:41 -0400 Subject: [PATCH 7/7] Add additional type checks to adapters --- packages/enzyme/src/selectors.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/enzyme/src/selectors.js b/packages/enzyme/src/selectors.js index 8cb98737f..48f379e82 100644 --- a/packages/enzyme/src/selectors.js +++ b/packages/enzyme/src/selectors.js @@ -13,7 +13,7 @@ import { childrenOfNode, hasClassName, } from './RSTTraversal'; -import { nodeHasType, propsOfNode } from './Utils'; +import { getAdapter, nodeHasType, propsOfNode } from './Utils'; // our CSS selector parser instance const parser = createParser(); @@ -239,8 +239,17 @@ function isComplexSelector(tokens) { * @param {Function|Object|String} selector */ export function buildPredicate(selector) { - // If the selector is a function, check if the node's constructor matches - if (typeof selector === 'function') { + // If the selector is a string, parse it as a simple CSS selector + if (typeof selector === 'string') { + const tokens = safelyGenerateTokens(selector); + if (isComplexSelector(tokens)) { + throw new TypeError('This method does not support complex CSS selectors'); + } + // Simple selectors only have a single selector token + return buildPredicateFromToken(tokens[0]); + } + // If the selector is an element type, check if the node's type matches + if (getAdapter().isValidElementType(selector)) { return node => node && node.type === selector; } // If the selector is an non-empty object, treat the keys/values as props @@ -254,15 +263,7 @@ export function buildPredicate(selector) { } throw new TypeError('Enzyme::Selector does not support an array, null, or empty object as a selector'); } - // If the selector is a string, parse it as a simple CSS selector - if (typeof selector === 'string') { - const tokens = safelyGenerateTokens(selector); - if (isComplexSelector(tokens)) { - throw new TypeError('This method does not support complex CSS selectors'); - } - // Simple selectors only have a single selector token - return buildPredicateFromToken(tokens[0]); - } + throw new TypeError('Enzyme::Selector expects a string, object, or Component Constructor'); }