Skip to content

Commit

Permalink
Allow .contains() to accept array of nodes.
Browse files Browse the repository at this point in the history
  • Loading branch information
lelandrichardson committed Feb 1, 2016
1 parent bd375b7 commit 3041ec3
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 21 deletions.
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* [findWhere(predicate)](/docs/api/ShallowWrapper/findWhere.md)
* [filter(selector)](/docs/api/ShallowWrapper/filter.md)
* [filterWhere(predicate)](/docs/api/ShallowWrapper/filterWhere.md)
* [contains(node)](/docs/api/ShallowWrapper/contains.md)
* [contains(nodeOrNodes)](/docs/api/ShallowWrapper/contains.md)
* [equals(node)](/docs/api/ShallowWrapper/equals.md)
* [hasClass(className)](/docs/api/ShallowWrapper/hasClass.md)
* [is(selector)](/docs/api/ShallowWrapper/is.md)
Expand Down Expand Up @@ -52,7 +52,7 @@
* [findWhere(predicate)](/docs/api/ReactWrapper/findWhere.md)
* [filter(selector)](/docs/api/ReactWrapper/filter.md)
* [filterWhere(predicate)](/docs/api/ReactWrapper/filterWhere.md)
* [contains(node)](/docs/api/ReactWrapper/contains.md)
* [contains(nodeOrNodes)](/docs/api/ReactWrapper/contains.md)
* [hasClass(className)](/docs/api/ReactWrapper/hasClass.md)
* [is(selector)](/docs/api/ReactWrapper/is.md)
* [not(selector)](/docs/api/ReactWrapper/not.md)
Expand Down
23 changes: 21 additions & 2 deletions docs/api/ReactWrapper/contains.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# `.contains(node)`
# `.contains(nodeOrNodes) => Boolean`

Returns whether or not the current wrapper has a node anywhere in it's render tree that looks like
the one passed in.


#### Arguments

1. `node` (`ReactElement`): The node whose presence you are detecting in the current instance's
1. `nodeOrNodes` (`ReactElement|Array<ReactElement>`): The node or array of nodes whose presence you are detecting in the current instance's
render tree.


Expand All @@ -26,6 +26,25 @@ const wrapper = mount(<MyComponent />);
expect(wrapper.contains(<div className="foo bar" />)).to.equal(true);
```

```jsx
const wrapper = mount(
<div>
<span>Hello</span>
<div>Goodbye</div>
<span>Again</span>
</div>
);
const passes = [
<span>Hello</span>,
<div>Goodbye</div>,
];

expect(wrapper.contains([
<span>Hello</span>,
<div>Goodbye</div>,
])).to.equal(true);
```


#### Common Gotchas

Expand Down
23 changes: 21 additions & 2 deletions docs/api/ShallowWrapper/contains.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# `.contains(node) => Boolean`
# `.contains(nodeOrNodes) => Boolean`

Returns whether or not the current wrapper has a node anywhere in it's render tree that looks like
the one passed in.


#### Arguments

1. `node` (`ReactElement`): The node whose presence you are detecting in the current instance's
1. `nodeOrNodes` (`ReactElement|Array<ReactElement>`): The node or array of nodes whose presence you are detecting in the current instance's
render tree.


Expand All @@ -26,6 +26,25 @@ const wrapper = shallow(<MyComponent />);
expect(wrapper.contains(<div className="foo bar" />)).to.equal(true);
```

```jsx
const wrapper = shallow(
<div>
<span>Hello</span>
<div>Goodbye</div>
<span>Again</span>
</div>
);
const passes = [
<span>Hello</span>,
<div>Goodbye</div>,
];

expect(wrapper.contains([
<span>Hello</span>,
<div>Goodbye</div>,
])).to.equal(true);
```


#### Common Gotchas

Expand Down
4 changes: 2 additions & 2 deletions docs/api/mount.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ Remove nodes in the current wrapper that do not match the provided selector.
#### [`.filterWhere(predicate) => ReactWrapper`](ReactWrapper/filterWhere.md)
Remove nodes in the current wrapper that do not return true for the provided predicate function.

