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;
}