Skip to content

Commit

Permalink
[Fix] shallow: Mock sCU if gDSFP defined in shallow renderer rerender
Browse files Browse the repository at this point in the history
  • Loading branch information
chenesan authored and ljharb committed Feb 28, 2019
1 parent 8d5a568 commit 98154a9
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 2 deletions.
3 changes: 2 additions & 1 deletion packages/enzyme-adapter-react-16/package.json
Expand Up @@ -40,7 +40,8 @@
"object.values": "^1.1.0",
"prop-types": "^15.7.2",
"react-is": "^16.7.0",
"react-test-renderer": "^16.0.0-0"
"react-test-renderer": "^16.0.0-0",
"semver": "^5.6.0"
},
"peerDependencies": {
"enzyme": "^3.0.0",
Expand Down
6 changes: 5 additions & 1 deletion packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Expand Up @@ -5,8 +5,10 @@ import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
// eslint-disable-next-line import/no-unresolved
import ShallowRenderer from 'react-test-renderer/shallow';
import { version as testRendererVersion } from 'react-test-renderer/package';
// eslint-disable-next-line import/no-unresolved
import TestUtils from 'react-dom/test-utils';
import semver from 'semver';
import checkPropTypes from 'prop-types/checkPropTypes';
import {
isElement,
Expand Down Expand Up @@ -52,6 +54,8 @@ const is165 = !!TestUtils.Simulate.auxClick; // 16.5+
const is166 = is165 && !React.unstable_AsyncMode; // 16.6+
const is168 = is166 && typeof TestUtils.act === 'function';

const hasShouldComponentUpdateBug = semver.satisfies(testRendererVersion, '< 16.8');

// Lazily populated if DOM is available.
let FiberTags = null;

Expand Down Expand Up @@ -303,7 +307,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
onSetState: true,
},
getDerivedStateFromProps: {
hasShouldComponentUpdateBug: !is168,
hasShouldComponentUpdateBug,
},
getSnapshotBeforeUpdate: true,
setState: {
Expand Down
37 changes: 37 additions & 0 deletions packages/enzyme/src/ShallowWrapper.js
Expand Up @@ -131,6 +131,10 @@ function getAdapterLifecycles({ options }) {
}),
}
: null;
const { getDerivedStateFromProps: originalGDSFP } = lifecycles;
const getDerivedStateFromProps = originalGDSFP ? {
hasShouldComponentUpdateBug: !!originalGDSFP.hasShouldComponentUpdateBug,
} : false;

return {
...lifecycles,
Expand All @@ -142,6 +146,7 @@ function getAdapterLifecycles({ options }) {
...lifecycles.getChildContext,
},
...(componentDidUpdate && { componentDidUpdate }),
getDerivedStateFromProps,
};
}

Expand Down Expand Up @@ -243,6 +248,30 @@ function privateSetChildContext(adapter, wrapper, instance, renderedNode, getChi
}
}

function mockSCUIfgDSFPReturnNonNull(node, state) {
const { getDerivedStateFromProps } = node.type;

if (typeof getDerivedStateFromProps === 'function') {
// we try to fix a React shallow renderer bug here.
// (facebook/react#14607, which has been fixed in react 16.8):
// when gDSFP return derived state, it will set instance state in shallow renderer before SCU,
// this will cause `this.state` in sCU be the updated state, which is wrong behavior.
// so we have to wrap sCU to pass the old state to original sCU.
const { instance } = node;
const { restore } = spyMethod(
instance,
'shouldComponentUpdate',
originalSCU => function shouldComponentUpdate(...args) {
instance.state = state;
const sCUResult = originalSCU.apply(instance, args);
const [, nextState] = args;
instance.state = nextState;
restore();
return sCUResult;
},
);
}
}

/**
* @class ShallowWrapper
Expand Down Expand Up @@ -452,6 +481,10 @@ class ShallowWrapper {
&& instance
) {
if (typeof instance.shouldComponentUpdate === 'function') {
const { getDerivedStateFromProps: gDSFP } = lifecycles;
if (gDSFP && gDSFP.hasShouldComponentUpdateBug) {
mockSCUIfgDSFPReturnNonNull(node, state);
}
shouldComponentUpdateSpy = spyMethod(instance, 'shouldComponentUpdate');
}
if (
Expand Down Expand Up @@ -601,6 +634,10 @@ class ShallowWrapper {
&& lifecycles.componentDidUpdate.onSetState
&& typeof instance.shouldComponentUpdate === 'function'
) {
const { getDerivedStateFromProps: gDSFP } = lifecycles;
if (gDSFP && gDSFP.hasShouldComponentUpdateBug) {
mockSCUIfgDSFPReturnNonNull(node, state);
}
shouldComponentUpdateSpy = spyMethod(instance, 'shouldComponentUpdate');
}
if (
Expand Down

0 comments on commit 98154a9

Please sign in to comment.