Skip to content

Commit d3d2259

Browse files
koba04ljharb
authored andcommitted
[New] shallow/mount: add invoke(propName)(...args)
1 parent 60ea652 commit d3d2259

File tree

9 files changed

+220
-0
lines changed

9 files changed

+220
-0
lines changed

SUMMARY.md

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* [hostNodes()](/docs/api/ShallowWrapper/hostNodes.md)
5050
* [html()](/docs/api/ShallowWrapper/html.md)
5151
* [instance()](/docs/api/ShallowWrapper/instance.md)
52+
* [invoke(propName)](/docs/api/ShallowWrapper/invoke.md)
5253
* [is(selector)](/docs/api/ShallowWrapper/is.md)
5354
* [isEmpty()](/docs/api/ShallowWrapper/isEmpty.md)
5455
* [isEmptyRender()](/docs/api/ShallowWrapper/isEmptyRender.md)
@@ -110,6 +111,7 @@
110111
* [hostNodes()](/docs/api/ReactWrapper/hostNodes.md)
111112
* [html()](/docs/api/ReactWrapper/html.md)
112113
* [instance()](/docs/api/ReactWrapper/instance.md)
114+
* [invoke(propName)](/docs/api/ReactWrapper/invoke.md)
113115
* [is(selector)](/docs/api/ReactWrapper/is.md)
114116
* [isEmpty()](/docs/api/ReactWrapper/isEmpty.md)
115117
* [isEmptyRender()](/docs/api/ReactWrapper/isEmptyRender.md)

docs/api/ReactWrapper/invoke.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# `.invoke(propName)(...args) => Any`
2+
3+
Invokes a function prop.
4+
5+
#### Arguments
6+
7+
1. `propName` (`String`): The function prop that is invoked
8+
2. `...args` (`Any` [optional]): Arguments that is passed to the prop function
9+
10+
11+
12+
#### Returns
13+
14+
`Any`: Returns the value from the prop function
15+
16+
#### Example
17+
18+
```jsx
19+
class Foo extends React.Component {
20+
loadData() {
21+
return fetch();
22+
}
23+
24+
render() {
25+
return (
26+
<div>
27+
<button
28+
type="button"
29+
onClick={() => this.loadData()}
30+
>
31+
Load more
32+
</button>
33+
</div>
34+
);
35+
}
36+
}
37+
const wrapper = mount(<Foo />);
38+
wrapper.find('a').invoke('onClick')().then(() => {
39+
// expect()
40+
});
41+
```

