Skip to content

Commit

Permalink
add support for object property selector
Browse files Browse the repository at this point in the history
  • Loading branch information
hartzis committed Jan 12, 2016
1 parent 53f43ef commit eb9dd29
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 5 deletions.
6 changes: 5 additions & 1 deletion docs/api/ReactWrapper/find.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ const wrapper = mount(<MyComponent />);
expect(wrapper.find('Foo')).to.have.length(1);
```


Object Property Selector:
```jsx
const wrapper = mount(<MyComponent />);
expect(wrapper.find({prop: 'value'})).to.have.length(1);
```

#### Related Methods

Expand Down
5 changes: 5 additions & 0 deletions docs/api/ShallowWrapper/find.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ const wrapper = shallow(<MyComponent />);
expect(wrapper.find('Foo')).to.have.length(1);
```

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


#### Related Methods
Expand Down
23 changes: 21 additions & 2 deletions docs/api/selector.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down Expand Up @@ -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(
<div>
<span foo={3} bar={false} title="baz" />
</div>
)

wrapper.find({ foo: 3 })
wrapper.find({ bar: false })
wrapper.find({ title: 'baz'})
```
15 changes: 14 additions & 1 deletion src/MountedTraversal.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isMatch, isEmpty } from 'underscore';
import {
coercePropValue,
nodeEqual,
Expand Down Expand Up @@ -177,6 +178,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 !isEmpty(props) && isMatch(propsOfNode(node), props);
}

export function buildInstPredicate(selector) {
switch (typeof selector) {
case 'function':
Expand Down Expand Up @@ -207,8 +214,14 @@ export function buildInstPredicate(selector) {
}
break;

case 'object':
if (!Array.isArray(selector) && selector !== null) {
return node => instMatchesObjectProps(node, selector);
}
throw new TypeError('Enzyme::Selector does not support an array or null 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`);
}
}

Expand Down
12 changes: 11 additions & 1 deletion src/ShallowTraversal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { isMatch, isEmpty } from 'underscore';
import {
coercePropValue,
propsOfNode,
Expand Down Expand Up @@ -95,6 +96,10 @@ export function nodeHasType(node, type) {
return node.type.name === type || node.type.displayName === type;
}

export function nodeMatchesObjectProps(node, props) {
return !isEmpty(props) && isMatch(propsOfNode(node), props);
}

export function buildPredicate(selector) {
switch (typeof selector) {
case 'function':
Expand Down Expand Up @@ -127,9 +132,14 @@ export function buildPredicate(selector) {
}
break;

case 'object':
if (!Array.isArray(selector) && selector !== null) {
return node => nodeMatchesObjectProps(node, selector);
}
throw new TypeError('Enzyme::Selector does not support an array or null 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`);
}
}

Expand Down
55 changes: 55 additions & 0 deletions src/__tests__/ReactWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,61 @@ describeWithDOM('mount', () => {
expect(() => wrapper.find('.foo .foo')).to.throw(Error);
});

it('should support object property selectors', () => {
const wrapper = mount(
<div>
<input data-test="ref" className="foo" type="text" />
<input data-test="ref" type="text"/>
<button data-test="ref" prop={undefined} />
<span data-test="ref" prop={null} />
<div data-test="ref" prop={123} />
<input data-test="ref" prop={false} />
<a data-test="ref" prop />
</div>
);
expect(wrapper.find({ a: 1 })).to.have.length(0);
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(7);
expect(wrapper.find({ className: 'foo' })).to.have.length(1);
expect(wrapper.find({ prop: undefined })).to.have.length(1);
expect(wrapper.find({ prop: null })).to.have.length(1);
expect(wrapper.find({ prop: 123 })).to.have.length(1);
expect(wrapper.find({ prop: false })).to.have.length(1);
expect(wrapper.find({ prop: true })).to.have.length(1);
});

it('should support complex object property selectors', () => {
const testFunction = () => {};
const wrapper = mount(
<div>
<span more={123} data-test="ref" prop onChange={testFunction}/>
<a more={123} data-test="ref" />
</div>
);
expect(wrapper.find({ more: 123, 'data-test': 'ref' })).to.have.length(2);
expect(wrapper
.find({ more: 123, 'data-test': 'ref', prop: true, onChange: testFunction })
).to.have.length(1);
});

it('should not match when given empty object property selector', () => {
const wrapper = mount(
<div>
<input className="foo" type="text" />
</div>
);
expect(wrapper.find({})).to.have.length(0);
});

it('should throw when given null or an array', () => {
const wrapper = mount(
<div>
<input className="foo" type="text" />
</div>
);
expect(() => wrapper.find([])).to.throw(Error);
expect(() => wrapper.find(null)).to.throw(Error);
});

});

describe('.findWhere(predicate)', () => {
Expand Down
55 changes: 55 additions & 0 deletions src/__tests__/ShallowWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,61 @@ describe('shallow', () => {
expect(() => wrapper.find('.foo .foo')).to.throw(Error);
});

it('should support object property selectors', () => {
const wrapper = shallow(
<div>
<input data-test="ref" className="foo" type="text" />
<input data-test="ref" type="text"/>
<button data-test="ref" prop={undefined} />
<span data-test="ref" prop={null} />
<div data-test="ref" prop={123} />
<input data-test="ref" prop={false} />
<a data-test="ref" prop />
</div>
);
expect(wrapper.find({ a: 1 })).to.have.length(0);
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(7);
expect(wrapper.find({ className: 'foo' })).to.have.length(1);
expect(wrapper.find({ prop: undefined })).to.have.length(1);
expect(wrapper.find({ prop: null })).to.have.length(1);
expect(wrapper.find({ prop: 123 })).to.have.length(1);
expect(wrapper.find({ prop: false })).to.have.length(1);
expect(wrapper.find({ prop: true })).to.have.length(1);
});

it('should support complex object property selectors', () => {
const testFunction = () => {};
const wrapper = shallow(
<div>
<span more={123} data-test="ref" prop onChange={testFunction}/>
<a more={123} data-test="ref" />
</div>
);
expect(wrapper.find({ more: 123, 'data-test': 'ref' })).to.have.length(2);
expect(wrapper
.find({ more: 123, 'data-test': 'ref', prop: true, onChange: testFunction })
).to.have.length(1);
});

it('should not match when given empty object property selector', () => {
const wrapper = shallow(
<div>
<input className="foo" type="text" />
</div>
);
expect(wrapper.find({})).to.have.length(0);
});

it('should throw when given null or an array', () => {
const wrapper = shallow(
<div>
<input className="foo" type="text" />
</div>
);
expect(() => wrapper.find([])).to.throw(Error);
expect(() => wrapper.find(null)).to.throw(Error);
});

});

describe('.findWhere(predicate)', () => {
Expand Down

0 comments on commit eb9dd29

Please sign in to comment.