From 02c2b1ba2ced61f3bc2621052dd895d9e9793eb0 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Fri, 8 Mar 2019 01:05:41 +0900 Subject: [PATCH] add invoke() api to shallow and mount --- SUMMARY.md | 2 + docs/api/ReactWrapper/invoke.md | 38 +++++++++++ docs/api/ShallowWrapper/invoke.md | 38 +++++++++++ docs/api/mount.md | 3 + docs/api/shallow.md | 3 + packages/enzyme-test-suite/package.json | 2 +- .../test/ReactWrapper-spec.jsx | 64 +++++++++++++++++++ .../test/ShallowWrapper-spec.jsx | 64 +++++++++++++++++++ packages/enzyme/src/ReactWrapper.js | 20 ++++++ packages/enzyme/src/ShallowWrapper.js | 20 ++++++ 10 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 docs/api/ReactWrapper/invoke.md create mode 100644 docs/api/ShallowWrapper/invoke.md diff --git a/SUMMARY.md b/SUMMARY.md index b2adcb25a..46906c113 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -48,6 +48,7 @@ * [hostNodes()](/docs/api/ShallowWrapper/hostNodes.md) * [html()](/docs/api/ShallowWrapper/html.md) * [instance()](/docs/api/ShallowWrapper/instance.md) + * [invoke(propName[, ...args])](/docs/api/ShallowWrapper/invoke.md) * [is(selector)](/docs/api/ShallowWrapper/is.md) * [isEmpty()](/docs/api/ShallowWrapper/isEmpty.md) * [isEmptyRender()](/docs/api/ShallowWrapper/isEmptyRender.md) @@ -108,6 +109,7 @@ * [hostNodes()](/docs/api/ReactWrapper/hostNodes.md) * [html()](/docs/api/ReactWrapper/html.md) * [instance()](/docs/api/ReactWrapper/instance.md) + * [invoke(propName[, ...args])](/docs/api/ReactWrapper/invoke.md) * [is(selector)](/docs/api/ReactWrapper/is.md) * [isEmpty()](/docs/api/ReactWrapper/isEmpty.md) * [isEmptyRender()](/docs/api/ReactWrapper/isEmptyRender.md) diff --git a/docs/api/ReactWrapper/invoke.md b/docs/api/ReactWrapper/invoke.md new file mode 100644 index 000000000..222e15670 --- /dev/null +++ b/docs/api/ReactWrapper/invoke.md @@ -0,0 +1,38 @@ +# `.invoke(propName[, ...args]) => Any` + + Invokes a function prop. + + #### Arguments + + 1. `propName` (`String`): The function prop to be invoked +2. `...args` (`Any` [optional]): Arguments that will be 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 ( +
+ +
+ ); + } +} + const wrapper = mount(); + wrapper.find('a').invoke('onClick').then(() => { + // expect() +}); +``` diff --git a/docs/api/ShallowWrapper/invoke.md b/docs/api/ShallowWrapper/invoke.md new file mode 100644 index 000000000..9fb63d381 --- /dev/null +++ b/docs/api/ShallowWrapper/invoke.md @@ -0,0 +1,38 @@ +# `.invoke(invokeName[, ...args]) => Any` + + Invokes a function prop. + + #### Arguments + + 1. `propName` (`String`): The function prop to be invoked +2. `...args` (`Any` [optional]): Arguments that will be 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 ( +
+ +
+ ); + } +} + const wrapper = shallow(); + wrapper.find('a').invoke('onClick').then(() => { + // expect() +}); +``` diff --git a/docs/api/mount.md b/docs/api/mount.md index 58b4e1f16..c4d993495 100644 --- a/docs/api/mount.md +++ b/docs/api/mount.md @@ -159,6 +159,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. diff --git a/docs/api/shallow.md b/docs/api/shallow.md index 7fd47d65a..98bcc1174 100644 --- a/docs/api/shallow.md +++ b/docs/api/shallow.md @@ -175,6 +175,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. diff --git a/packages/enzyme-test-suite/package.json b/packages/enzyme-test-suite/package.json index 4bbf4023b..7c4d1d543 100644 --- a/packages/enzyme-test-suite/package.json +++ b/packages/enzyme-test-suite/package.json @@ -57,4 +57,4 @@ "eslint-plugin-react": "^7.12.4", "react-is": "^16.7.0" } -} +} \ No newline at end of file diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index e39eaa063..f92cc788f 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -5408,6 +5408,70 @@ describeWithDOM('mount', () => { }); }); + describe('.invoke(propName, ..args)', () => { + it('can update the state value', () => { + class Foo extends React.Component { + constructor(props) { + super(props); + this.state = { + count: 0, + }; + } + + render() { + const { count } = this.state; + return ( +
+ +
+ ); + } + } + const wrapper = mount(); + wrapper.find('button').invoke('onClick'); + expect(wrapper.state('count')).to.equal(1); + }); + it('can return the handlers return value', () => { + const spy = sinon.stub().returns(123); + class Foo extends React.Component { + render() { + return ( +
+ foo +
+ ); + } + } + const wrapper = mount(); + const value = wrapper.find('a').invoke('onClick'); + expect(value).to.equal(123); + expect(spy.called).to.equal(true); + }); + it('can pass in arguments', () => { + const spy = sinon.spy(); + class Foo extends React.Component { + render() { + return ( +
+ foo +
+ ); + } + } + const wrapper = mount(); + const a = {}; + const b = {}; + wrapper.find('a').invoke('onClick', a, b); + expect(spy.args[0][0]).to.equal(a); + expect(spy.args[0][1]).to.equal(b); + }); + }); + wrap() .withConsoleThrows() .describe('.renderProp()', () => { diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index 422ee07f4..ff94da129 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -5489,6 +5489,70 @@ describe('shallow', () => { }); }); + describe('.invoke(propName, ..args)', () => { + it('can update the state value', () => { + class Foo extends React.Component { + constructor(props) { + super(props); + this.state = { + count: 0, + }; + } + + render() { + const { count } = this.state; + return ( +
+ +
+ ); + } + } + const wrapper = shallow(); + wrapper.find('button').invoke('onClick'); + expect(wrapper.state('count')).to.equal(1); + }); + it('can return the handlers return value', () => { + const spy = sinon.stub().returns(123); + class Foo extends React.Component { + render() { + return ( +
+ +
+ ); + } + } + const wrapper = shallow(); + const value = wrapper.find('button').invoke('onClick'); + expect(value).to.equal(123); + expect(spy.called).to.equal(true); + }); + it('can pass in arguments', () => { + const spy = sinon.spy(); + class Foo extends React.Component { + render() { + return ( +
+ +
+ ); + } + } + const wrapper = shallow(); + const a = {}; + const b = {}; + wrapper.find('button').invoke('onClick', a, b); + expect(spy.args[0][0]).to.equal(a); + expect(spy.args[0][1]).to.equal(b); + }); + }); + wrap() .withConsoleThrows() .describe('.renderProp()', () => { diff --git a/packages/enzyme/src/ReactWrapper.js b/packages/enzyme/src/ReactWrapper.js index 6b6c9e3f0..56d0ac578 100644 --- a/packages/enzyme/src/ReactWrapper.js +++ b/packages/enzyme/src/ReactWrapper.js @@ -802,6 +802,26 @@ class ReactWrapper { return this.props()[propName]; } + /** + * Used to invoke a function prop. + * Will invoke an function prop and return its value. + * + * @param {String} propName + * @param {Array} args + * @returns {Any} + */ + invoke(propName, ...args) { + return this.single('invoke', () => { + const handler = this.prop(propName); + if (typeof handler !== 'function') { + throw new TypeError('ReactWrapper::invoke() expects a function prop name as its first argument'); + } + const response = handler.apply(this, args); + this[ROOT].update(); + return response; + }); + } + /** * Returns a wrapper of the node rendered by the provided render prop. * diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index b0165110e..dc5681dd7 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -1132,6 +1132,26 @@ class ShallowWrapper { return this.props()[propName]; } + /** + * Used to invoke a function prop. + * Will invoke an function prop and return its value. + * + * @param {String} propName + * @param {Array} args + * @returns {Any} + */ + invoke(propName, ...args) { + return this.single('invoke', () => { + const handler = this.prop(propName); + if (typeof handler !== 'function') { + throw new TypeError('ShallowWrapper::invoke() expects a function prop name as its first argument'); + } + const response = handler.apply(this, args); + this[ROOT].update(); + return response; + }); + } + /** * Returns a wrapper of the node rendered by the provided render prop. *