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(
+