diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index 4e8159392..3cb8261b2 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -1699,6 +1699,22 @@ describeWithDOM('mount', () => { }); describe('.setProps(newProps[, callback])', () => { + it('throws on a non-function callback', () => { + class Foo extends React.Component { + render() { + return null; + } + } + const wrapper = mount(); + + expect(() => wrapper.setProps({}, undefined)).to.throw(); + expect(() => wrapper.setProps({}, null)).to.throw(); + expect(() => wrapper.setProps({}, false)).to.throw(); + expect(() => wrapper.setProps({}, true)).to.throw(); + expect(() => wrapper.setProps({}, [])).to.throw(); + expect(() => wrapper.setProps({}, {})).to.throw(); + }); + it('should set props for a component multiple times', () => { class Foo extends React.Component { render() { @@ -2361,6 +2377,22 @@ describeWithDOM('mount', () => { }); describe('.setState(newState[, callback])', () => { + it('throws on a non-function callback', () => { + class Foo extends React.Component { + render() { + return null; + } + } + const wrapper = mount(); + + expect(() => wrapper.setState({}, undefined)).to.throw(); + expect(() => wrapper.setState({}, null)).to.throw(); + expect(() => wrapper.setState({}, false)).to.throw(); + expect(() => wrapper.setState({}, true)).to.throw(); + expect(() => wrapper.setState({}, [])).to.throw(); + expect(() => wrapper.setState({}, {})).to.throw(); + }); + it('should set the state of the root node', () => { class Foo extends React.Component { constructor(props) { @@ -2456,13 +2488,17 @@ describeWithDOM('mount', () => { }); }); - it('should throw error when cb is not a function', () => { + it('throws an error when cb is not a function', () => { class Foo extends React.Component { constructor(props) { super(props); this.state = { id: 'foo' }; } + setBadState() { + this.setState({}, 1); + } + render() { return (
@@ -2472,6 +2508,29 @@ describeWithDOM('mount', () => { const wrapper = mount(); expect(wrapper.state()).to.eql({ id: 'foo' }); expect(() => wrapper.setState({ id: 'bar' }, 1)).to.throw(Error); + expect(() => wrapper.instance().setBadState()).to.throw(Error); + }); + + it('does not throw with a null/undefined callback', () => { + class Foo extends React.Component { + constructor() { + super(); + + this.state = {}; + } + + setStateWithNullishCallback() { + this.setState({}, null); + this.setState({}, undefined); + } + + render() { + return null; + } + } + + const wrapper = mount(); + expect(() => wrapper.instance().setStateWithNullishCallback()).not.to.throw(); }); it('should preserve the receiver', () => { diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index b59bb8ee9..c7ab783f3 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -1514,6 +1514,22 @@ describe('shallow', () => { }); describe('.setProps(newProps)', () => { + it('throws on a non-function callback', () => { + class Foo extends React.Component { + render() { + return null; + } + } + const wrapper = shallow(); + + expect(() => wrapper.setProps({}, undefined)).to.throw(); + expect(() => wrapper.setProps({}, null)).to.throw(); + expect(() => wrapper.setProps({}, false)).to.throw(); + expect(() => wrapper.setProps({}, true)).to.throw(); + expect(() => wrapper.setProps({}, [])).to.throw(); + expect(() => wrapper.setProps({}, {})).to.throw(); + }); + it('should set props for a component multiple times', () => { class Foo extends React.Component { render() { @@ -2309,6 +2325,22 @@ describe('shallow', () => { }); describe('.setState(newState[, callback])', () => { + it('throws on a non-function callback', () => { + class Foo extends React.Component { + render() { + return null; + } + } + const wrapper = shallow(); + + expect(() => wrapper.setState({}, undefined)).to.throw(); + expect(() => wrapper.setState({}, null)).to.throw(); + expect(() => wrapper.setState({}, false)).to.throw(); + expect(() => wrapper.setState({}, true)).to.throw(); + expect(() => wrapper.setState({}, [])).to.throw(); + expect(() => wrapper.setState({}, {})).to.throw(); + }); + it('should set the state of the root node', () => { class Foo extends React.Component { constructor(props) { @@ -2402,13 +2434,17 @@ describe('shallow', () => { }); }); - it('should throw error when cb is not a function', () => { + it('throws an error when cb is not a function', () => { class Foo extends React.Component { constructor(props) { super(props); this.state = { id: 'foo' }; } + setBadState() { + this.setState({}, 1); + } + render() { return (
@@ -2418,6 +2454,29 @@ describe('shallow', () => { const wrapper = shallow(); expect(wrapper.state()).to.eql({ id: 'foo' }); expect(() => wrapper.setState({ id: 'bar' }, 1)).to.throw(Error); + expect(() => wrapper.instance().setBadState()).to.throw(Error); + }); + + it('does not throw with a null/undefined callback', () => { + class Foo extends React.Component { + constructor() { + super(); + + this.state = {}; + } + + setStateWithNullishCallback() { + this.setState({}, null); + this.setState({}, undefined); + } + + render() { + return null; + } + } + + const wrapper = shallow(); + expect(() => wrapper.instance().setStateWithNullishCallback()).not.to.throw(); }); it('should preserve the receiver', () => { diff --git a/packages/enzyme/src/ReactWrapper.js b/packages/enzyme/src/ReactWrapper.js index 02b56b59b..37d966df7 100644 --- a/packages/enzyme/src/ReactWrapper.js +++ b/packages/enzyme/src/ReactWrapper.js @@ -25,8 +25,6 @@ import { import { buildPredicate, reduceTreesBySelector } from './selectors'; -const noop = () => {}; - const NODE = sym('__node__'); const NODES = sym('__nodes__'); const RENDERER = sym('__renderer__'); @@ -273,18 +271,20 @@ class ReactWrapper { * @param {Function} cb - callback function * @returns {ReactWrapper} */ - setProps(props, callback = noop) { + setProps(props, callback = undefined) { if (this[ROOT] !== this) { throw new Error('ReactWrapper::setProps() can only be called on the root'); } - if (typeof callback !== 'function') { + if (arguments.length > 1 && typeof callback !== 'function') { throw new TypeError('ReactWrapper::setProps() expects a function as its second argument'); } const adapter = getAdapter(this[OPTIONS]); this[UNRENDERED] = cloneElement(adapter, this[UNRENDERED], props); this[RENDERER].render(this[UNRENDERED], null, () => { this.update(); - callback(); + if (callback) { + callback(); + } }); return this; } diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index 1e9e68969..47ae8465b 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -29,8 +29,6 @@ import { } from './RSTTraversal'; import { buildPredicate, reduceTreesBySelector } from './selectors'; -const noop = () => {}; - const NODE = sym('__node__'); const NODES = sym('__nodes__'); const RENDERER = sym('__renderer__'); @@ -187,7 +185,9 @@ class ShallowWrapper { // Ensure to call componentDidUpdate when instance.setState is called if (lifecycles.componentDidUpdate.onSetState && !instance[SET_STATE]) { privateSet(instance, SET_STATE, instance.setState); - instance.setState = (...args) => this.setState(...args); + instance.setState = (updater, callback = undefined) => this.setState( + ...(callback == null ? [updater] : [updater, callback]), + ); } if (typeof instance.componentDidMount === 'function') { @@ -398,15 +398,17 @@ class ShallowWrapper { * @param {Function} cb - callback function * @returns {ShallowWrapper} */ - setProps(props, callback = noop) { + setProps(props, callback = undefined) { if (this[ROOT] !== this) { throw new Error('ShallowWrapper::setProps() can only be called on the root'); } - if (typeof callback !== 'function') { - throw new TypeError('ShallowWrapper::setProps() expects a function as its second argument'); + if (arguments.length > 1 && typeof callback !== 'function') { + throw new TypeError('ReactWrapper::setProps() expects a function as its second argument'); } this.rerender(props); - callback(); + if (callback) { + callback(); + } return this; }