Skip to content

Commit ebdd4e7

Browse files
authored
Merge pull request #2027 from peanutenthusiast/2020/cdu-on-setprops
[Fix] `shallow`: ensure that if gDSFP exists, cDU is called. Fixes #2020.
2 parents 82b9da8 + 275b0b3 commit ebdd4e7

File tree

3 files changed

+248
-5
lines changed

3 files changed

+248
-5
lines changed

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2502,8 +2502,8 @@ describeWithDOM('mount', () => {
25022502
]);
25032503
});
25042504

2505-
describe('setProps should not call componentDidUpdate twice', () => {
2506-
it('first test case', () => {
2505+
describe('setProps does not call componentDidUpdate twice', () => {
2506+
it('when setState is called in cWRP', () => {
25072507
class Dummy extends React.Component {
25082508
constructor(...args) {
25092509
super(...args);
@@ -6946,6 +6946,125 @@ describeWithDOM('mount', () => {
69466946
wrapper.instance().setDeepDifferentState();
69476947
expect(updateSpy).to.have.property('callCount', 1);
69486948
});
6949+
6950+
describeIf(is('>= 16.3'), 'setProps calls `componentDidUpdate` when `getDerivedStateFromProps` is defined', () => {
6951+
class DummyComp extends PureComponent {
6952+
constructor(...args) {
6953+
super(...args);
6954+
this.state = { state: -1 };
6955+
}
6956+
6957+
static getDerivedStateFromProps({ changeState, counter }) {
6958+
return changeState ? { state: counter * 10 } : null;
6959+
}
6960+
6961+
componentDidUpdate() {}
6962+
6963+
render() {
6964+
const { counter } = this.props;
6965+
const { state } = this.state;
6966+
return (
6967+
<p>
6968+
{counter}
6969+
{state}
6970+
</p>
6971+
);
6972+
}
6973+
}
6974+
6975+
const cDU = sinon.spy(DummyComp.prototype, 'componentDidUpdate');
6976+
const gDSFP = sinon.spy(DummyComp, 'getDerivedStateFromProps');
6977+
6978+
beforeEach(() => { // eslint-disable-line mocha/no-sibling-hooks
6979+
cDU.resetHistory();
6980+
gDSFP.resetHistory();
6981+
});
6982+
6983+
it('with no state changes, calls both methods with a sync and async setProps', () => {
6984+
const wrapper = mount(<DummyComp changeState={false} counter={0} />);
6985+
6986+
expect(cDU).to.have.property('callCount', 0);
6987+
expect(gDSFP).to.have.property('callCount', 1);
6988+
const [firstCall] = gDSFP.args;
6989+
expect(firstCall).to.eql([{
6990+
changeState: false,
6991+
counter: 0,
6992+
}, {
6993+
state: -1,
6994+
}]);
6995+
expect(wrapper.state()).to.eql({ state: -1 });
6996+
6997+
wrapper.setProps({ counter: 1 });
6998+
6999+
expect(cDU).to.have.property('callCount', 1);
7000+
expect(gDSFP).to.have.property('callCount', 2);
7001+
const [, secondCall] = gDSFP.args;
7002+
expect(secondCall).to.eql([{
7003+
changeState: false,
7004+
counter: 1,
7005+
}, {
7006+
state: -1,
7007+
}]);
7008+
expect(wrapper.state()).to.eql({ state: -1 });
7009+
7010+
return new Promise((resolve) => {
7011+
wrapper.setProps({ counter: 2 }, resolve);
7012+
}).then(() => {
7013+
expect(cDU).to.have.property('callCount', 2);
7014+
expect(gDSFP).to.have.property('callCount', 3);
7015+
const [, , thirdCall] = gDSFP.args;
7016+
expect(thirdCall).to.eql([{
7017+
changeState: false,
7018+
counter: 2,
7019+
}, {
7020+
state: -1,
7021+
}]);
7022+
expect(wrapper.state()).to.eql({ state: -1 });
7023+
});
7024+
});
7025+
7026+
it('with a state changes, calls both methods with a sync and async setProps', () => {
7027+
const wrapper = mount(<DummyComp changeState counter={0} />);
7028+
7029+
expect(gDSFP).to.have.property('callCount', 1);
7030+
const [firstCall] = gDSFP.args;
7031+
expect(firstCall).to.eql([{
7032+
changeState: true,
7033+
counter: 0,
7034+
}, {
7035+
state: -1,
7036+
}]);
7037+
expect(wrapper.state()).to.eql({ state: 0 });
7038+
7039+
wrapper.setProps({ counter: 1 });
7040+
7041+
expect(cDU).to.have.property('callCount', 1);
7042+
expect(gDSFP).to.have.property('callCount', 2);
7043+
const [, secondCall] = gDSFP.args;
7044+
expect(secondCall).to.eql([{
7045+
changeState: true,
7046+
counter: 1,
7047+
}, {
7048+
state: 0,
7049+
}]);
7050+
expect(wrapper.state()).to.eql({ state: 10 });
7051+
7052+
return new Promise((resolve) => {
7053+
wrapper.setProps({ counter: 2 }, resolve);
7054+
}).then(() => {
7055+
expect(cDU).to.have.property('callCount', 2);
7056+
expect(gDSFP).to.have.property('callCount', 3);
7057+
const [, , thirdCall] = gDSFP.args;
7058+
expect(thirdCall).to.eql([{
7059+
changeState: true,
7060+
counter: 2,
7061+
}, {
7062+
state: 10,
7063+
}]);
7064+
expect(wrapper.state()).to.eql({ state: 20 });
7065+
});
7066+
});
7067+
});
69497068
});
69507069

69517070
describe('Own PureComponent implementation', () => {

packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2505,8 +2505,8 @@ describe('shallow', () => {
25052505
]);
25062506
});
25072507

2508-
describe('setProps should not call componentDidUpdate twice', () => {
2509-
it('first test case', () => {
2508+
describe('setProps does not call componentDidUpdate twice', () => {
2509+
it('when setState is called in cWRP', () => {
25102510
class Dummy extends React.Component {
25112511
constructor(...args) {
25122512
super(...args);
@@ -7235,6 +7235,125 @@ describe('shallow', () => {
72357235
wrapper.instance().setDeepDifferentState();
72367236
expect(updateSpy).to.have.property('callCount', 1);
72377237
});
7238+
7239+
describeIf(is('>= 16.3'), 'setProps calls `componentDidUpdate` when `getDerivedStateFromProps` is defined', () => {
7240+
class DummyComp extends PureComponent {
7241+
constructor(...args) {
7242+
super(...args);
7243+
this.state = { state: -1 };
7244+
}
7245+
7246+
static getDerivedStateFromProps({ changeState, counter }) {
7247+
return changeState ? { state: counter * 10 } : null;
7248+
}
7249+
7250+
componentDidUpdate() {}
7251+
7252+
render() {
7253+
const { counter } = this.props;
7254+
const { state } = this.state;
7255+
return (
7256+
<p>
7257+
{counter}
7258+
{state}
7259+
</p>
7260+
);
7261+
}
7262+
}
7263+
7264+
const cDU = sinon.spy(DummyComp.prototype, 'componentDidUpdate');
7265+
const gDSFP = sinon.spy(DummyComp, 'getDerivedStateFromProps');
7266+
7267+
beforeEach(() => { // eslint-disable-line mocha/no-sibling-hooks
7268+
cDU.resetHistory();
7269+
gDSFP.resetHistory();
7270+
});
7271+
7272+
it('with no state changes, calls both methods with a sync and async setProps', () => {
7273+
const wrapper = shallow(<DummyComp changeState={false} counter={0} />);
7274+
7275+
expect(gDSFP).to.have.property('callCount', 1);
7276+
const [firstCall] = gDSFP.args;
7277+
expect(firstCall).to.eql([{
7278+
changeState: false,
7279+
counter: 0,
7280+
}, {
7281+
state: -1,
7282+
}]);
7283+
expect(wrapper.state()).to.eql({ state: -1 });
7284+
7285+
wrapper.setProps({ counter: 1 });
7286+
7287+
expect(cDU).to.have.property('callCount', 1);
7288+
expect(gDSFP).to.have.property('callCount', 2);
7289+
const [, secondCall] = gDSFP.args;
7290+
expect(secondCall).to.eql([{
7291+
changeState: false,
7292+
counter: 1,
7293+
}, {
7294+
state: -1,
7295+
}]);
7296+
expect(wrapper.state()).to.eql({ state: -1 });
7297+
7298+
return new Promise((resolve) => {
7299+
wrapper.setProps({ counter: 2 }, resolve);
7300+
}).then(() => {
7301+
expect(cDU).to.have.property('callCount', 2);
7302+
expect(gDSFP).to.have.property('callCount', 3);
7303+
const [, , thirdCall] = gDSFP.args;
7304+
expect(thirdCall).to.eql([{
7305+
changeState: false,
7306+
counter: 2,
7307+
}, {
7308+
state: -1,
7309+
}]);
7310+
expect(wrapper.state()).to.eql({ state: -1 });
7311+
});
7312+
});
7313+
7314+
it('with a state changes, calls both methods with a sync and async setProps', () => {
7315+
const wrapper = shallow(<DummyComp changeState counter={0} />);
7316+
7317+
expect(cDU).to.have.property('callCount', 0);
7318+
expect(gDSFP).to.have.property('callCount', 1);
7319+
const [firstCall] = gDSFP.args;
7320+
expect(firstCall).to.eql([{
7321+
changeState: true,
7322+
counter: 0,
7323+
}, {
7324+
state: -1,
7325+
}]);
7326+
expect(wrapper.state()).to.eql({ state: 0 });
7327+
7328+
wrapper.setProps({ counter: 1 });
7329+
7330+
expect(cDU).to.have.property('callCount', 1);
7331+
expect(gDSFP).to.have.property('callCount', 2);
7332+
const [, secondCall] = gDSFP.args;
7333+
expect(secondCall).to.eql([{
7334+
changeState: true,
7335+
counter: 1,
7336+
}, {
7337+
state: 0,
7338+
}]);
7339+
expect(wrapper.state()).to.eql({ state: 10 });
7340+
7341+
return new Promise((resolve) => {
7342+
wrapper.setProps({ counter: 2 }, resolve);
7343+
}).then(() => {
7344+
expect(cDU).to.have.property('callCount', 2);
7345+
expect(gDSFP).to.have.property('callCount', 3);
7346+
const [, , thirdCall] = gDSFP.args;
7347+
expect(thirdCall).to.eql([{
7348+
changeState: true,
7349+
counter: 2,
7350+
}, {
7351+
state: 10,
7352+
}]);
7353+
expect(wrapper.state()).to.eql({ state: 20 });
7354+
});
7355+
});
7356+
});
72387357
});
72397358

72407359
describe('Own PureComponent implementation', () => {

packages/enzyme/src/ShallowWrapper.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ class ShallowWrapper {
462462
// this case, state will be undefined, but props/context will exist.
463463
const node = this[RENDERER].getNode();
464464
const instance = node.instance || {};
465+
const type = node.type || {};
465466
const { state } = instance;
466467
const prevProps = instance.props || this[UNRENDERED].props;
467468
const prevContext = instance.context || this[OPTIONS].context;
@@ -522,7 +523,11 @@ class ShallowWrapper {
522523
if (
523524
lifecycles.componentDidUpdate
524525
&& typeof instance.componentDidUpdate === 'function'
525-
&& (!state || shallowEqual(state, this.instance().state))
526+
&& (
527+
!state
528+
|| shallowEqual(state, this.instance().state)
529+
|| typeof type.getDerivedStateFromProps === 'function'
530+
)
526531
) {
527532
instance.componentDidUpdate(prevProps, state, snapshot);
528533
}

0 commit comments

Comments
 (0)