Skip to content

Commit 3feb0ca

Browse files
committed
Merge pull request #110 from hartzis/master
[semver-minor] add support for object property selector
2 parents 46f4d36 + 443a73e commit 3feb0ca

File tree

8 files changed

+167
-5
lines changed

8 files changed

+167
-5
lines changed

docs/api/ReactWrapper/find.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ const wrapper = mount(<MyComponent />);
4747
expect(wrapper.find('Foo')).to.have.length(1);
4848
```
4949

50-
50+
Object Property Selector:
51+
```jsx
52+
const wrapper = mount(<MyComponent />);
53+
expect(wrapper.find({prop: 'value'})).to.have.length(1);
54+
```
5155

5256
#### Related Methods
5357

docs/api/ShallowWrapper/find.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ const wrapper = shallow(<MyComponent />);
4444
expect(wrapper.find('Foo')).to.have.length(1);
4545
```
4646

47+
Object Property Selector:
48+
```jsx
49+
const wrapper = shallow(<MyComponent />);
50+
expect(wrapper.find({prop: 'value'})).to.have.length(1);
51+
```
4752

4853

4954
#### Related Methods

docs/api/selector.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ one of the following three categories:
66

77
### 1. A Valid CSS Selector
88

9-
Enzyme supports a subset of valid CSS selectors to find nodes inside a render tree. Support is as
9+
Enzyme supports a subset of valid CSS selectors to find nodes inside a render tree. Support is as
1010
follows:
1111

1212
- class syntax (`.foo`, `.foo-bar`, etc.)
@@ -92,6 +92,25 @@ MyComponent.displayName = 'MyComponent';
9292
const myComponents = wrapper.find('MyComponent');
9393
```
9494

95-
NOTE: This will *only* work if the selector (and thus the component's `displayName`) is a string
95+
NOTE: This will *only* work if the selector (and thus the component's `displayName`) is a string
9696
starting with a capital letter. Strings starting with lower case letters will assume it is a CSS
9797
selector using the tag syntax.
98+
99+
100+
101+
### 4. Object Property Selector
102+
103+
Enzyme allows you to find components and nodes based on a subset of their properties:
104+
105+
106+
```jsx
107+
const wrapper = mount(
108+
<div>
109+
<span foo={3} bar={false} title="baz" />
110+
</div>
111+
)
112+
113+
wrapper.find({ foo: 3 })
114+
wrapper.find({ bar: false })
115+
wrapper.find({ title: 'baz'})
116+
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"license": "MIT",
4848
"dependencies": {
4949
"cheerio": "^0.19.0",
50+
"is-subset": "^0.1.1",
5051
"object.assign": "^4.0.3",
5152
"sinon": "^1.15.4",
5253
"underscore": "^1.8.3"

src/MountedTraversal.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isEmpty } from 'underscore';
2+
import isSubset from 'is-subset';
13
import {
24
coercePropValue,
35
nodeEqual,
@@ -177,6 +179,12 @@ export function parentsOfInst(inst, root) {
177179
return pathToNode(inst, root).reverse();
178180
}
179181

182+
export function instMatchesObjectProps(inst, props) {
183+
if (!isDOMComponent(inst)) return false;
184+
const node = getNode(inst);
185+
return isSubset(propsOfNode(node), props);
186+
}
187+
180188
export function buildInstPredicate(selector) {
181189
switch (typeof selector) {
182190
case 'function':
@@ -207,8 +215,16 @@ export function buildInstPredicate(selector) {
207215
}
208216
break;
209217

218+
case 'object':
219+
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) {
220+
return node => instMatchesObjectProps(node, selector);
221+
}
222+
throw new TypeError(
223+
'Enzyme::Selector does not support an array, null, or empty object as a selector'
224+
);
225+
210226
default:
211-
throw new TypeError('Expecting a string or Component Constructor');
227+
throw new TypeError(`Enzyme::Selector expects a string, object, or Component Constructor`);
212228
}
213229
}
214230

src/ShallowTraversal.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React from 'react';
2+
import { isEmpty } from 'underscore';
3+
import isSubset from 'is-subset';
24
import {
35
coercePropValue,
46
propsOfNode,
@@ -95,6 +97,10 @@ export function nodeHasType(node, type) {
9597
return node.type.name === type || node.type.displayName === type;
9698
}
9799

100+
export function nodeMatchesObjectProps(node, props) {
101+
return isSubset(propsOfNode(node), props);
102+
}
103+
98104
export function buildPredicate(selector) {
99105
switch (typeof selector) {
100106
case 'function':
@@ -127,9 +133,16 @@ export function buildPredicate(selector) {
127133
}
128134
break;
129135

136+
case 'object':
137+
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) {
138+
return node => nodeMatchesObjectProps(node, selector);
139+
}
140+
throw new TypeError(
141+
'Enzyme::Selector does not support an array, null, or empty object as a selector'
142+
);
130143

131144
default:
132-
throw new TypeError('Expecting a string or Component Constructor');
145+
throw new TypeError(`Enzyme::Selector expects a string, object, or Component Constructor`);
133146
}
134147
}
135148

src/__tests__/ReactWrapper-spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,58 @@ describeWithDOM('mount', () => {
261261
expect(() => wrapper.find('.foo .foo')).to.throw(Error);
262262
});
263263

