diff --git a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js index 9fa723aa3..0d7a7e2db 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, @@ -249,6 +250,10 @@ class ReactThirteenAdapter extends EnzymeAdapter { return React.createElement(node.type, propsWithKeysAndRef(node)); } + displayNameOfNode(node) { + return displayNameOfNode(node); + } + elementToNode(element) { return elementToTree(element); } @@ -261,6 +266,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 802f599bd..d87e25f19 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, @@ -229,10 +230,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 e539b3600..472932d36 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, @@ -262,10 +263,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 057f28938..db557717f 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, @@ -262,10 +263,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 55117fcfe..58d20ad76 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", "object.assign": "^4.1.0", "object.values": "^1.0.4", "prop-types": "^15.6.0", diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index 2883d98c4..6b9576517 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 @@ -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 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}`); @@ -337,10 +350,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'; + case Portal: return 'Portal'; + + 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'; + } + default: 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-utils/package.json b/packages/enzyme-adapter-utils/package.json index e200fee85..3860c2dc8 100644 --- a/packages/enzyme-adapter-utils/package.json +++ b/packages/enzyme-adapter-utils/package.json @@ -34,6 +34,7 @@ "author": "Leland Richardson ", "license": "MIT", "dependencies": { + "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 cbd1c8348..6f3f77a72 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'; diff --git a/packages/enzyme-test-suite/package.json b/packages/enzyme-test-suite/package.json index e05d1422c..1c20f200e 100644 --- a/packages/enzyme-test-suite/package.json +++ b/packages/enzyme-test-suite/package.json @@ -33,6 +33,7 @@ "enzyme": "^3.3.0", "enzyme-adapter-utils": "^1.3.0", "jsdom": "^6.5.1", + "mocha-wrap": "^2.1.2", "object.assign": "^4.1.0", "prop-types": "^15.6.0", "semver": "^5.5.0", diff --git a/packages/enzyme-test-suite/test/Adapter-spec.jsx b/packages/enzyme-test-suite/test/Adapter-spec.jsx index c022b4de2..dacd6f051 100644 --- a/packages/enzyme-test-suite/test/Adapter-spec.jsx +++ b/packages/enzyme-test-suite/test/Adapter-spec.jsx @@ -6,7 +6,16 @@ import { configure, shallow } from 'enzyme'; import './_helpers/setupAdapters'; import Adapter from './_helpers/adapter'; -import { renderToString } from './_helpers/react-compat'; +import { + renderToString, + createContext, + createPortal, + forwardRef, + Fragment, + StrictMode, + AsyncMode, + Profiler, +} from './_helpers/react-compat'; import { is } from './_helpers/version'; import { itIf, describeWithDOM } from './_helpers'; @@ -801,4 +810,100 @@ describe('Adapter', () => { }, })); }); + + describe('determines valid element types', () => { + itIf(is('> 0.13'), 'supports stateless function components', () => { + const SFC = () => null; + + expect(adapter.isValidElementType(SFC)).to.equal(true); + }); + + it('supports custom components', () => { + class Component extends React.Component { + render() { return null; } + } + + expect(adapter.isValidElementType(Component)).to.equal(true); + }); + + it('supports HTML elements', () => { + expect(adapter.isValidElementType('div')).to.equal(true); + }); + + itIf(is('>= 16'), 'supports Portals', () => { + expect(adapter.isValidElementType(createPortal(
, { nodeType: 1 }))).to.equal(false); + }); + + itIf(is('>= 16.3'), 'supports Context', () => { + const Context = createContext({ }); + expect(adapter.isValidElementType(Context.Consumer)).to.equal(true); + expect(adapter.isValidElementType(Context.Provider)).to.equal(true); + }); + + itIf(is('>= 16.3'), 'supports forward refs', () => { + expect(adapter.isValidElementType(forwardRef(() => null))).to.equal(true); + }); + }); + + describe('provides node displayNames', () => { + const getDisplayName = el => adapter.displayNameOfNode(adapter.elementToNode(el)); + + itIf(is('> 0.13'), 'supports stateless function components', () => { + const SFC = () => null; + + expect(getDisplayName()).to.equal('SFC'); + }); + + it('supports custom components', () => { + class Component extends React.Component { + render() { return null; } + } + class Something extends React.Component { + render() { return null; } + } + Something.displayName = 'MyComponent'; + + expect(getDisplayName()).to.equal('Component'); + expect(getDisplayName()).to.equal('MyComponent'); + }); + + it('supports HTML elements', () => { + expect(getDisplayName(
)).to.equal('div'); + }); + + itIf(is('>= 16.2'), 'supports Fragments', () => { + expect(getDisplayName()).to.equal('Fragment'); + }); + + itIf(is('>= 16'), 'supports Portals', () => { + expect(getDisplayName(createPortal(
, { nodeType: 1 }))).to.equal('Portal'); + }); + + itIf(is('>= 16.3'), 'supports Context', () => { + const Context = createContext({}); + expect(getDisplayName()).to.equal('ContextConsumer'); + expect(getDisplayName()).to.equal('ContextProvider'); + }); + + itIf(is('>= 16.3'), 'supports forward refs', () => { + const ForwaredRef = forwardRef(() => null); + // eslint-disable-next-line prefer-arrow-callback + const NamedForwardedRef = forwardRef(function Named() { return null; }); + + expect(getDisplayName()).to.equal('ForwardRef'); + expect(getDisplayName()).to.equal('ForwardRef(Named)'); + }); + + itIf(is('>= 16.3'), 'supports StrictMode', () => { + expect(getDisplayName()).to.equal('StrictMode'); + }); + + itIf(is('>= 16.3'), 'supports AsyncMode', () => { + expect(getDisplayName()).to.equal('AsyncMode'); + }); + + itIf(is('>= 16.4'), 'supports Profiler', () => { + expect(getDisplayName()).to.equal('Profiler'); + }); + }); }); diff --git a/packages/enzyme-test-suite/test/Debug-spec.jsx b/packages/enzyme-test-suite/test/Debug-spec.jsx index d5c4498f1..5de2c84a6 100644 --- a/packages/enzyme-test-suite/test/Debug-spec.jsx +++ b/packages/enzyme-test-suite/test/Debug-spec.jsx @@ -1,5 +1,8 @@ import { expect } from 'chai'; import React from 'react'; +import wrap from 'mocha-wrap'; +import sinon from 'sinon'; + import { mount, shallow } from 'enzyme'; import { get } from 'enzyme/build/configuration'; import { @@ -23,30 +26,56 @@ const { adapter } = get(); const debugElement = element => debugNode(adapter.elementToNode(element)); describe('debug', () => { - describe('typeName(node)', () => { - it('returns `.type` when not a function', () => { - const type = {}; - expect(typeName({ type })).to.equal(type); - }); - - describe('when `.type` is a function', () => { - it('returns the function’s name', () => { - function Foo() {} - expect(typeName({ type: Foo })).to.equal('Foo'); + wrap() + .withOverride(() => adapter, 'displayNameOfNode', () => undefined) + .describe('typeName(node)', () => { + it('returns `.type` when not a function', () => { + const type = {}; + expect(typeName({ type })).to.equal(type); }); - it('returns the function’s `.displayName` when present', () => { - function Foo() {} - Foo.displayName = 'Bar'; - expect(typeName({ type: Foo })).to.equal('Bar'); + describe('when `.type` is a function', () => { + it('returns the function’s name', () => { + function Foo() {} + expect(typeName({ type: Foo })).to.equal('Foo'); + }); + + it('returns the function’s `.displayName` when present', () => { + function Foo() {} + Foo.displayName = 'Bar'; + expect(typeName({ type: Foo })).to.equal('Bar'); + }); + + it('returns "Component" when the function is anonymous', () => { + const anon = Object(() => {}); + expect(typeName({ type: anon })).to.equal('Component'); + }); }); - it('returns "Component" when the function is anonymous', () => { - const anon = Object(() => {}); - expect(typeName({ type: anon })).to.equal('Component'); - }); + wrap() + .withOverride(() => adapter, 'displayNameOfNode', () => sinon.stub()) + .describe('when the adapter has a `displayNameOfNode` function', () => { + it('calls it, and returns its return value', () => { + const stub = adapter.displayNameOfNode; + const sentinel = {}; + stub.returns(sentinel); + + const node = {}; + expect(typeName(node)).to.equal(sentinel); + + expect(stub).to.have.property('callCount', 1); + const { args } = stub.firstCall; + expect(args).to.eql([node]); + }); + + it('returns "Component" when `adapter.displayNameOfNode` returns something falsy', () => { + const stub = adapter.displayNameOfNode; + stub.returns(''); + + expect(typeName()).to.equal('Component'); + }); + }); }); - }); describe('spaces(n)', () => { it('should return n spaces', () => { diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index 3998d91f9..d902bd494 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -3,12 +3,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import { expect } from 'chai'; import sinon from 'sinon'; +import wrap from 'mocha-wrap'; import { mount, render, ReactWrapper, } from 'enzyme'; -import { ITERATOR_SYMBOL, sym } from 'enzyme/build/Utils'; +import { + ITERATOR_SYMBOL, + sym, + getAdapter, +} from 'enzyme/build/Utils'; import './_helpers/setupAdapters'; import { createClass, createContext, createPortal } from './_helpers/react-compat'; @@ -431,508 +436,528 @@ describeWithDOM('mount', () => { }); }); - describe('.find(selector)', () => { - it('should find an element based on a class name', () => { - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('.foo').type()).to.equal('input'); - }); - - it('should find an SVG element based on a class name', () => { - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('.foo').type()).to.equal('svg'); - }); - - it('should find an element based on a tag name', () => { - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('input').props().className).to.equal('foo'); - }); - - it('should find an element based on a tag name and class name', () => { - const wrapper = mount(( -
- -
-
- )); - expect(wrapper.find('input.foo')).to.have.lengthOf(1); - }); - - it('should work on non-single nodes', () => { - const wrapper = mount(( -
-
-
Text
-
Text
-
Text
-
-
-
Text
-
Text
-
Text
+ wrap() + .withOverride(() => getAdapter(), 'isValidElementType', () => undefined) + .describe('.find(selector)', () => { + it('should find an element based on a class name', () => { + const wrapper = mount(( +
+
-
- )); - expect(wrapper.find('.a')).to.have.lengthOf(1); - expect(wrapper.find('.b')).to.have.lengthOf(2); - expect(wrapper.find('.b').find('.c')).to.have.lengthOf(6); - }); + )); + expect(wrapper.find('.foo').type()).to.equal('input'); + }); + it('should find an SVG element based on a class name', () => { + const wrapper = mount(( +
+ +
+ )); + expect(wrapper.find('.foo').type()).to.equal('svg'); + }); - it('should find an element based on a tag name and id', () => { - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('input#foo')).to.have.lengthOf(1); - }); + it('should find an element based on a tag name', () => { + const wrapper = mount(( +
+ +
+ )); + expect(wrapper.find('input').props().className).to.equal('foo'); + }); - it('should find an element based on a tag name, id, and class name', () => { - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('input#foo.bar')).to.have.lengthOf(1); - }); + it('should find an element based on a tag name and class name', () => { + const wrapper = mount(( +
+ +
+
+ )); + expect(wrapper.find('input.foo')).to.have.lengthOf(1); + }); - it('should find a component based on a constructor', () => { - class Foo extends React.Component { - render() { return
; } - } - const wrapper = mount(( -
- -
- )); - expect(wrapper.find(Foo).type()).to.equal(Foo); - }); + it('should work on non-single nodes', () => { + const wrapper = mount(( +
+
+
Text
+
Text
+
Text
+
+
+
Text
+
Text
+
Text
+
+
+ )); + expect(wrapper.find('.a')).to.have.lengthOf(1); + expect(wrapper.find('.b')).to.have.lengthOf(2); + expect(wrapper.find('.b').find('.c')).to.have.lengthOf(6); + }); - it('should find a component based on a component displayName', () => { - class Foo extends React.Component { - render() { return
; } - } - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('Foo').type()).to.equal(Foo); - }); - describeIf(is('> 0.13'), 'stateless components', () => { - it('should find a stateless component based on a component displayName', () => { - const Foo = () =>
; + it('should find an element based on a tag name and id', () => { const wrapper = mount((
- +
)); - expect(wrapper.find('Foo').type()).to.equal(Foo); + expect(wrapper.find('input#foo')).to.have.lengthOf(1); }); - it('should find a stateless component based on a component displayName if rendered by function', () => { - const Foo = () =>
; - const renderStatelessComponent = () => ; + it('should find an element based on a tag name, id, and class name', () => { const wrapper = mount((
- {renderStatelessComponent()} +
)); - expect(wrapper.find('Foo').type()).to.equal(Foo); + expect(wrapper.find('input#foo.bar')).to.have.lengthOf(1); }); - }); - - it('should find component based on a react prop', () => { - const wrapper = mount(( -
- -
-
- )); - - expect(wrapper.find('[htmlFor="foo"]')).to.have.lengthOf(1); - expect(wrapper.find('[htmlFor]')).to.have.lengthOf(2); - }); - - it('should error sensibly if any of the search props are undefined', () => { - const wrapper = mount(( -
- -
- )); - expect(() => wrapper.find({ type: undefined })).to.throw( - TypeError, - 'Enzyme::Props can’t have `undefined` values. Try using ‘findWhere()’ instead.', - ); - }); + it('should find a component based on a constructor', () => { + class Foo extends React.Component { + render() { return
; } + } + const wrapper = mount(( +
+ +
+ )); + expect(wrapper.find(Foo).type()).to.equal(Foo); + }); - it('should compound tag and prop selector', () => { - const wrapper = mount(( -
- -
- )); + wrap() + .withOverride(() => getAdapter(), 'isValidElementType', () => () => false) + .it('throws when an adapter’s `isValidElementType` lies', () => { + class Foo extends React.Component { + render() { return
; } + } + const wrapper = mount(( +
+ +
+ )); - expect(wrapper.find('span[htmlFor="foo"]')).to.have.lengthOf(1); - expect(wrapper.find('span[htmlFor]')).to.have.lengthOf(1); - }); + expect(() => wrapper.find(Foo)).to.throw( + TypeError, + 'Enzyme::Selector expects a string, object, or valid element type (Component Constructor)', + ); + }); - it('works with an adjacent sibling selector', () => { - const a = 'some'; - const b = 'text'; - const wrapper = mount(( -
-
- {a} - {b} -
-
- {a} - {b} + it('should find a component based on a component displayName', () => { + class Foo extends React.Component { + render() { return
; } + } + const wrapper = mount(( +
+
-
- )); - expect(wrapper.find('.row')).to.have.lengthOf(2); - expect(wrapper.find('.row + .row')).to.have.lengthOf(1); - }); - - it('should throw for non-numeric attribute values without quotes', () => { - const wrapper = mount(( -
- - - -
- )); - expect(() => wrapper.find('[type=text]')).to.throw( - Error, - 'Failed to parse selector: [type=text]', - ); - expect(() => wrapper.find('[type=hidden]')).to.throw( - Error, - 'Failed to parse selector: [type=hidden]', - ); - expect(() => wrapper.find('[type="text"]')).to.not.throw( - Error, - 'Failed to parse selector: [type="text"]', - ); - }); + )); + expect(wrapper.find('Foo').type()).to.equal(Foo); + }); - it('should support data prop selectors', () => { - const wrapper = mount(( -
- - - - -
- )); + describeIf(is('> 0.13'), 'stateless components', () => { + it('should find a stateless component based on a component displayName', () => { + const Foo = () =>
; + const wrapper = mount(( +
+ +
+ )); + expect(wrapper.find('Foo').type()).to.equal(Foo); + }); - expect(wrapper.find('[data-foo="bar"]')).to.have.lengthOf(1); - expect(wrapper.find('[data-foo]')).to.have.lengthOf(1); + it('should find a stateless component based on a component displayName if rendered by function', () => { + const Foo = () =>
; + const renderStatelessComponent = () => ; + const wrapper = mount(( +
+ {renderStatelessComponent()} +
+ )); + expect(wrapper.find('Foo').type()).to.equal(Foo); + }); + }); - expect(wrapper.find('[data-foo-123]')).to.have.lengthOf(1); - expect(wrapper.find('[data-foo-123="bar2"]')).to.have.lengthOf(1); + it('should find component based on a react prop', () => { + const wrapper = mount(( +
+ +
+
+ )); - expect(wrapper.find('[data-123-foo]')).to.have.lengthOf(1); - expect(wrapper.find('[data-123-foo="bar3"]')).to.have.lengthOf(1); + expect(wrapper.find('[htmlFor="foo"]')).to.have.lengthOf(1); + expect(wrapper.find('[htmlFor]')).to.have.lengthOf(2); + }); - expect(wrapper.find('[data-foo_bar]')).to.have.lengthOf(1); - expect(wrapper.find('[data-foo_bar="bar4"]')).to.have.lengthOf(1); - }); + it('should error sensibly if any of the search props are undefined', () => { + const wrapper = mount(( +
+ +
+ )); - it('should find components with multiple matching props', () => { - const onChange = () => ({}); - const wrapper = mount(( -
- -
- )); + expect(() => wrapper.find({ type: undefined })).to.throw( + TypeError, + 'Enzyme::Props can’t have `undefined` values. Try using ‘findWhere()’ instead.', + ); + }); - expect(wrapper.find('span[htmlFor="foo"][onChange]')).to.have.lengthOf(1); - expect(wrapper.find('span[htmlFor="foo"][preserveAspectRatio="xMaxYMax"]')).to.have.lengthOf(1); - }); + it('should compound tag and prop selector', () => { + const wrapper = mount(( +
+ +
+ )); - it('should not find property when undefined', () => { - const wrapper = mount(( -
- -
- )); + expect(wrapper.find('span[htmlFor="foo"]')).to.have.lengthOf(1); + expect(wrapper.find('span[htmlFor]')).to.have.lengthOf(1); + }); - expect(wrapper.find('[data-foo]')).to.have.lengthOf(0); - }); + it('works with an adjacent sibling selector', () => { + const a = 'some'; + const b = 'text'; + const wrapper = mount(( +
+
+ {a} + {b} +
+
+ {a} + {b} +
+
+ )); + expect(wrapper.find('.row')).to.have.lengthOf(2); + expect(wrapper.find('.row + .row')).to.have.lengthOf(1); + }); - it('should support boolean and numeric values for matching props', () => { - const wrapper = mount(( - - )); + it('should throw for non-numeric attribute values without quotes', () => { + const wrapper = mount(( +
+ + + +
+ )); + expect(() => wrapper.find('[type=text]')).to.throw( + Error, + 'Failed to parse selector: [type=text]', + ); + expect(() => wrapper.find('[type=hidden]')).to.throw( + Error, + 'Failed to parse selector: [type=hidden]', + ); + expect(() => wrapper.find('[type="text"]')).to.not.throw( + Error, + 'Failed to parse selector: [type="text"]', + ); + }); - expect(wrapper.find('span[value=1]')).to.have.lengthOf(1); - expect(wrapper.find('span[value=2]')).to.have.lengthOf(0); - expect(wrapper.find('a[value=false]')).to.have.lengthOf(1); - expect(wrapper.find('a[value=true]')).to.have.lengthOf(0); - }); + it('should support data prop selectors', () => { + const wrapper = mount(( +
+ + + + +
+ )); - it('should not find key or ref via property selector', () => { - class Foo extends React.Component { - render() { - const arrayOfComponents = [
,
]; + expect(wrapper.find('[data-foo="bar"]')).to.have.lengthOf(1); + expect(wrapper.find('[data-foo]')).to.have.lengthOf(1); - return ( -
-
- {arrayOfComponents} -
- ); - } - } + expect(wrapper.find('[data-foo-123]')).to.have.lengthOf(1); + expect(wrapper.find('[data-foo-123="bar2"]')).to.have.lengthOf(1); - const wrapper = mount(); + expect(wrapper.find('[data-123-foo]')).to.have.lengthOf(1); + expect(wrapper.find('[data-123-foo="bar3"]')).to.have.lengthOf(1); - expect(wrapper.find('div[ref="foo"]')).to.have.lengthOf(0); - expect(wrapper.find('div[key="1"]')).to.have.lengthOf(0); - expect(wrapper.find('[ref]')).to.have.lengthOf(0); - expect(wrapper.find('[key]')).to.have.lengthOf(0); - }); + expect(wrapper.find('[data-foo_bar]')).to.have.lengthOf(1); + expect(wrapper.find('[data-foo_bar="bar4"]')).to.have.lengthOf(1); + }); - it('should find multiple elements based on a class name', () => { - const wrapper = mount(( -
- -
- )); - expect(wrapper.find('.foo')).to.have.lengthOf(2); - }); + it('should find components with multiple matching props', () => { + const onChange = () => ({}); + const wrapper = mount(( +
+ +
+ )); - it('should find multiple elements based on a tag name', () => { - const wrapper = mount(( -
- - -
- )); - expect(wrapper.find('input')).to.have.lengthOf(2); - expect(wrapper.find('button')).to.have.lengthOf(1); - }); + expect(wrapper.find('span[htmlFor="foo"][onChange]')).to.have.lengthOf(1); + expect(wrapper.find('span[htmlFor="foo"][preserveAspectRatio="xMaxYMax"]')).to.have.lengthOf(1); + }); - it('should find multiple elements based on a constructor', () => { - const wrapper = mount(( -
- - -
- )); - expect(wrapper.find('input')).to.have.lengthOf(2); - expect(wrapper.find('button')).to.have.lengthOf(1); - }); + it('should not find property when undefined', () => { + const wrapper = mount(( +
+ +
+ )); - it('should support object property selectors', () => { - const wrapper = mount(( -
- - -
)); - expect(wrapper.find(Foo).type()).to.equal(Foo); + expect(wrapper.find('.foo')).to.have.lengthOf(2); }); - it('should find a component based on a component displayName', () => { - const Foo = () =>
; + it('should find multiple elements based on a tag name', () => { const wrapper = mount((
- + + +
)); - expect(wrapper.find('Foo').type()).to.equal(Foo); + expect(wrapper.find('input')).to.have.lengthOf(2); + expect(wrapper.find('button')).to.have.lengthOf(1); }); - it('should not find key via property selector', () => { - const Foo = () => { - const arrayOfComponents = [
,
]; - return ( -
- {arrayOfComponents} -
- ); - }; + it('should find multiple elements based on a constructor', () => { + const wrapper = mount(( +
+ + +
+ )); + expect(wrapper.find('input')).to.have.lengthOf(2); + expect(wrapper.find('button')).to.have.lengthOf(1); + }); - const wrapper = mount(); + it('should support object property selectors', () => { + const wrapper = mount(( +
+ + + -