#### [`.contains(node) => Boolean`](ReactWrapper/contains.md)
Returns whether or not a given node is somewhere in the render tree.
#### [`.contains(nodeOrNodes) => Boolean`](ReactWrapper/contains.md)
Returns whether or not a given node or array of nodes is somewhere in the render tree.

#### [`.hasClass(className) => Boolean`](ReactWrapper/hasClass.md)
Returns whether or not the current root node has the given class name or not.
Expand Down
4 changes: 2 additions & 2 deletions docs/api/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ Remove nodes in the current wrapper that do not match the provided selector.
#### [`.filterWhere(predicate) => ShallowWrapper`](ShallowWrapper/filterWhere.md)
Remove nodes in the current wrapper that do not return true for the provided predicate function.

#### [`.contains(node) => Boolean`](ShallowWrapper/contains.md)
Returns whether or not a given node is somewhere in the render tree.
#### [`.contains(nodeOrNodes) => Boolean`](ShallowWrapper/contains.md)
Returns whether or not a given node or array of nodes is somewhere in the render tree.

#### [`.equals(node) => Boolean`](ShallowWrapper/equals.md)
Returns whether or not the current render tree is equal to the given node.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"check": "npm run lint && npm run test:all",
"build": "babel src --out-dir build",
"test:watch": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/*.js --watch",
"test:only": "mocha --compilers js:babel-core/register --watch",
"test:describeWithDOMOnly": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/describeWithDOM/describeWithDOMOnly-spec.js",
"test:describeWithDOMSkip": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/describeWithDOM/describeWithDOMSkip-spec.js",
"test:all": "npm run react:13 && npm test && npm run test:describeWithDOMOnly && npm run test:describeWithDOMSkip && npm run react:14 && npm test && npm run test:describeWithDOMOnly && npm run test:describeWithDOMSkip",
Expand Down
15 changes: 11 additions & 4 deletions src/ReactWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
Simulate,
findDOMNode,
} from './react-compat';
import { mapNativeEventNames } from './Utils';
import {
mapNativeEventNames,
containsChildrenSubArray,
} from './Utils';

/**
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
Expand Down Expand Up @@ -207,11 +210,15 @@ export default class ReactWrapper {
* expect(wrapper.contains(<div className="foo bar" />)).to.equal(true);
* ```
*
* @param {ReactElement} node
* @param {ReactElement|Array<ReactElement>} nodeOrNodes
* @returns {Boolean}
*/
contains(node) {
return findWhereUnwrapped(this, other => instEqual(node, other)).length > 0;
contains(nodeOrNodes) {
const predicate = Array.isArray(nodeOrNodes)
? other => containsChildrenSubArray(instEqual, other, nodeOrNodes)
: other => instEqual(nodeOrNodes, other);

return findWhereUnwrapped(this, predicate).length > 0;
}

/**
Expand Down
11 changes: 8 additions & 3 deletions src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { flatten, unique, compact } from 'underscore';
import cheerio from 'cheerio';
import {
nodeEqual,
containsChildrenSubArray,
propFromEvent,
withSetStateAllowed,
propsOfNode,
Expand Down Expand Up @@ -198,11 +199,15 @@ export default class ShallowWrapper {
* expect(wrapper.contains(<div className="foo bar" />)).to.equal(true);
* ```
*
* @param {ReactElement} node
* @param {ReactElement|Array<ReactElement>} nodeOrNodes
* @returns {Boolean}
*/
contains(node) {
return findWhereUnwrapped(this, other => nodeEqual(node, other)).length > 0;
contains(nodeOrNodes) {
const predicate = Array.isArray(nodeOrNodes)
? other => containsChildrenSubArray(nodeEqual, other, nodeOrNodes)
: other => nodeEqual(nodeOrNodes, other);

return findWhereUnwrapped(this, predicate).length > 0;
}

/**
Expand Down
19 changes: 17 additions & 2 deletions src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isEqual } from 'underscore';
import {
isDOMComponent,
findDOMNode,
Children,
} from './react-compat';
import {
REACT013,
Expand Down Expand Up @@ -64,15 +65,14 @@ export function nodeEqual(a, b) {
if (a === b) return true;
if (!a || !b) return false;
if (a.type !== b.type) return false;

const left = propsOfNode(a);
const leftKeys = Object.keys(left);
const right = propsOfNode(b);
for (let i = 0; i < leftKeys.length; i++) {
const prop = leftKeys[i];
if (!(prop in right)) return false;
if (prop === 'children') {
if (!childrenEqual(left.children, right.children)) return false;
if (!childrenEqual(Children.toArray(left.children), Children.toArray(right.children))) return false;
} else if (right[prop] === left[prop]) {
// continue;
} else if (typeof right[prop] === typeof left[prop] && typeof left[prop] === 'object') {
Expand All @@ -89,6 +89,21 @@ export function nodeEqual(a, b) {
return false;
}

export function containsChildrenSubArray(match, node, subArray) {
const children = childrenOfNode(node);
return children.some((_, i) => arraysEqual(match, children.slice(i, subArray.length), subArray));
}

function arraysEqual(match, left, right) {
return left.length === right.length && left.every((el, i) => match(el, right[i]));
}

function childrenOfNode(node) {
const { children } = propsOfNode(node);
return Children.toArray(children);
}


// 'click' => 'onClick'
// 'mouseEnter' => 'onMouseEnter'
export function propFromEvent(event) {
Expand Down
22 changes: 22 additions & 0 deletions src/__tests__/ReactWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ describeWithDOM('mount', () => {
expect(wrapper.contains(b)).to.equal(true);
});

it('should do something with arrays of nodes', () => {
const wrapper = mount(
<div>
<span>Hello</span>
<div>Goodbye</div>
<span>More</span>
</div>
);
const fails = [
<span>wrong</span>,
<div>Goodbye</div>,
];

const passes = [
<span>Hello</span>,
<div>Goodbye</div>,
];

expect(wrapper.contains(fails)).to.equal(false);
expect(wrapper.contains(passes)).to.equal(true);
});

});

describe('.find(selector)', () => {
Expand Down
22 changes: 22 additions & 0 deletions src/__tests__/ShallowWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ describe('shallow', () => {
expect(wrapper.contains(<div>{5}</div>)).to.equal(true);
});

it('should do something with arrays of nodes', () => {
const wrapper = shallow(
<div>
<span>Hello</span>
<div>Goodbye</div>
<span>More</span>
</div>
);
const fails = [
<span>wrong</span>,
<div>Goodbye</div>,
];

const passes = [
<span>Hello</span>,
<div>Goodbye</div>,
];

expect(wrapper.contains(fails)).to.equal(false);
expect(wrapper.contains(passes)).to.equal(true);
});

});

describe('.equals(node)', () => {
Expand Down
9 changes: 7 additions & 2 deletions src/react-compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ let renderIntoDocument;
let findDOMNode;
let React;
let ReactContext;

React = require('react');

if (REACT013) {
renderToStaticMarkup = require('react').renderToStaticMarkup;
React = require('react');
renderToStaticMarkup = React.renderToStaticMarkup;
/* eslint-disable react/no-deprecated */
findDOMNode = React.findDOMNode;
/* eslint-enable react/no-deprecated */
Expand Down Expand Up @@ -88,6 +90,8 @@ const {
findAllInRenderedTree,
} = TestUtils;

const { Children } = React;

export {
createShallowRenderer,
renderToStaticMarkup,
Expand All @@ -102,4 +106,5 @@ export {
Simulate,
findDOMNode,
findAllInRenderedTree,
Children,
};

0 comments on commit 3041ec3

Please sign in to comment.