diff --git a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js index 37edd3520..f9c461498 100644 --- a/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js +++ b/packages/enzyme-adapter-react-13/src/ReactThirteenAdapter.js @@ -99,9 +99,17 @@ function instanceToTree(inst) { class ReactThirteenAdapter extends EnzymeAdapter { constructor() { super(); + + const { lifecycles } = this.options; this.options = { ...this.options, - supportPrevContextArgumentOfComponentDidUpdate: true, + supportPrevContextArgumentOfComponentDidUpdate: true, // TODO: remove, semver-major + lifecycles: { + ...lifecycles, + componentDidUpdate: { + prevContext: true, + }, + }, }; } createMountRenderer(options) { diff --git a/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js b/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js index b6454613a..98a2694be 100644 --- a/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js +++ b/packages/enzyme-adapter-react-14/src/ReactFourteenAdapter.js @@ -72,9 +72,17 @@ function instanceToTree(inst) { class ReactFourteenAdapter extends EnzymeAdapter { constructor() { super(); + + const { lifecycles } = this.options; this.options = { ...this.options, - supportPrevContextArgumentOfComponentDidUpdate: true, + supportPrevContextArgumentOfComponentDidUpdate: true, // TODO: remove, semver-major + lifecycles: { + ...lifecycles, + componentDidUpdate: { + prevContext: true, + }, + }, }; } createMountRenderer(options) { diff --git a/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js b/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js index a26078a01..c539ee7f4 100644 --- a/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js +++ b/packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js @@ -103,9 +103,17 @@ function instanceToTree(inst) { class ReactFifteenFourAdapter extends EnzymeAdapter { constructor() { super(); + + const { lifecycles } = this.options; this.options = { ...this.options, - supportPrevContextArgumentOfComponentDidUpdate: true, + supportPrevContextArgumentOfComponentDidUpdate: true, // TODO: remove, semver-major + lifecycles: { + ...lifecycles, + componentDidUpdate: { + prevContext: true, + }, + }, }; } createMountRenderer(options) { diff --git a/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js b/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js index d1704e784..5195c93fc 100644 --- a/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js +++ b/packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js @@ -103,9 +103,17 @@ function instanceToTree(inst) { class ReactFifteenAdapter extends EnzymeAdapter { constructor() { super(); + + const { lifecycles } = this.options; this.options = { ...this.options, - supportPrevContextArgumentOfComponentDidUpdate: true, + supportPrevContextArgumentOfComponentDidUpdate: true, // TODO: remove, semver-major + lifecycles: { + ...lifecycles, + componentDidUpdate: { + prevContext: true, + }, + }, }; } createMountRenderer(options) { diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index 3fe9b07f6..01c9930d2 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -160,10 +160,17 @@ function nodeToHostNode(_node) { class ReactSixteenAdapter extends EnzymeAdapter { constructor() { super(); + const { lifecycles } = this.options; this.options = { ...this.options, - enableComponentDidUpdateOnSetState: true, - supportGetSnapshotBeforeUpdate: true, + enableComponentDidUpdateOnSetState: true, // TODO: remove, semver-major + lifecycles: { + ...lifecycles, + componentDidUpdate: { + onSetState: true, + }, + getSnapshotBeforeUpdate: true, + }, }; } createMountRenderer(options) { diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index ce3c1e9bd..f87b8c03f 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -66,7 +66,13 @@ function filterWhereUnwrapped(wrapper, predicate) { * @param {Object} options */ function validateOptions(options) { - const { lifecycleExperimental, disableLifecycleMethods } = options; + const { + lifecycleExperimental, + disableLifecycleMethods, + enableComponentDidUpdateOnSetState, + supportPrevContextArgumentOfComponentDidUpdate, + lifecycles, + } = options; if ( typeof lifecycleExperimental !== 'undefined' && typeof lifecycleExperimental !== 'boolean' @@ -88,6 +94,48 @@ function validateOptions(options) { ) { throw new Error('lifecycleExperimental and disableLifecycleMethods cannot be set to the same value'); } + + if ( + typeof enableComponentDidUpdateOnSetState !== 'undefined' && + lifecycles.componentDidUpdate && + lifecycles.componentDidUpdate.onSetState !== enableComponentDidUpdateOnSetState + ) { + throw new TypeError('the legacy enableComponentDidUpdateOnSetState option should be matched by `lifecycles: { componentDidUpdate: { onSetState: true } }`, for compatibility'); + } + + if ( + typeof supportPrevContextArgumentOfComponentDidUpdate !== 'undefined' && + lifecycles.componentDidUpdate && + lifecycles.componentDidUpdate.prevContext !== supportPrevContextArgumentOfComponentDidUpdate + ) { + throw new TypeError('the legacy supportPrevContextArgumentOfComponentDidUpdate option should be matched by `lifecycles: { componentDidUpdate: { prevContext: true } }`, for compatibility'); + } +} + +function getAdapterLifecycles({ options }) { + const { + lifecycles = {}, + enableComponentDidUpdateOnSetState, + supportPrevContextArgumentOfComponentDidUpdate, + } = options; + + const hasLegacySetStateArg = typeof enableComponentDidUpdateOnSetState !== 'undefined'; + const hasLegacyPrevContextArg = typeof supportPrevContextArgumentOfComponentDidUpdate !== 'undefined'; + const componentDidUpdate = hasLegacySetStateArg || hasLegacyPrevContextArg + ? { + ...(hasLegacySetStateArg && { + onSetState: !!enableComponentDidUpdateOnSetState, + }), + ...(hasLegacyPrevContextArg && { + prevContext: !!supportPrevContextArgumentOfComponentDidUpdate, + }), + } + : null; + + return { + ...lifecycles, + ...(componentDidUpdate && { componentDidUpdate }), + }; } function getRootNode(node) { @@ -302,12 +350,13 @@ class ShallowWrapper { if (originalShouldComponentUpdate) { instance.shouldComponentUpdate = originalShouldComponentUpdate; } + const lifecycles = getAdapterLifecycles(adapter); if ( !this[OPTIONS].disableLifecycleMethods && instance ) { if ( - adapter.options.supportGetSnapshotBeforeUpdate + lifecycles.getSnapshotBeforeUpdate && typeof instance.getSnapshotBeforeUpdate === 'function' ) { const snapshot = instance.getSnapshotBeforeUpdate(prevProps, state); @@ -315,7 +364,10 @@ class ShallowWrapper { instance.componentDidUpdate(prevProps, state, snapshot); } } else if (typeof instance.componentDidUpdate === 'function') { - if (adapter.options.supportPrevContextArgumentOfComponentDidUpdate) { + if ( + lifecycles.componentDidUpdate && + lifecycles.componentDidUpdate.prevContext + ) { instance.componentDidUpdate(prevProps, state, prevContext); } else { instance.componentDidUpdate(prevProps, state); @@ -379,6 +431,9 @@ class ShallowWrapper { this.single('setState', () => { withSetStateAllowed(() => { const adapter = getAdapter(this[OPTIONS]); + + const lifecycles = getAdapterLifecycles(adapter); + const instance = this.instance(); const prevProps = instance.props; const prevState = instance.state; @@ -391,7 +446,8 @@ class ShallowWrapper { let originalShouldComponentUpdate; if ( !this[OPTIONS].disableLifecycleMethods && - adapter.options.enableComponentDidUpdateOnSetState && + lifecycles.componentDidUpdate && + lifecycles.componentDidUpdate.onSetState && instance && typeof instance.shouldComponentUpdate === 'function' ) { @@ -405,14 +461,16 @@ class ShallowWrapper { // We don't pass the setState callback here // to guarantee to call the callback after finishing the render instance.setState(state); + if ( shouldRender && !this[OPTIONS].disableLifecycleMethods && - adapter.options.enableComponentDidUpdateOnSetState && + lifecycles.componentDidUpdate && + lifecycles.componentDidUpdate.onSetState && instance ) { if ( - adapter.options.supportGetSnapshotBeforeUpdate && + lifecycles.getSnapshotBeforeUpdate && typeof instance.getSnapshotBeforeUpdate === 'function' ) { const snapshot = instance.getSnapshotBeforeUpdate(prevProps, prevState); @@ -420,7 +478,7 @@ class ShallowWrapper { instance.componentDidUpdate(prevProps, prevState, snapshot); } } else if (typeof instance.componentDidUpdate === 'function') { - if (adapter.options.supportPrevContextArgumentOfComponentDidUpdate) { + if (lifecycles.componentDidUpdate.prevContext) { instance.componentDidUpdate(prevProps, prevState, prevContext); } else { instance.componentDidUpdate(prevProps, prevState);