Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for object property selector #110

Merged
merged 1 commit into from
Jan 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'})
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 17 additions & 1 deletion src/MountedTraversal.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isEmpty } from 'underscore';
import isSubset from 'is-subset';
import {
coercePropValue,
nodeEqual,
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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`);
}
}

Expand Down
15 changes: 14 additions & 1 deletion src/ShallowTraversal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import { isEmpty } from 'underscore';
import isSubset from 'is-subset';
import {
coercePropValue,
propsOfNode,
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it expect an array also, per the above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array gets caught by the typeof is 'object', then errors out there

i can also update the verbiage on the object error if you would like

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes i misread the wording. it doesn't support null, or an array, or an empty object - i read "empty" as applying to all three. perhaps reverse the list on line 141?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch, updated

}
}

Expand Down
52 changes: 52 additions & 0 deletions src/__tests__/ReactWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,58 @@ 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 and nested object property selectors', () => {
const testFunction = () => {};
const wrapper = mount(
<div>
<span more={[{ id: 1 }]} data-test="ref" prop onChange={testFunction} />
<a more={[{ id: 1 }]} data-test="ref" />
<div more={{ item: { id: 1 } }} data-test="ref" />
<input style={{ height: 20 }} data-test="ref" />
</div>
);
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(4);
expect(wrapper.find({ more: { a: 1 } })).to.have.length(0);
expect(wrapper.find({ more: [{ id: 1 }] })).to.have.length(2);
expect(wrapper.find({ more: { item: { id: 1 } } })).to.have.length(1);
expect(wrapper.find({ style: { height: 20 } })).to.have.length(1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lelandrichardson This works now with is-subset, what do you think?

expect(wrapper
.find({ more: [{ id: 1 }], 'data-test': 'ref', prop: true, onChange: testFunction })
).to.have.length(1);
});

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

describe('.findWhere(predicate)', () => {
Expand Down
52 changes: 52 additions & 0 deletions src/__tests__/ShallowWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,58 @@ 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 and nested object property selectors', () => {
const testFunction = () => {};
const wrapper = shallow(
<div>
<span more={[{ id: 1 }]} data-test="ref" prop onChange={testFunction} />
<a more={[{ id: 1 }]} data-test="ref" />
<div more={{ item: { id: 1 } }} data-test="ref" />
<input style={{ height: 20 }} data-test="ref" />
</div>
);
expect(wrapper.find({ 'data-test': 'ref' })).to.have.length(4);
expect(wrapper.find({ more: { a: 1 } })).to.have.length(0);
expect(wrapper.find({ more: [{ id: 1 }] })).to.have.length(2);
expect(wrapper.find({ more: { item: { id: 1 } } })).to.have.length(1);
expect(wrapper.find({ style: { height: 20 } })).to.have.length(1);
expect(wrapper
.find({ more: [{ id: 1 }], 'data-test': 'ref', prop: true, onChange: testFunction })
).to.have.length(1);
});

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

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