diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index f397cd455..c401f312d 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -146,6 +146,13 @@ function nodeToHostNode(_node) { } class ReactSixteenAdapter extends EnzymeAdapter { + constructor() { + super(); + this.options = { + ...this.options, + enableComponentDidUpdateOnSetState: true, + }; + } createMountRenderer(options) { assertDomAvailable('mount'); const domNode = options.attachTo || global.document.createElement('div'); diff --git a/packages/enzyme-test-suite/package.json b/packages/enzyme-test-suite/package.json index 95f8af3e6..070a254cc 100644 --- a/packages/enzyme-test-suite/package.json +++ b/packages/enzyme-test-suite/package.json @@ -41,3 +41,4 @@ "react-dom": "^15.5.0" } } + diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index b0b547a86..306910ac2 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -3441,8 +3441,7 @@ describe('shallow', () => { context('updating state', () => { // NOTE: There is a bug in react 16 shallow renderer where prevContext is not passed - // into componentDidUpdate. Skip this test for react 16 only. add back in if it gets fixed. - itIf(!REACT16, 'should call shouldComponentUpdate, componentWillUpdate and componentDidUpdate', () => { + it('should call shouldComponentUpdate, componentWillUpdate and componentDidUpdate', () => { const spy = sinon.spy(); class Foo extends React.Component { diff --git a/packages/enzyme/src/EnzymeAdapter.js b/packages/enzyme/src/EnzymeAdapter.js index 76122a50b..f323c98be 100644 --- a/packages/enzyme/src/EnzymeAdapter.js +++ b/packages/enzyme/src/EnzymeAdapter.js @@ -5,6 +5,9 @@ function unimplementedError(methodName, classname) { } class EnzymeAdapter { + constructor() { + this.options = {}; + } // Provided a bag of options, return an `EnzymeRenderer`. Some options can be implementation // specific, like `attach` etc. for React, but not part of this interface explicitly. // eslint-disable-next-line class-methods-use-this, no-unused-vars diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index 7010982f1..d18da5e37 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -369,7 +369,40 @@ class ShallowWrapper { } this.single('setState', () => { withSetStateAllowed(() => { - this.instance().setState(state, callback); + const adapter = getAdapter(this[OPTIONS]); + const instance = this.instance(); + const prevProps = instance.props; + const prevState = instance.state; + const prevContext = instance.context; + let shouldRender = true; + // This is a dirty hack but it requires to know the result of shouldComponentUpdate. + // When shouldComponentUpdate returns false we shouldn't call componentDidUpdate. + // shouldComponentUpdate is called in `instance.setState` + // so we replace shouldComponentUpdate to know the result and restore it later. + let originalShouldComponentUpdate; + if ( + this[OPTIONS].lifecycleExperimental && + adapter.options.enableComponentDidUpdateOnSetState && + instance && + typeof instance.shouldComponentUpdate === 'function' + ) { + originalShouldComponentUpdate = instance.shouldComponentUpdate; + instance.shouldComponentUpdate = (...args) => { + shouldRender = originalShouldComponentUpdate.apply(instance, args); + instance.shouldComponentUpdate = originalShouldComponentUpdate; + return shouldRender; + }; + } + instance.setState(state, callback); + if ( + shouldRender && + this[OPTIONS].lifecycleExperimental && + adapter.options.enableComponentDidUpdateOnSetState && + instance && + typeof instance.componentDidUpdate === 'function' + ) { + instance.componentDidUpdate(prevProps, prevState, prevContext); + } this.update(); }); });