diff --git a/docs/api/ReactWrapper/find.md b/docs/api/ReactWrapper/find.md index 223bfa2e7..0417b5e7a 100644 --- a/docs/api/ReactWrapper/find.md +++ b/docs/api/ReactWrapper/find.md @@ -47,7 +47,11 @@ const wrapper = mount(); expect(wrapper.find('Foo')).to.have.length(1); ``` - +Object Property Selector: +```jsx +const wrapper = mount(); +expect(wrapper.find({prop: 'value'})).to.have.length(1); +``` #### Related Methods diff --git a/docs/api/ShallowWrapper/find.md b/docs/api/ShallowWrapper/find.md index 635602de1..85dd30579 100644 --- a/docs/api/ShallowWrapper/find.md +++ b/docs/api/ShallowWrapper/find.md @@ -44,6 +44,11 @@ const wrapper = shallow(); expect(wrapper.find('Foo')).to.have.length(1); ``` +Object Property Selector: +```jsx +const wrapper = shallow(); +expect(wrapper.find({prop: 'value'})).to.have.length(1); +``` #### Related Methods diff --git a/docs/api/selector.md b/docs/api/selector.md index 1eb357396..5d11ae734 100644 --- a/docs/api/selector.md +++ b/docs/api/selector.md @@ -6,7 +6,7 @@ one of the following three categories: ### 1. A Valid CSS Selector -Enzyme supports a subset of valid CSS selectors to find nodes inside a render tree. Support is as +Enzyme supports a subset of valid CSS selectors to find nodes inside a render tree. Support is as follows: - class syntax (`.foo`, `.foo-bar`, etc.) @@ -92,6 +92,25 @@ MyComponent.displayName = 'MyComponent'; const myComponents = wrapper.find('MyComponent'); ``` -NOTE: This will *only* work if the selector (and thus the component's `displayName`) is a string +NOTE: This will *only* work if the selector (and thus the component's `displayName`) is a string starting with a capital letter. Strings starting with lower case letters will assume it is a CSS selector using the tag syntax. + + + +### 4. Object Property Selector + +Enzyme allows you to find components and nodes based on a subset of their properties: + + +```jsx +const wrapper = mount( +
+ +
+) + +wrapper.find({ foo: 3 }) +wrapper.find({ bar: false }) +wrapper.find({ title: 'baz'}) +``` diff --git a/package.json b/package.json index bec2fffcb..e8d7a9990 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "license": "MIT", "dependencies": { "cheerio": "^0.19.0", + "is-subset": "^0.1.1", "object.assign": "^4.0.3", "sinon": "^1.15.4", "underscore": "^1.8.3" diff --git a/src/MountedTraversal.js b/src/MountedTraversal.js index 54bea0128..faff9433a 100644 --- a/src/MountedTraversal.js +++ b/src/MountedTraversal.js @@ -1,3 +1,5 @@ +import { isEmpty } from 'underscore'; +import isSubset from 'is-subset'; import { coercePropValue, nodeEqual, @@ -177,6 +179,12 @@ export function parentsOfInst(inst, root) { return pathToNode(inst, root).reverse(); } +export function instMatchesObjectProps(inst, props) { + if (!isDOMComponent(inst)) return false; + const node = getNode(inst); + return isSubset(propsOfNode(node), props); +} + export function buildInstPredicate(selector) { switch (typeof selector) { case 'function': @@ -207,8 +215,16 @@ export function buildInstPredicate(selector) { } break; + case 'object': + if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) { + return node => instMatchesObjectProps(node, selector); + } + throw new TypeError( + 'Enzyme::Selector does not support an array, null, or empty object as a selector' + ); + default: - throw new TypeError('Expecting a string or Component Constructor'); + throw new TypeError(`Enzyme::Selector expects a string, object, or Component Constructor`); } } diff --git a/src/ShallowTraversal.js b/src/ShallowTraversal.js index e634c2d2e..803e7668f 100644 --- a/src/ShallowTraversal.js +++ b/src/ShallowTraversal.js @@ -1,4 +1,6 @@ import React from 'react'; +import { isEmpty } from 'underscore'; +import isSubset from 'is-subset'; import { coercePropValue, propsOfNode, @@ -95,6 +97,10 @@ export function nodeHasType(node, type) { return node.type.name === type || node.type.displayName === type; } +export function nodeMatchesObjectProps(node, props) { + return isSubset(propsOfNode(node), props); +} + export function buildPredicate(selector) { switch (typeof selector) { case 'function': @@ -127,9 +133,16 @@ export function buildPredicate(selector) { } break; + case 'object': + if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) { + return node => nodeMatchesObjectProps(node, selector); + } + throw new TypeError( + 'Enzyme::Selector does not support an array, null, or empty object as a selector' + ); default: - throw new TypeError('Expecting a string or Component Constructor'); + throw new TypeError(`Enzyme::Selector expects a string, object, or Component Constructor`); } } diff --git a/src/__tests__/ReactWrapper-spec.js b/src/__tests__/ReactWrapper-spec.js index 39a216318..efb44d8df 100644 --- a/src/__tests__/ReactWrapper-spec.js +++ b/src/__tests__/ReactWrapper-spec.js @@ -261,6 +261,58 @@ describeWithDOM('mount', () => { expect(() => wrapper.find('.foo .foo')).to.throw(Error); }); + it('should support object property selectors', () => { + const wrapper = mount( +
+ + +