diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
index c01f4c056..cc3c97d23 100644
--- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
@@ -2502,8 +2502,8 @@ describeWithDOM('mount', () => {
]);
});
- describe('setProps should not call componentDidUpdate twice', () => {
- it('first test case', () => {
+ describe('setProps does not call componentDidUpdate twice', () => {
+ it('when setState is called in cWRP', () => {
class Dummy extends React.Component {
constructor(...args) {
super(...args);
@@ -6946,6 +6946,125 @@ describeWithDOM('mount', () => {
wrapper.instance().setDeepDifferentState();
expect(updateSpy).to.have.property('callCount', 1);
});
+
+ describeIf(is('>= 16.3'), 'setProps calls `componentDidUpdate` when `getDerivedStateFromProps` is defined', () => {
+ class DummyComp extends PureComponent {
+ constructor(...args) {
+ super(...args);
+ this.state = { state: -1 };
+ }
+
+ static getDerivedStateFromProps({ changeState, counter }) {
+ return changeState ? { state: counter * 10 } : null;
+ }
+
+ componentDidUpdate() {}
+
+ render() {
+ const { counter } = this.props;
+ const { state } = this.state;
+ return (
+
+ {counter}
+ {state}
+
+ );
+ }
+ }
+
+ const cDU = sinon.spy(DummyComp.prototype, 'componentDidUpdate');
+ const gDSFP = sinon.spy(DummyComp, 'getDerivedStateFromProps');
+
+ beforeEach(() => { // eslint-disable-line mocha/no-sibling-hooks
+ cDU.resetHistory();
+ gDSFP.resetHistory();
+ });
+
+ it('with no state changes, calls both methods with a sync and async setProps', () => {
+ const wrapper = mount();
+
+ expect(cDU).to.have.property('callCount', 0);
+ expect(gDSFP).to.have.property('callCount', 1);
+ const [firstCall] = gDSFP.args;
+ expect(firstCall).to.eql([{
+ changeState: false,
+ counter: 0,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: -1 });
+
+ wrapper.setProps({ counter: 1 });
+
+ expect(cDU).to.have.property('callCount', 1);
+ expect(gDSFP).to.have.property('callCount', 2);
+ const [, secondCall] = gDSFP.args;
+ expect(secondCall).to.eql([{
+ changeState: false,
+ counter: 1,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: -1 });
+
+ return new Promise((resolve) => {
+ wrapper.setProps({ counter: 2 }, resolve);
+ }).then(() => {
+ expect(cDU).to.have.property('callCount', 2);
+ expect(gDSFP).to.have.property('callCount', 3);
+ const [, , thirdCall] = gDSFP.args;
+ expect(thirdCall).to.eql([{
+ changeState: false,
+ counter: 2,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: -1 });
+ });
+ });
+
+ it('with a state changes, calls both methods with a sync and async setProps', () => {
+ const wrapper = mount();
+
+ expect(gDSFP).to.have.property('callCount', 1);
+ const [firstCall] = gDSFP.args;
+ expect(firstCall).to.eql([{
+ changeState: true,
+ counter: 0,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: 0 });
+
+ wrapper.setProps({ counter: 1 });
+
+ expect(cDU).to.have.property('callCount', 1);
+ expect(gDSFP).to.have.property('callCount', 2);
+ const [, secondCall] = gDSFP.args;
+ expect(secondCall).to.eql([{
+ changeState: true,
+ counter: 1,
+ }, {
+ state: 0,
+ }]);
+ expect(wrapper.state()).to.eql({ state: 10 });
+
+ return new Promise((resolve) => {
+ wrapper.setProps({ counter: 2 }, resolve);
+ }).then(() => {
+ expect(cDU).to.have.property('callCount', 2);
+ expect(gDSFP).to.have.property('callCount', 3);
+ const [, , thirdCall] = gDSFP.args;
+ expect(thirdCall).to.eql([{
+ changeState: true,
+ counter: 2,
+ }, {
+ state: 10,
+ }]);
+ expect(wrapper.state()).to.eql({ state: 20 });
+ });
+ });
+ });
});
describe('Own PureComponent implementation', () => {
diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
index ccf776123..b20b598f0 100644
--- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
@@ -2505,8 +2505,8 @@ describe('shallow', () => {
]);
});
- describe('setProps should not call componentDidUpdate twice', () => {
- it('first test case', () => {
+ describe('setProps does not call componentDidUpdate twice', () => {
+ it('when setState is called in cWRP', () => {
class Dummy extends React.Component {
constructor(...args) {
super(...args);
@@ -7235,6 +7235,125 @@ describe('shallow', () => {
wrapper.instance().setDeepDifferentState();
expect(updateSpy).to.have.property('callCount', 1);
});
+
+ describeIf(is('>= 16.3'), 'setProps calls `componentDidUpdate` when `getDerivedStateFromProps` is defined', () => {
+ class DummyComp extends PureComponent {
+ constructor(...args) {
+ super(...args);
+ this.state = { state: -1 };
+ }
+
+ static getDerivedStateFromProps({ changeState, counter }) {
+ return changeState ? { state: counter * 10 } : null;
+ }
+
+ componentDidUpdate() {}
+
+ render() {
+ const { counter } = this.props;
+ const { state } = this.state;
+ return (
+
+ {counter}
+ {state}
+
+ );
+ }
+ }
+
+ const cDU = sinon.spy(DummyComp.prototype, 'componentDidUpdate');
+ const gDSFP = sinon.spy(DummyComp, 'getDerivedStateFromProps');
+
+ beforeEach(() => { // eslint-disable-line mocha/no-sibling-hooks
+ cDU.resetHistory();
+ gDSFP.resetHistory();
+ });
+
+ it('with no state changes, calls both methods with a sync and async setProps', () => {
+ const wrapper = shallow();
+
+ expect(gDSFP).to.have.property('callCount', 1);
+ const [firstCall] = gDSFP.args;
+ expect(firstCall).to.eql([{
+ changeState: false,
+ counter: 0,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: -1 });
+
+ wrapper.setProps({ counter: 1 });
+
+ expect(cDU).to.have.property('callCount', 1);
+ expect(gDSFP).to.have.property('callCount', 2);
+ const [, secondCall] = gDSFP.args;
+ expect(secondCall).to.eql([{
+ changeState: false,
+ counter: 1,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: -1 });
+
+ return new Promise((resolve) => {
+ wrapper.setProps({ counter: 2 }, resolve);
+ }).then(() => {
+ expect(cDU).to.have.property('callCount', 2);
+ expect(gDSFP).to.have.property('callCount', 3);
+ const [, , thirdCall] = gDSFP.args;
+ expect(thirdCall).to.eql([{
+ changeState: false,
+ counter: 2,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: -1 });
+ });
+ });
+
+ it('with a state changes, calls both methods with a sync and async setProps', () => {
+ const wrapper = shallow();
+
+ expect(cDU).to.have.property('callCount', 0);
+ expect(gDSFP).to.have.property('callCount', 1);
+ const [firstCall] = gDSFP.args;
+ expect(firstCall).to.eql([{
+ changeState: true,
+ counter: 0,
+ }, {
+ state: -1,
+ }]);
+ expect(wrapper.state()).to.eql({ state: 0 });
+
+ wrapper.setProps({ counter: 1 });
+
+ expect(cDU).to.have.property('callCount', 1);
+ expect(gDSFP).to.have.property('callCount', 2);
+ const [, secondCall] = gDSFP.args;
+ expect(secondCall).to.eql([{
+ changeState: true,
+ counter: 1,
+ }, {
+ state: 0,
+ }]);
+ expect(wrapper.state()).to.eql({ state: 10 });
+
+ return new Promise((resolve) => {
+ wrapper.setProps({ counter: 2 }, resolve);
+ }).then(() => {
+ expect(cDU).to.have.property('callCount', 2);
+ expect(gDSFP).to.have.property('callCount', 3);
+ const [, , thirdCall] = gDSFP.args;
+ expect(thirdCall).to.eql([{
+ changeState: true,
+ counter: 2,
+ }, {
+ state: 10,
+ }]);
+ expect(wrapper.state()).to.eql({ state: 20 });
+ });
+ });
+ });
});
describe('Own PureComponent implementation', () => {
diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js
index fca3632d6..7885e5d12 100644
--- a/packages/enzyme/src/ShallowWrapper.js
+++ b/packages/enzyme/src/ShallowWrapper.js
@@ -462,6 +462,7 @@ class ShallowWrapper {
// this case, state will be undefined, but props/context will exist.
const node = this[RENDERER].getNode();
const instance = node.instance || {};
+ const type = node.type || {};
const { state } = instance;
const prevProps = instance.props || this[UNRENDERED].props;
const prevContext = instance.context || this[OPTIONS].context;
@@ -522,7 +523,11 @@ class ShallowWrapper {
if (
lifecycles.componentDidUpdate
&& typeof instance.componentDidUpdate === 'function'
- && (!state || shallowEqual(state, this.instance().state))
+ && (
+ !state
+ || shallowEqual(state, this.instance().state)
+ || typeof type.getDerivedStateFromProps === 'function'
+ )
) {
instance.componentDidUpdate(prevProps, state, snapshot);
}