diff --git a/packages/react-test-renderer/src/ReactShallowRenderer.js b/packages/react-test-renderer/src/ReactShallowRenderer.js
index 7472556c68954..c48f705c4ff7c 100644
--- a/packages/react-test-renderer/src/ReactShallowRenderer.js
+++ b/packages/react-test-renderer/src/ReactShallowRenderer.js
@@ -8,7 +8,7 @@
*/
import React from 'react';
-import {isForwardRef} from 'react-is';
+import {isForwardRef, isMemo} from 'react-is';
import describeComponentFrame from 'shared/describeComponentFrame';
import getComponentName from 'shared/getComponentName';
import shallowEqual from 'shared/shallowEqual';
@@ -500,7 +500,8 @@ class ReactShallowRenderer {
element.type,
);
invariant(
- isForwardRef(element) || typeof element.type === 'function',
+ isForwardRef(element) ||
+ (typeof element.type === 'function' || isMemo(element.type)),
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
'components, but the provided element type was `%s`.',
Array.isArray(element.type)
@@ -514,24 +515,37 @@ class ReactShallowRenderer {
return;
}
+ const elementType = isMemo(element.type) ? element.type.type : element.type;
this._rendering = true;
+
this._element = element;
- this._context = getMaskedContext(element.type.contextTypes, context);
+ this._context = getMaskedContext(elementType.contextTypes, context);
+
+ // Inner memo component props aren't currently validated in createElement.
+ if (isMemo(element.type) && elementType.propTypes) {
+ currentlyValidatingElement = element;
+ checkPropTypes(
+ elementType.propTypes,
+ element.props,
+ 'prop',
+ getComponentName(elementType),
+ getStackAddendum,
+ );
+ }
if (this._instance) {
this._updateClassComponent(element, this._context);
} else {
if (isForwardRef(element)) {
- this._rendered = element.type.render(element.props, element.ref);
- } else if (shouldConstruct(element.type)) {
- this._instance = new element.type(
+ this._rendered = elementType.render(element.props, element.ref);
+ } else if (shouldConstruct(elementType)) {
+ this._instance = new elementType(
element.props,
this._context,
this._updater,
);
-
- if (typeof element.type.getDerivedStateFromProps === 'function') {
- const partialState = element.type.getDerivedStateFromProps.call(
+ if (typeof elementType.getDerivedStateFromProps === 'function') {
+ const partialState = elementType.getDerivedStateFromProps.call(
null,
element.props,
this._instance.state,
@@ -545,14 +559,13 @@ class ReactShallowRenderer {
}
}
- if (element.type.hasOwnProperty('contextTypes')) {
+ if (elementType.hasOwnProperty('contextTypes')) {
currentlyValidatingElement = element;
-
checkPropTypes(
- element.type.contextTypes,
+ elementType.contextTypes,
this._context,
'context',
- getName(element.type, this._instance),
+ getName(elementType, this._instance),
getStackAddendum,
);
@@ -563,13 +576,9 @@ class ReactShallowRenderer {
} else {
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = this._dispatcher;
- this._prepareToUseHooks(element.type);
+ this._prepareToUseHooks(elementType);
try {
- this._rendered = element.type.call(
- undefined,
- element.props,
- this._context,
- );
+ this._rendered = elementType(element.props, this._context);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
@@ -604,6 +613,7 @@ class ReactShallowRenderer {
this._instance.props = element.props;
this._instance.state = this._instance.state || null;
this._instance.updater = this._updater;
+ const elementType = isMemo(element.type) ? element.type.type : element.type;
if (
typeof this._instance.UNSAFE_componentWillMount === 'function' ||
@@ -614,7 +624,7 @@ class ReactShallowRenderer {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
- typeof element.type.getDerivedStateFromProps !== 'function' &&
+ typeof elementType.getDerivedStateFromProps !== 'function' &&
typeof this._instance.getSnapshotBeforeUpdate !== 'function'
) {
if (typeof this._instance.componentWillMount === 'function') {
@@ -637,16 +647,17 @@ class ReactShallowRenderer {
}
_updateClassComponent(element: ReactElement, context: null | Object) {
- const {props, type} = element;
+ const {props} = element;
const oldState = this._instance.state || emptyObject;
const oldProps = this._instance.props;
+ const elementType = isMemo(element.type) ? element.type.type : element.type;
if (oldProps !== props) {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
- typeof element.type.getDerivedStateFromProps !== 'function' &&
+ typeof elementType.getDerivedStateFromProps !== 'function' &&
typeof this._instance.getSnapshotBeforeUpdate !== 'function'
) {
if (typeof this._instance.componentWillReceiveProps === 'function') {
@@ -662,8 +673,8 @@ class ReactShallowRenderer {
// Read state after cWRP in case it calls setState
let state = this._newState || oldState;
- if (typeof type.getDerivedStateFromProps === 'function') {
- const partialState = type.getDerivedStateFromProps.call(
+ if (typeof elementType.getDerivedStateFromProps === 'function') {
+ const partialState = elementType.getDerivedStateFromProps.call(
null,
props,
state,
@@ -683,7 +694,10 @@ class ReactShallowRenderer {
state,
context,
);
- } else if (type.prototype && type.prototype.isPureReactComponent) {
+ } else if (
+ elementType.prototype &&
+ elementType.prototype.isPureReactComponent
+ ) {
shouldUpdate =
!shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
}
@@ -692,7 +706,7 @@ class ReactShallowRenderer {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
- typeof element.type.getDerivedStateFromProps !== 'function' &&
+ typeof elementType.getDerivedStateFromProps !== 'function' &&
typeof this._instance.getSnapshotBeforeUpdate !== 'function'
) {
if (typeof this._instance.componentWillUpdate === 'function') {
@@ -727,7 +741,8 @@ function getDisplayName(element) {
} else if (typeof element.type === 'string') {
return element.type;
} else {
- return element.type.displayName || element.type.name || 'Unknown';
+ const elementType = isMemo(element.type) ? element.type.type : element.type;
+ return elementType.displayName || elementType.name || 'Unknown';
}
}
diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
index d83dada160f90..e95508c8d58c2 100644
--- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
@@ -1454,4 +1454,13 @@ describe('ReactShallowRenderer', () => {
shallowRenderer.render();
expect(logs).toEqual([undefined]);
});
+
+ it('should handle memo', () => {
+ const Foo = () => {
+ return
{}}
+ onClick={this.handleUserClick}
+ className={this.state.clicked ? 'clicked' : ''}
+ />
+ );
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+ let result = shallowRenderer.getRenderOutput();
+ expect(result.type).toEqual('div');
+ expect(result.props.className).toEqual('');
+ result.props.onClick();
+
+ result = shallowRenderer.getRenderOutput();
+ expect(result.type).toEqual('div');
+ expect(result.props.className).toEqual('clicked');
+ });
+
+ it('can initialize state via static getDerivedStateFromProps', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ count: 1,
+ };
+
+ static getDerivedStateFromProps(props, prevState) {
+ return {
+ count: prevState.count + props.incrementBy,
+ other: 'foobar',
+ };
+ }
+
+ render() {
+ return (
+
{`count:${this.state.count}, other:${this.state.other}`}
+ );
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result).toEqual(
count:3, other:foobar
);
+ });
+
+ it('can setState in componentWillMount when shallow rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ UNSAFE_componentWillMount() {
+ this.setState({groovy: 'doovy'});
+ }
+
+ render() {
+ return
{this.state.groovy}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result).toEqual(
doovy
);
+ });
+
+ it('can setState in componentWillMount repeatedly when shallow rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ separator: '-',
+ };
+
+ UNSAFE_componentWillMount() {
+ this.setState({groovy: 'doovy'});
+ this.setState({doovy: 'groovy'});
+ }
+
+ render() {
+ const {groovy, doovy, separator} = this.state;
+
+ return
{`${groovy}${separator}${doovy}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result).toEqual(
doovy-groovy
);
+ });
+
+ it('can setState in componentWillMount with an updater function repeatedly when shallow rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ separator: '-',
+ };
+
+ UNSAFE_componentWillMount() {
+ this.setState(state => ({groovy: 'doovy'}));
+ this.setState(state => ({doovy: state.groovy}));
+ }
+
+ render() {
+ const {groovy, doovy, separator} = this.state;
+
+ return
{`${groovy}${separator}${doovy}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result).toEqual(
doovy-doovy
);
+ });
+
+ it('can setState in componentWillReceiveProps when shallow rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {count: 0};
+
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ if (nextProps.updateState) {
+ this.setState({count: 1});
+ }
+ }
+
+ render() {
+ return
{this.state.count}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
+
,
+ );
+ expect(result.props.children).toEqual(0);
+
+ result = shallowRenderer.render(
);
+ expect(result.props.children).toEqual(1);
+ });
+
+ it('can update state with static getDerivedStateFromProps when shallow rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {count: 1};
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.updateState) {
+ return {count: nextProps.incrementBy + prevState.count};
+ }
+
+ return null;
+ }
+
+ render() {
+ return
{this.state.count}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
+
,
+ );
+ expect(result.props.children).toEqual(1);
+
+ result = shallowRenderer.render(
+
,
+ );
+ expect(result.props.children).toEqual(3);
+
+ result = shallowRenderer.render(
+
,
+ );
+ expect(result.props.children).toEqual(3);
+ });
+
+ it('should not override state with stale values if prevState is spread within getDerivedStateFromProps', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {value: 0};
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ return {...prevState};
+ }
+
+ updateState = () => {
+ this.setState(state => ({value: state.value + 1}));
+ };
+
+ render() {
+ return
{`value:${this.state.value}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
);
+ expect(result).toEqual(
value:0
);
+
+ let instance = shallowRenderer.getMountedInstance();
+ instance.updateState();
+ result = shallowRenderer.getRenderOutput();
+ expect(result).toEqual(
value:1
);
+ });
+
+ it('should pass previous state to shouldComponentUpdate even with getDerivedStateFromProps', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ value: props.value,
+ };
+ }
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.value === prevState.value) {
+ return null;
+ }
+ return {value: nextProps.value};
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return nextState.value !== this.state.value;
+ }
+
+ render() {
+ return
{`value:${this.state.value}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const initialResult = shallowRenderer.render(
+
,
+ );
+ expect(initialResult).toEqual(
value:initial
);
+ const updatedResult = shallowRenderer.render(
+
,
+ );
+ expect(updatedResult).toEqual(
value:updated
);
+ });
+
+ it('can setState with an updater function', () => {
+ let instance;
+
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ counter: 0,
+ };
+
+ render() {
+ instance = this;
+ return (
+
+ );
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
);
+ expect(result.props.children).toEqual(0);
+
+ instance.setState((state, props) => {
+ return {counter: props.defaultCount + 1};
+ });
+
+ result = shallowRenderer.getRenderOutput();
+ expect(result.props.children).toEqual(2);
+ });
+
+ it('can access component instance from setState updater function', done => {
+ let instance;
+
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {};
+
+ render() {
+ instance = this;
+ return null;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+
+ instance.setState(function updater(state, props) {
+ expect(this).toBe(instance);
+ done();
+ });
+ });
+
+ it('can setState with a callback', () => {
+ let instance;
+
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ counter: 0,
+ };
+ render() {
+ instance = this;
+ return
{this.state.counter}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result.props.children).toBe(0);
+
+ const callback = jest.fn(function() {
+ expect(this).toBe(instance);
+ });
+
+ instance.setState({counter: 1}, callback);
+
+ const updated = shallowRenderer.getRenderOutput();
+ expect(updated.props.children).toBe(1);
+ expect(callback).toHaveBeenCalled();
+ });
+
+ it('can replaceState with a callback', () => {
+ let instance;
+
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ counter: 0,
+ };
+ render() {
+ instance = this;
+ return
{this.state.counter}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result.props.children).toBe(0);
+
+ const callback = jest.fn(function() {
+ expect(this).toBe(instance);
+ });
+
+ // No longer a public API, but we can test that it works internally by
+ // reaching into the updater.
+ shallowRenderer._updater.enqueueReplaceState(
+ instance,
+ {counter: 1},
+ callback,
+ );
+
+ const updated = shallowRenderer.getRenderOutput();
+ expect(updated.props.children).toBe(1);
+ expect(callback).toHaveBeenCalled();
+ });
+
+ it('can forceUpdate with a callback', () => {
+ let instance;
+
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ state = {
+ counter: 0,
+ };
+ render() {
+ instance = this;
+ return
{this.state.counter}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
);
+ expect(result.props.children).toBe(0);
+
+ const callback = jest.fn(function() {
+ expect(this).toBe(instance);
+ });
+
+ instance.forceUpdate(callback);
+
+ const updated = shallowRenderer.getRenderOutput();
+ expect(updated.props.children).toBe(0);
+ expect(callback).toHaveBeenCalled();
+ });
+
+ it('can pass context when shallowly rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ static contextTypes = {
+ name: PropTypes.string,
+ };
+
+ render() {
+ return
{this.context.name}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const result = shallowRenderer.render(
, {
+ name: 'foo',
+ });
+ expect(result).toEqual(
foo
);
+ });
+
+ it('should track context across updates', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ static contextTypes = {
+ foo: PropTypes.string,
+ };
+
+ state = {
+ bar: 'bar',
+ };
+
+ render() {
+ return
{`${this.context.foo}:${this.state.bar}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
, {
+ foo: 'foo',
+ });
+ expect(result).toEqual(
foo:bar
);
+
+ const instance = shallowRenderer.getMountedInstance();
+ instance.setState({bar: 'baz'});
+
+ result = shallowRenderer.getRenderOutput();
+ expect(result).toEqual(
foo:baz
);
+ });
+
+ it('should filter context by contextTypes', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ static contextTypes = {
+ foo: PropTypes.string,
+ };
+ render() {
+ return
{`${this.context.foo}:${this.context.bar}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
, {
+ foo: 'foo',
+ bar: 'bar',
+ });
+ expect(result).toEqual(
foo:undefined
);
+ });
+
+ it('can fail context when shallowly rendering', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ static contextTypes = {
+ name: PropTypes.string.isRequired,
+ };
+
+ render() {
+ return
{this.context.name}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ expect(() => shallowRenderer.render(
)).toWarnDev(
+ 'Warning: Failed context type: The context `name` is marked as ' +
+ 'required in `SimpleComponent`, but its value is `undefined`.\n' +
+ ' in SimpleComponent (at **)',
+ );
+ });
+
+ it('should warn about propTypes (but only once)', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ };
+
+ render() {
+ return React.createElement('div', null, this.props.name);
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ expect(() =>
+ shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})),
+ ).toWarnDev(
+ 'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
+ 'supplied to `SimpleComponent`, expected `string`.\n' +
+ ' in SimpleComponent',
+ );
+ });
+
+ it('should enable rendering of cloned element', () => {
+ const SimpleComponent = React.memo(
+ class SimpleComponent extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ bar: 'bar',
+ };
+ }
+
+ render() {
+ return
{`${this.props.foo}:${this.state.bar}`}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ const el =
;
+ let result = shallowRenderer.render(el);
+ expect(result).toEqual(
foo:bar
);
+
+ const cloned = React.cloneElement(el, {foo: 'baz'});
+ result = shallowRenderer.render(cloned);
+ expect(result).toEqual(
baz:bar
);
+ });
+
+ it('this.state should be updated on setState callback inside componentWillMount', () => {
+ let stateSuccessfullyUpdated = false;
+
+ const Component = React.memo(
+ class Component extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ hasUpdatedState: false,
+ };
+ }
+
+ UNSAFE_componentWillMount() {
+ this.setState(
+ {hasUpdatedState: true},
+ () => (stateSuccessfullyUpdated = this.state.hasUpdatedState),
+ );
+ }
+
+ render() {
+ return
{this.props.children}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+ expect(stateSuccessfullyUpdated).toBe(true);
+ });
+
+ it('should handle multiple callbacks', () => {
+ const mockFn = jest.fn();
+ const shallowRenderer = createRenderer();
+
+ const Component = React.memo(
+ class Component extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ foo: 'foo',
+ };
+ }
+
+ UNSAFE_componentWillMount() {
+ this.setState({foo: 'bar'}, () => mockFn());
+ this.setState({foo: 'foobar'}, () => mockFn());
+ }
+
+ render() {
+ return
{this.state.foo}
;
+ }
+ },
+ );
+
+ shallowRenderer.render(
);
+
+ expect(mockFn).toHaveBeenCalledTimes(2);
+
+ // Ensure the callback queue is cleared after the callbacks are invoked
+ const mountedInstance = shallowRenderer.getMountedInstance();
+ mountedInstance.setState({foo: 'bar'}, () => mockFn());
+ expect(mockFn).toHaveBeenCalledTimes(3);
+ });
+
+ it('should call the setState callback even if shouldComponentUpdate = false', done => {
+ const mockFn = jest.fn().mockReturnValue(false);
+
+ const Component = React.memo(
+ class Component extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ hasUpdatedState: false,
+ };
+ }
+
+ shouldComponentUpdate() {
+ return mockFn();
+ }
+
+ render() {
+ return
{this.state.hasUpdatedState}
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+
+ const mountedInstance = shallowRenderer.getMountedInstance();
+ mountedInstance.setState({hasUpdatedState: true}, () => {
+ expect(mockFn).toBeCalled();
+ expect(mountedInstance.state.hasUpdatedState).toBe(true);
+ done();
+ });
+ });
+
+ it('throws usefully when rendering badly-typed elements', () => {
+ const shallowRenderer = createRenderer();
+
+ const renderAndVerifyWarningAndError = (Component, typeString) => {
+ expect(() => {
+ expect(() => shallowRenderer.render(
)).toWarnDev(
+ 'React.createElement: type is invalid -- expected a string ' +
+ '(for built-in components) or a class/function (for composite components) ' +
+ `but got: ${typeString}.`,
+ );
+ }).toThrowError(
+ 'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
+ `components, but the provided element type was \`${typeString}\`.`,
+ );
+ };
+
+ renderAndVerifyWarningAndError(undefined, 'undefined');
+ renderAndVerifyWarningAndError(null, 'null');
+ renderAndVerifyWarningAndError([], 'array');
+ renderAndVerifyWarningAndError({}, 'object');
+ });
+
+ it('should have initial state of null if not defined', () => {
+ const SomeComponent = React.memo(
+ class SomeComponent extends React.Component {
+ render() {
+ return
;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+
+ expect(shallowRenderer.getMountedInstance().state).toBeNull();
+ });
+
+ it('should invoke both deprecated and new lifecycles if both are present', () => {
+ const log = [];
+
+ const Component = React.memo(
+ class Component extends React.Component {
+ componentWillMount() {
+ log.push('componentWillMount');
+ }
+ componentWillReceiveProps() {
+ log.push('componentWillReceiveProps');
+ }
+ componentWillUpdate() {
+ log.push('componentWillUpdate');
+ }
+ UNSAFE_componentWillMount() {
+ log.push('UNSAFE_componentWillMount');
+ }
+ UNSAFE_componentWillReceiveProps() {
+ log.push('UNSAFE_componentWillReceiveProps');
+ }
+ UNSAFE_componentWillUpdate() {
+ log.push('UNSAFE_componentWillUpdate');
+ }
+ render() {
+ return null;
+ }
+ },
+ );
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+ expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']);
+
+ log.length = 0;
+
+ shallowRenderer.render(
);
+ expect(log).toEqual([
+ 'componentWillReceiveProps',
+ 'UNSAFE_componentWillReceiveProps',
+ 'componentWillUpdate',
+ 'UNSAFE_componentWillUpdate',
+ ]);
+ });
+
+ it('should stop the update when setState returns null or undefined', () => {
+ const log = [];
+ let instance;
+ const Component = React.memo(
+ class Component extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ count: 0,
+ };
+ }
+ render() {
+ log.push('render');
+ instance = this;
+ return null;
+ }
+ },
+ );
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+ log.length = 0;
+ instance.setState(() => null);
+ instance.setState(() => undefined);
+ instance.setState(null);
+ instance.setState(undefined);
+ expect(log).toEqual([]);
+ instance.setState(state => ({count: state.count + 1}));
+ expect(log).toEqual(['render']);
+ });
+
+ it('should not get this in a function component', () => {
+ const logs = [];
+ const Foo = React.memo(function Foo() {
+ logs.push(this);
+ return
foo
;
+ });
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render(
);
+ expect(logs).toEqual([undefined]);
+ });
+});