Skip to content

Commit

Permalink
[New] shallow/mount: add invoke(propName)(...args)
Browse files Browse the repository at this point in the history
Merge pull request #1856 from koba04/invoke-api
  • Loading branch information
ljharb committed Apr 6, 2019
2 parents ab86297 + d3d2259 commit 31c33e5
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 0 deletions.
2 changes: 2 additions & 0 deletions SUMMARY.md
Expand Up @@ -49,6 +49,7 @@
* [hostNodes()](/docs/api/ShallowWrapper/hostNodes.md)
* [html()](/docs/api/ShallowWrapper/html.md)
* [instance()](/docs/api/ShallowWrapper/instance.md)
* [invoke(propName)](/docs/api/ShallowWrapper/invoke.md)
* [is(selector)](/docs/api/ShallowWrapper/is.md)
* [isEmpty()](/docs/api/ShallowWrapper/isEmpty.md)
* [isEmptyRender()](/docs/api/ShallowWrapper/isEmptyRender.md)
Expand Down Expand Up @@ -110,6 +111,7 @@
* [hostNodes()](/docs/api/ReactWrapper/hostNodes.md)
* [html()](/docs/api/ReactWrapper/html.md)
* [instance()](/docs/api/ReactWrapper/instance.md)
* [invoke(propName)](/docs/api/ReactWrapper/invoke.md)
* [is(selector)](/docs/api/ReactWrapper/is.md)
* [isEmpty()](/docs/api/ReactWrapper/isEmpty.md)
* [isEmptyRender()](/docs/api/ReactWrapper/isEmptyRender.md)
Expand Down
41 changes: 41 additions & 0 deletions docs/api/ReactWrapper/invoke.md
@@ -0,0 +1,41 @@
# `.invoke(propName)(...args) => Any`

Invokes a function prop.

#### Arguments

1. `propName` (`String`): The function prop that is invoked
2. `...args` (`Any` [optional]): Arguments that is passed to the prop function



#### Returns

`Any`: Returns the value from the prop function

#### Example

```jsx
class Foo extends React.Component {
loadData() {
return fetch();
}

render() {
return (
<div>
<button
type="button"
onClick={() => this.loadData()}
>
Load more
</button>
</div>
);
}
}
const wrapper = mount(<Foo />);
wrapper.find('a').invoke('onClick')().then(() => {
// expect()
});
```
40 changes: 40 additions & 0 deletions docs/api/ShallowWrapper/invoke.md
@@ -0,0 +1,40 @@
# `.invoke(invokePropName)(...args) => Any`

Invokes a function prop.

#### Arguments

1. `propName` (`String`): The function prop that is invoked
2. `...args` (`Any` [optional]): Arguments that is passed to the prop function

This essentially calls wrapper.prop(propName)(...args).

#### Returns

`Any`: Returns the value from the prop function

#### Example