docs/api/ShallowWrapper/invoke.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# `.invoke(invokePropName)(...args) => Any`
2+
3+
Invokes a function prop.
4+
5+
#### Arguments
6+
7+
1. `propName` (`String`): The function prop that is invoked
8+
2. `...args` (`Any` [optional]): Arguments that is passed to the prop function
9+
10+
This essentially calls wrapper.prop(propName)(...args).
11+
12+
#### Returns
13+
14+
`Any`: Returns the value from the prop function
15+
16+
#### Example
17+
18+
```jsx
19+
class Foo extends React.Component {
20+
loadData() {
21+
return fetch();
22+
}
23+
24+
render() {
25+
return (
26+
<div>
27+
<button
28+
type="button"
29+
onClick={() => this.loadData()}
30+
>
31+
Load more
32+
</button>
33+
</div>
34+
);
35+
}
36+
}
37+
const wrapper = shallow(<Foo />);
38+
wrapper.find('a').invoke('onClick')().then(() => {
39+
// expect()
40+
});

docs/api/mount.md

+3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ Returns the props of the root component.
161161
#### [`.prop(key) => Any`](ReactWrapper/prop.md)
162162
Returns the named prop of the root component.
163163

164+
#### [`.invoke(propName)(...args) => Any`](ReactWrapper/invoke.md)
165+
Invokes a prop function on the current node and returns the function's return value.
166+
164167
#### [`.key() => String`](ReactWrapper/key.md)
165168
Returns the key of the root component.
166169

docs/api/shallow.md

+3
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ Returns the named prop of the current node.
177177
#### [`.key() => String`](ShallowWrapper/key.md)
178178
Returns the key of the current node.
179179

180+
#### [`.invoke(propName)(...args) => Any`](ShallowWrapper/invoke.md)
181+
Invokes a prop function on the current node and returns the function's return value.
182+
180183
#### [`.simulate(event[, data]) => ShallowWrapper`](ShallowWrapper/simulate.md)
181184
Simulates an event on the current node.
182185

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,7 @@ describeWithDOM('mount', () => {
10431043
'hostNodes',
10441044
'html',
10451045
'instance',
1046+
'invoke',
10461047
'is',
10471048
'isEmpty',
10481049
'isEmptyRender',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import sinon from 'sinon-sandbox';
4+
5+
export default function describeInvoke({
6+
Wrap,
7+
WrapperName,
8+
}) {
9+
describe('.invoke(propName)(..args)', () => {
10+
class CounterButton extends React.Component {
11+
constructor(props) {
12+
super(props);
13+
this.state = { count: 0 };
14+
}
15+
16+
render() {
17+
const { count } = this.state;
18+
return (
19+
<div>
20+
<button
21+
type="button"
22+
onClick={() => this.setState(({ count: oldCount }) => ({ count: oldCount + 1 }))}
23+
>
24+
{count}
25+
</button>
26+
</div>
27+
);
28+
}
29+
}
30+
31+
class ClickableLink extends React.Component {
32+
render() {
33+
const { onClick } = this.props;
34+
return (
35+
<div>
36+
<a onClick={onClick}>foo</a>
37+
</div>
38+
);
39+
}
40+
}
41+
42+
it('throws when pointing to a non-function prop', () => {
43+
const wrapper = Wrap(<div data-a={{}} />);
44+
45+
expect(() => wrapper.invoke('data-a')).to.throw(
46+
TypeError,
47+
`${WrapperName}::invoke() requires the name of a prop whose value is a function`,
48+
);
49+
50+
expect(() => wrapper.invoke('does not exist')).to.throw(
51+
TypeError,
52+
`${WrapperName}::invoke() requires the name of a prop whose value is a function`,
53+
);
54+
});
55+
56+
it('can update the state value', () => {
57+
const wrapper = Wrap(<CounterButton />);
58+
expect(wrapper.state('count')).to.equal(0);
59+
wrapper.find('button').invoke('onClick')();
60+
expect(wrapper.state('count')).to.equal(1);
61+
});
62+
63+
it('can return the handlers’ return value', () => {
64+
const sentinel = {};
65+
const spy = sinon.stub().returns(sentinel);
66+
67+
const wrapper = Wrap(<ClickableLink onClick={spy} />);
68+
69+
const value = wrapper.find('a').invoke('onClick')();
70+
expect(value).to.equal(sentinel);
71+
expect(spy).to.have.property('callCount', 1);
72+
});
73+
74+
it('can pass in arguments', () => {
75+
const spy = sinon.spy();
76+
77+
const wrapper = Wrap(<ClickableLink onClick={spy} />);
78+
79+
const a = {};
80+
const b = {};
81+
wrapper.find('a').invoke('onClick')(a, b);
82+
expect(spy).to.have.property('callCount', 1);
83+
const [[arg1, arg2]] = spy.args;
84+
expect(arg1).to.equal(a);
85+
expect(arg2).to.equal(b);
86+
});
87+
});
88+
}

packages/enzyme/src/ReactWrapper.js

+21
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,27 @@ class ReactWrapper {
827827
return this.props()[propName];
828828
}
829829

830+
/**
831+
* Used to invoke a function prop.
832+
* Will invoke an function prop and return its value.
833+
*
834+
* @param {String} propName
835+
* @returns {Any}
836+
*/
837+
invoke(propName) {
838+
return this.single('invoke', () => {
839+
const handler = this.prop(propName);
840+
if (typeof handler !== 'function') {
841+
throw new TypeError('ReactWrapper::invoke() requires the name of a prop whose value is a function');
842+
}
843+
return (...args) => {
844+
const response = handler(...args);
845+
this[ROOT].update();
846+
return response;
847+
};
848+
});
849+
}
850+
830851
/**
831852
* Returns a wrapper of the node rendered by the provided render prop.
832853
*

packages/enzyme/src/ShallowWrapper.js

+21
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,27 @@ class ShallowWrapper {
12951295
return this.props()[propName];
12961296
}
12971297

1298+
/**
1299+
* Used to invoke a function prop.
1300+
* Will invoke an function prop and return its value.
1301+
*
1302+
* @param {String} propName
1303+
* @returns {Any}
1304+
*/
1305+
invoke(propName) {
1306+
return this.single('invoke', () => {
1307+
const handler = this.prop(propName);
1308+
if (typeof handler !== 'function') {
1309+
throw new TypeError('ShallowWrapper::invoke() requires the name of a prop whose value is a function');
1310+
}
1311+
return (...args) => {
1312+
const response = handler(...args);
1313+
this[ROOT].update();
1314+
return response;
1315+
};
1316+
});
1317+
}
1318+
12981319
/**
12991320
* Returns a wrapper of the node rendered by the provided render prop.
13001321
*

0 commit comments

Comments
 (0)