From a70fb0533f125ebbf9e65e10d41bd43953564b8d Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Wed, 22 Aug 2018 13:35:22 +0900 Subject: [PATCH] Fix to prevent updates with PureComponent --- .../test/ReactWrapper-spec.jsx | 34 +++++++++++++++++++ .../test/ShallowWrapper-spec.jsx | 34 +++++++++++++++++++ packages/enzyme/src/ShallowWrapper.js | 11 ++++++ 3 files changed, 79 insertions(+) diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index e426c9e83..089fd445d 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -5066,6 +5066,40 @@ describeWithDOM('mount', () => { }); }); + describeIf(is('>= 15.3'), 'PureComponent', () => { + it('should not update when state and props did not change', () => { + class Foo extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + foo: 'init', + }; + } + + componentDidUpdate() {} + + render() { + return ( +
+ {this.state.foo} +
+ ); + } + } + const spy = sinon.spy(Foo.prototype, 'componentDidUpdate'); + const wrapper = mount(); + wrapper.setState({ foo: 'update' }); + expect(spy).to.have.property('callCount', 1); + wrapper.setState({ foo: 'update' }); + expect(spy).to.have.property('callCount', 1); + + wrapper.setProps({ id: 2 }); + expect(spy).to.have.property('callCount', 2); + wrapper.setProps({ id: 2 }); + expect(spy).to.have.property('callCount', 2); + }); + }); + describeIf(is('>= 16.3'), 'support getSnapshotBeforeUpdate', () => { it('should call getSnapshotBeforeUpdate and pass snapshot to componentDidUpdate', () => { const spy = sinon.spy(); diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index 93e9b2093..68ce1c4ef 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -5437,6 +5437,40 @@ describe('shallow', () => { }); }); + describeIf(is('>= 15.3'), 'PureComponent', () => { + it('should not update when state and props did not change', () => { + class Foo extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + foo: 'init', + }; + } + + componentDidUpdate() {} + + render() { + return ( +
+ {this.state.foo} +
+ ); + } + } + const spy = sinon.spy(Foo.prototype, 'componentDidUpdate'); + const wrapper = shallow(); + wrapper.setState({ foo: 'update' }); + expect(spy).to.have.property('callCount', 1); + wrapper.setState({ foo: 'update' }); + expect(spy).to.have.property('callCount', 1); + + wrapper.setProps({ id: 2 }); + expect(spy).to.have.property('callCount', 2); + wrapper.setProps({ id: 2 }); + expect(spy).to.have.property('callCount', 2); + }); + }); + describeIf(is('>= 16.3'), 'support getSnapshotBeforeUpdate', () => { it('should call getSnapshotBeforeUpdate and pass snapshot to componentDidUpdate', () => { const spy = sinon.spy(); diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index 264a54597..d191b0303 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -339,6 +339,9 @@ class ShallowWrapper { && typeof instance.shouldComponentUpdate === 'function' ) { spy = spyMethod(instance, 'shouldComponentUpdate'); + // PureComponent + } else if (instance.isPureReactComponent) { + shouldRender = !isEqual(prevProps, props) || !isEqual(state, instance.state); } if (props) this[UNRENDERED] = cloneElement(adapter, this[UNRENDERED], props); this[RENDERER].render(this[UNRENDERED], nextContext); @@ -446,6 +449,11 @@ class ShallowWrapper { const prevProps = instance.props; const prevState = instance.state; const prevContext = instance.context; + + if (typeof state === 'function') { + state = state.call(instance, prevState, prevProps); + } + // When shouldComponentUpdate returns false we shouldn't call componentDidUpdate. // so we spy shouldComponentUpdate to get the result. let spy; @@ -458,6 +466,9 @@ class ShallowWrapper { && typeof instance.shouldComponentUpdate === 'function' ) { spy = spyMethod(instance, 'shouldComponentUpdate'); + // PureComponent + } else if (instance.isPureReactComponent) { + shouldRender = !isEqual(prevProps, instance.props) || !isEqual(prevState, state); } // We don't pass the setState callback here // to guarantee to call the callback after finishing the render