264+
it('should support object property selectors', () => {
265+
const wrapper = mount(
266+
<div>
267+
<input data-test="ref" className="foo" type="text" />
268+
<input data-test="ref" type="text"/>
269+
<button data-test="ref" prop={undefined} />
270+
<span data-test="ref" prop={null} />
271+
<div data-test="ref" prop={123} />
272+
<input data-test="ref" prop={false} />
273+
<a data-test="ref" prop />
274+
</div>
275+
);
276+
expect(wrapper.find({ a: 1 })).to.have.length(0);
277+
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(7);
278+
expect(wrapper.find({ className: 'foo' })).to.have.length(1);
279+
expect(wrapper.find({ prop: undefined })).to.have.length(1);
280+
expect(wrapper.find({ prop: null })).to.have.length(1);
281+
expect(wrapper.find({ prop: 123 })).to.have.length(1);
282+
expect(wrapper.find({ prop: false })).to.have.length(1);
283+
expect(wrapper.find({ prop: true })).to.have.length(1);
284+
});
285+
286+
it('should support complex and nested object property selectors', () => {
287+
const testFunction = () => {};
288+
const wrapper = mount(
289+
<div>
290+
<span more={[{ id: 1 }]} data-test="ref" prop onChange={testFunction} />
291+
<a more={[{ id: 1 }]} data-test="ref" />
292+
<div more={{ item: { id: 1 } }} data-test="ref" />
293+
<input style={{ height: 20 }} data-test="ref" />
294+
</div>
295+
);
296+
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(4);
297+
expect(wrapper.find({ more: { a: 1 } })).to.have.length(0);
298+
expect(wrapper.find({ more: [{ id: 1 }] })).to.have.length(2);
299+
expect(wrapper.find({ more: { item: { id: 1 } } })).to.have.length(1);
300+
expect(wrapper.find({ style: { height: 20 } })).to.have.length(1);
301+
expect(wrapper
302+
.find({ more: [{ id: 1 }], 'data-test': 'ref', prop: true, onChange: testFunction })
303+
).to.have.length(1);
304+
});
305+
306+
it('should throw when given empty object, null, or an array', () => {
307+
const wrapper = mount(
308+
<div>
309+
<input className="foo" type="text" />
310+
</div>
311+
);
312+
expect(() => wrapper.find({})).to.throw(Error);
313+
expect(() => wrapper.find([])).to.throw(Error);
314+
expect(() => wrapper.find(null)).to.throw(Error);
315+
});
264316
});
265317

266318
describe('.findWhere(predicate)', () => {

src/__tests__/ShallowWrapper-spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,58 @@ describe('shallow', () => {
256256
expect(() => wrapper.find('.foo .foo')).to.throw(Error);
257257
});
258258

259+
it('should support object property selectors', () => {
260+
const wrapper = shallow(
261+
<div>
262+
<input data-test="ref" className="foo" type="text" />
263+
<input data-test="ref" type="text"/>
264+
<button data-test="ref" prop={undefined} />
265+
<span data-test="ref" prop={null} />
266+
<div data-test="ref" prop={123} />
267+
<input data-test="ref" prop={false} />
268+
<a data-test="ref" prop />
269+
</div>
270+
);
271+
expect(wrapper.find({ a: 1 })).to.have.length(0);
272+
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(7);
273+
expect(wrapper.find({ className: 'foo' })).to.have.length(1);
274+
expect(wrapper.find({ prop: undefined })).to.have.length(1);
275+
expect(wrapper.find({ prop: null })).to.have.length(1);
276+
expect(wrapper.find({ prop: 123 })).to.have.length(1);
277+
expect(wrapper.find({ prop: false })).to.have.length(1);
278+
expect(wrapper.find({ prop: true })).to.have.length(1);
279+
});
280+
281+
it('should support complex and nested object property selectors', () => {
282+
const testFunction = () => {};
283+
const wrapper = shallow(
284+
<div>
285+
<span more={[{ id: 1 }]} data-test="ref" prop onChange={testFunction} />
286+
<a more={[{ id: 1 }]} data-test="ref" />
287+
<div more={{ item: { id: 1 } }} data-test="ref" />
288+
<input style={{ height: 20 }} data-test="ref" />
289+
</div>
290+
);
291+
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(4);
292+
expect(wrapper.find({ more: { a: 1 } })).to.have.length(0);
293+
expect(wrapper.find({ more: [{ id: 1 }] })).to.have.length(2);
294+
expect(wrapper.find({ more: { item: { id: 1 } } })).to.have.length(1);
295+
expect(wrapper.find({ style: { height: 20 } })).to.have.length(1);
296+
expect(wrapper
297+
.find({ more: [{ id: 1 }], 'data-test': 'ref', prop: true, onChange: testFunction })
298+
).to.have.length(1);
299+
});
300+
301+
it('should throw when given empty object, null, or an array', () => {
302+
const wrapper = shallow(
303+
<div>
304+
<input className="foo" type="text" />
305+
</div>
306+
);
307+
expect(() => wrapper.find({})).to.throw(Error);
308+
expect(() => wrapper.find([])).to.throw(Error);
309+
expect(() => wrapper.find(null)).to.throw(Error);
310+
});
259311
});
260312

261313
describe('.findWhere(predicate)', () => {

0 commit comments

Comments
 (0)