```jsx
class Foo extends React.Component {
loadData() {
return fetch();
}

render() {
return (
<div>
<button
type="button"
onClick={() => this.loadData()}
>
Load more
</button>
</div>
);
}
}
const wrapper = shallow(<Foo />);
wrapper.find('a').invoke('onClick')().then(() => {
// expect()
});
3 changes: 3 additions & 0 deletions docs/api/mount.md
Expand Up @@ -161,6 +161,9 @@ Returns the props of the root component.
#### [`.prop(key) => Any`](ReactWrapper/prop.md)
Returns the named prop of the root component.

#### [`.invoke(propName)(...args) => Any`](ReactWrapper/invoke.md)
Invokes a prop function on the current node and returns the function's return value.

#### [`.key() => String`](ReactWrapper/key.md)
Returns the key of the root component.

Expand Down
3 changes: 3 additions & 0 deletions docs/api/shallow.md
Expand Up @@ -177,6 +177,9 @@ Returns the named prop of the current node.
#### [`.key() => String`](ShallowWrapper/key.md)
Returns the key of the current node.

#### [`.invoke(propName)(...args) => Any`](ShallowWrapper/invoke.md)
Invokes a prop function on the current node and returns the function's return value.

#### [`.simulate(event[, data]) => ShallowWrapper`](ShallowWrapper/simulate.md)
Simulates an event on the current node.

Expand Down
1 change: 1 addition & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Expand Up @@ -1043,6 +1043,7 @@ describeWithDOM('mount', () => {
'hostNodes',
'html',
'instance',
'invoke',
'is',
'isEmpty',
'isEmptyRender',
Expand Down
88 changes: 88 additions & 0 deletions packages/enzyme-test-suite/test/shared/methods/invoke.jsx
@@ -0,0 +1,88 @@
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon-sandbox';

export default function describeInvoke({
Wrap,
WrapperName,
}) {
describe('.invoke(propName)(..args)', () => {
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

render() {
const { count } = this.state;
return (
<div>
<button
type="button"
onClick={() => this.setState(({ count: oldCount }) => ({ count: oldCount + 1 }))}
>
{count}
</button>
</div>
);
}
}

class ClickableLink extends React.Component {
render() {
const { onClick } = this.props;
return (
<div>
<a onClick={onClick}>foo</a>
</div>
);
}
}

it('throws when pointing to a non-function prop', () => {
const wrapper = Wrap(<div data-a={{}} />);

expect(() => wrapper.invoke('data-a')).to.throw(
TypeError,
`${WrapperName}::invoke() requires the name of a prop whose value is a function`,
);

expect(() => wrapper.invoke('does not exist')).to.throw(
TypeError,
`${WrapperName}::invoke() requires the name of a prop whose value is a function`,
);
});

it('can update the state value', () => {
const wrapper = Wrap(<CounterButton />);
expect(wrapper.state('count')).to.equal(0);
wrapper.find('button').invoke('onClick')();
expect(wrapper.state('count')).to.equal(1);
});

it('can return the handlers’ return value', () => {
const sentinel = {};
const spy = sinon.stub().returns(sentinel);

const wrapper = Wrap(<ClickableLink onClick={spy} />);

const value = wrapper.find('a').invoke('onClick')();
expect(value).to.equal(sentinel);
expect(spy).to.have.property('callCount', 1);
});

it('can pass in arguments', () => {
const spy = sinon.spy();

const wrapper = Wrap(<ClickableLink onClick={spy} />);

const a = {};
const b = {};
wrapper.find('a').invoke('onClick')(a, b);
expect(spy).to.have.property('callCount', 1);
const [[arg1, arg2]] = spy.args;
expect(arg1).to.equal(a);
expect(arg2).to.equal(b);
});
});
}
21 changes: 21 additions & 0 deletions packages/enzyme/src/ReactWrapper.js
Expand Up @@ -827,6 +827,27 @@ class ReactWrapper {
return this.props()[propName];
}

/**
* Used to invoke a function prop.
* Will invoke an function prop and return its value.
*
* @param {String} propName
* @returns {Any}
*/
invoke(propName) {
return this.single('invoke', () => {
const handler = this.prop(propName);
if (typeof handler !== 'function') {
throw new TypeError('ReactWrapper::invoke() requires the name of a prop whose value is a function');
}
return (...args) => {
const response = handler(...args);
this[ROOT].update();
return response;
};
});
}

/**
* Returns a wrapper of the node rendered by the provided render prop.
*
Expand Down
21 changes: 21 additions & 0 deletions packages/enzyme/src/ShallowWrapper.js
Expand Up @@ -1295,6 +1295,27 @@ class ShallowWrapper {
return this.props()[propName];
}

/**
* Used to invoke a function prop.
* Will invoke an function prop and return its value.
*
* @param {String} propName
* @returns {Any}
*/
invoke(propName) {
return this.single('invoke', () => {
const handler = this.prop(propName);
if (typeof handler !== 'function') {
throw new TypeError('ShallowWrapper::invoke() requires the name of a prop whose value is a function');
}
return (...args) => {
const response = handler(...args);
this[ROOT].update();
return response;
};
});
}

/**
* Returns a wrapper of the node rendered by the provided render prop.
*
Expand Down

0 comments on commit 31c33e5

Please sign in to comment.