1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. let React;
    
  13. let ReactDOM;
    
  14. let ReactTestUtils;
    
  15. let PropTypes;
    
  16. 
    
  17. const clone = function (o) {
    
  18.   return JSON.parse(JSON.stringify(o));
    
  19. };
    
  20. 
    
  21. const GET_INIT_STATE_RETURN_VAL = {
    
  22.   hasWillMountCompleted: false,
    
  23.   hasRenderCompleted: false,
    
  24.   hasDidMountCompleted: false,
    
  25.   hasWillUnmountCompleted: false,
    
  26. };
    
  27. 
    
  28. const INIT_RENDER_STATE = {
    
  29.   hasWillMountCompleted: true,
    
  30.   hasRenderCompleted: false,
    
  31.   hasDidMountCompleted: false,
    
  32.   hasWillUnmountCompleted: false,
    
  33. };
    
  34. 
    
  35. const DID_MOUNT_STATE = {
    
  36.   hasWillMountCompleted: true,
    
  37.   hasRenderCompleted: true,
    
  38.   hasDidMountCompleted: false,
    
  39.   hasWillUnmountCompleted: false,
    
  40. };
    
  41. 
    
  42. const NEXT_RENDER_STATE = {
    
  43.   hasWillMountCompleted: true,
    
  44.   hasRenderCompleted: true,
    
  45.   hasDidMountCompleted: true,
    
  46.   hasWillUnmountCompleted: false,
    
  47. };
    
  48. 
    
  49. const WILL_UNMOUNT_STATE = {
    
  50.   hasWillMountCompleted: true,
    
  51.   hasDidMountCompleted: true,
    
  52.   hasRenderCompleted: true,
    
  53.   hasWillUnmountCompleted: false,
    
  54. };
    
  55. 
    
  56. const POST_WILL_UNMOUNT_STATE = {
    
  57.   hasWillMountCompleted: true,
    
  58.   hasDidMountCompleted: true,
    
  59.   hasRenderCompleted: true,
    
  60.   hasWillUnmountCompleted: true,
    
  61. };
    
  62. 
    
  63. /**
    
  64.  * Every React component is in one of these life cycles.
    
  65.  */
    
  66. type ComponentLifeCycle =
    
  67.   /**
    
  68.    * Mounted components have a DOM node representation and are capable of
    
  69.    * receiving new props.
    
  70.    */
    
  71.   | 'MOUNTED'
    
  72.   /**
    
  73.    * Unmounted components are inactive and cannot receive new props.
    
  74.    */
    
  75.   | 'UNMOUNTED';
    
  76. 
    
  77. function getLifeCycleState(instance): ComponentLifeCycle {
    
  78.   return instance.updater.isMounted(instance) ? 'MOUNTED' : 'UNMOUNTED';
    
  79. }
    
  80. 
    
  81. /**
    
  82.  * TODO: We should make any setState calls fail in
    
  83.  * `getInitialState` and `componentWillMount`. They will usually fail
    
  84.  * anyways because `this._renderedComponent` is empty, however, if a component
    
  85.  * is *reused*, then that won't be the case and things will appear to work in
    
  86.  * some cases. Better to just block all updates in initialization.
    
  87.  */
    
  88. describe('ReactComponentLifeCycle', () => {
    
  89.   beforeEach(() => {
    
  90.     jest.resetModules();
    
  91.     React = require('react');
    
  92.     ReactDOM = require('react-dom');
    
  93.     ReactTestUtils = require('react-dom/test-utils');
    
  94.     PropTypes = require('prop-types');
    
  95.   });
    
  96. 
    
  97.   it('should not reuse an instance when it has been unmounted', () => {
    
  98.     const container = document.createElement('div');
    
  99. 
    
  100.     class StatefulComponent extends React.Component {
    
  101.       state = {};
    
  102. 
    
  103.       render() {
    
  104.         return <div />;
    
  105.       }
    
  106.     }
    
  107. 
    
  108.     const element = <StatefulComponent />;
    
  109.     const firstInstance = ReactDOM.render(element, container);
    
  110.     ReactDOM.unmountComponentAtNode(container);
    
  111.     const secondInstance = ReactDOM.render(element, container);
    
  112.     expect(firstInstance).not.toBe(secondInstance);
    
  113.   });
    
  114. 
    
  115.   /**
    
  116.    * If a state update triggers rerendering that in turn fires an onDOMReady,
    
  117.    * that second onDOMReady should not fail.
    
  118.    */
    
  119.   it('it should fire onDOMReady when already in onDOMReady', () => {
    
  120.     const _testJournal = [];
    
  121. 
    
  122.     class Child extends React.Component {
    
  123.       componentDidMount() {
    
  124.         _testJournal.push('Child:onDOMReady');
    
  125.       }
    
  126. 
    
  127.       render() {
    
  128.         return <div />;
    
  129.       }
    
  130.     }
    
  131. 
    
  132.     class SwitcherParent extends React.Component {
    
  133.       constructor(props) {
    
  134.         super(props);
    
  135.         _testJournal.push('SwitcherParent:getInitialState');
    
  136.         this.state = {showHasOnDOMReadyComponent: false};
    
  137.       }
    
  138. 
    
  139.       componentDidMount() {
    
  140.         _testJournal.push('SwitcherParent:onDOMReady');
    
  141.         this.switchIt();
    
  142.       }
    
  143. 
    
  144.       switchIt = () => {
    
  145.         this.setState({showHasOnDOMReadyComponent: true});
    
  146.       };
    
  147. 
    
  148.       render() {
    
  149.         return (
    
  150.           <div>
    
  151.             {this.state.showHasOnDOMReadyComponent ? <Child /> : <div />}
    
  152.           </div>
    
  153.         );
    
  154.       }
    
  155.     }
    
  156. 
    
  157.     ReactTestUtils.renderIntoDocument(<SwitcherParent />);
    
  158.     expect(_testJournal).toEqual([
    
  159.       'SwitcherParent:getInitialState',
    
  160.       'SwitcherParent:onDOMReady',
    
  161.       'Child:onDOMReady',
    
  162.     ]);
    
  163.   });
    
  164. 
    
  165.   // You could assign state here, but not access members of it, unless you
    
  166.   // had provided a getInitialState method.
    
  167.   it('throws when accessing state in componentWillMount', () => {
    
  168.     class StatefulComponent extends React.Component {
    
  169.       UNSAFE_componentWillMount() {
    
  170.         void this.state.yada;
    
  171.       }
    
  172. 
    
  173.       render() {
    
  174.         return <div />;
    
  175.       }
    
  176.     }
    
  177. 
    
  178.     let instance = <StatefulComponent />;
    
  179.     expect(function () {
    
  180.       instance = ReactTestUtils.renderIntoDocument(instance);
    
  181.     }).toThrow();
    
  182.   });
    
  183. 
    
  184.   it('should allow update state inside of componentWillMount', () => {
    
  185.     class StatefulComponent extends React.Component {
    
  186.       UNSAFE_componentWillMount() {
    
  187.         this.setState({stateField: 'something'});
    
  188.       }
    
  189. 
    
  190.       render() {
    
  191.         return <div />;
    
  192.       }
    
  193.     }
    
  194. 
    
  195.     let instance = <StatefulComponent />;
    
  196.     expect(function () {
    
  197.       instance = ReactTestUtils.renderIntoDocument(instance);
    
  198.     }).not.toThrow();
    
  199.   });
    
  200. 
    
  201.   it("warns if setting 'this.state = props'", () => {
    
  202.     class StatefulComponent extends React.Component {
    
  203.       constructor(props, context) {
    
  204.         super(props, context);
    
  205.         this.state = props;
    
  206.       }
    
  207.       render() {
    
  208.         return <div />;
    
  209.       }
    
  210.     }
    
  211. 
    
  212.     expect(() => {
    
  213.       ReactTestUtils.renderIntoDocument(<StatefulComponent />);
    
  214.     }).toErrorDev(
    
  215.       'StatefulComponent: It is not recommended to assign props directly to state ' +
    
  216.         "because updates to props won't be reflected in state. " +
    
  217.         'In most cases, it is better to use props directly.',
    
  218.     );
    
  219.   });
    
  220. 
    
  221.   it('should not allow update state inside of getInitialState', () => {
    
  222.     class StatefulComponent extends React.Component {
    
  223.       constructor(props, context) {
    
  224.         super(props, context);
    
  225.         this.setState({stateField: 'something'});
    
  226. 
    
  227.         this.state = {stateField: 'somethingelse'};
    
  228.       }
    
  229. 
    
  230.       render() {
    
  231.         return <div />;
    
  232.       }
    
  233.     }
    
  234. 
    
  235.     expect(() => {
    
  236.       ReactTestUtils.renderIntoDocument(<StatefulComponent />);
    
  237.     }).toErrorDev(
    
  238.       "Warning: Can't call setState on a component that is not yet mounted. " +
    
  239.         'This is a no-op, but it might indicate a bug in your application. ' +
    
  240.         'Instead, assign to `this.state` directly or define a `state = {};` ' +
    
  241.         'class property with the desired state in the StatefulComponent component.',
    
  242.     );
    
  243. 
    
  244.     // Check deduplication; (no extra warnings should be logged).
    
  245.     ReactTestUtils.renderIntoDocument(<StatefulComponent />);
    
  246.   });
    
  247. 
    
  248.   it('should correctly determine if a component is mounted', () => {
    
  249.     class Component extends React.Component {
    
  250.       _isMounted() {
    
  251.         // No longer a public API, but we can test that it works internally by
    
  252.         // reaching into the updater.
    
  253.         return this.updater.isMounted(this);
    
  254.       }
    
  255.       UNSAFE_componentWillMount() {
    
  256.         expect(this._isMounted()).toBeFalsy();
    
  257.       }
    
  258.       componentDidMount() {
    
  259.         expect(this._isMounted()).toBeTruthy();
    
  260.       }
    
  261.       render() {
    
  262.         expect(this._isMounted()).toBeFalsy();
    
  263.         return <div />;
    
  264.       }
    
  265.     }
    
  266. 
    
  267.     const element = <Component />;
    
  268. 
    
  269.     expect(() => {
    
  270.       const instance = ReactTestUtils.renderIntoDocument(element);
    
  271.       expect(instance._isMounted()).toBeTruthy();
    
  272.     }).toErrorDev('Component is accessing isMounted inside its render()');
    
  273.   });
    
  274. 
    
  275.   it('should correctly determine if a null component is mounted', () => {
    
  276.     class Component extends React.Component {
    
  277.       _isMounted() {
    
  278.         // No longer a public API, but we can test that it works internally by
    
  279.         // reaching into the updater.
    
  280.         return this.updater.isMounted(this);
    
  281.       }
    
  282.       UNSAFE_componentWillMount() {
    
  283.         expect(this._isMounted()).toBeFalsy();
    
  284.       }
    
  285.       componentDidMount() {
    
  286.         expect(this._isMounted()).toBeTruthy();
    
  287.       }
    
  288.       render() {
    
  289.         expect(this._isMounted()).toBeFalsy();
    
  290.         return null;
    
  291.       }
    
  292.     }
    
  293. 
    
  294.     const element = <Component />;
    
  295. 
    
  296.     expect(() => {
    
  297.       const instance = ReactTestUtils.renderIntoDocument(element);
    
  298.       expect(instance._isMounted()).toBeTruthy();
    
  299.     }).toErrorDev('Component is accessing isMounted inside its render()');
    
  300.   });
    
  301. 
    
  302.   it('isMounted should return false when unmounted', () => {
    
  303.     class Component extends React.Component {
    
  304.       render() {
    
  305.         return <div />;
    
  306.       }
    
  307.     }
    
  308. 
    
  309.     const container = document.createElement('div');
    
  310.     const instance = ReactDOM.render(<Component />, container);
    
  311. 
    
  312.     // No longer a public API, but we can test that it works internally by
    
  313.     // reaching into the updater.
    
  314.     expect(instance.updater.isMounted(instance)).toBe(true);
    
  315. 
    
  316.     ReactDOM.unmountComponentAtNode(container);
    
  317. 
    
  318.     expect(instance.updater.isMounted(instance)).toBe(false);
    
  319.   });
    
  320. 
    
  321.   it('warns if findDOMNode is used inside render', () => {
    
  322.     class Component extends React.Component {
    
  323.       state = {isMounted: false};
    
  324.       componentDidMount() {
    
  325.         this.setState({isMounted: true});
    
  326.       }
    
  327.       render() {
    
  328.         if (this.state.isMounted) {
    
  329.           expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
    
  330.         }
    
  331.         return <div />;
    
  332.       }
    
  333.     }
    
  334. 
    
  335.     expect(() => {
    
  336.       ReactTestUtils.renderIntoDocument(<Component />);
    
  337.     }).toErrorDev('Component is accessing findDOMNode inside its render()');
    
  338.   });
    
  339. 
    
  340.   it('should carry through each of the phases of setup', () => {
    
  341.     class LifeCycleComponent extends React.Component {
    
  342.       constructor(props, context) {
    
  343.         super(props, context);
    
  344.         this._testJournal = {};
    
  345.         const initState = {
    
  346.           hasWillMountCompleted: false,
    
  347.           hasDidMountCompleted: false,
    
  348.           hasRenderCompleted: false,
    
  349.           hasWillUnmountCompleted: false,
    
  350.         };
    
  351.         this._testJournal.returnedFromGetInitialState = clone(initState);
    
  352.         this._testJournal.lifeCycleAtStartOfGetInitialState =
    
  353.           getLifeCycleState(this);
    
  354.         this.state = initState;
    
  355.       }
    
  356. 
    
  357.       UNSAFE_componentWillMount() {
    
  358.         this._testJournal.stateAtStartOfWillMount = clone(this.state);
    
  359.         this._testJournal.lifeCycleAtStartOfWillMount = getLifeCycleState(this);
    
  360.         this.state.hasWillMountCompleted = true;
    
  361.       }
    
  362. 
    
  363.       componentDidMount() {
    
  364.         this._testJournal.stateAtStartOfDidMount = clone(this.state);
    
  365.         this._testJournal.lifeCycleAtStartOfDidMount = getLifeCycleState(this);
    
  366.         this.setState({hasDidMountCompleted: true});
    
  367.       }
    
  368. 
    
  369.       render() {
    
  370.         const isInitialRender = !this.state.hasRenderCompleted;
    
  371.         if (isInitialRender) {
    
  372.           this._testJournal.stateInInitialRender = clone(this.state);
    
  373.           this._testJournal.lifeCycleInInitialRender = getLifeCycleState(this);
    
  374.         } else {
    
  375.           this._testJournal.stateInLaterRender = clone(this.state);
    
  376.           this._testJournal.lifeCycleInLaterRender = getLifeCycleState(this);
    
  377.         }
    
  378.         // you would *NEVER* do anything like this in real code!
    
  379.         this.state.hasRenderCompleted = true;
    
  380.         return <div ref={React.createRef()}>I am the inner DIV</div>;
    
  381.       }
    
  382. 
    
  383.       componentWillUnmount() {
    
  384.         this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
    
  385.         this._testJournal.lifeCycleAtStartOfWillUnmount =
    
  386.           getLifeCycleState(this);
    
  387.         this.state.hasWillUnmountCompleted = true;
    
  388.       }
    
  389.     }
    
  390. 
    
  391.     // A component that is merely "constructed" (as in "constructor") but not
    
  392.     // yet initialized, or rendered.
    
  393.     //
    
  394.     const container = document.createElement('div');
    
  395. 
    
  396.     let instance;
    
  397.     expect(() => {
    
  398.       instance = ReactDOM.render(<LifeCycleComponent />, container);
    
  399.     }).toErrorDev(
    
  400.       'LifeCycleComponent is accessing isMounted inside its render() function',
    
  401.     );
    
  402. 
    
  403.     // getInitialState
    
  404.     expect(instance._testJournal.returnedFromGetInitialState).toEqual(
    
  405.       GET_INIT_STATE_RETURN_VAL,
    
  406.     );
    
  407.     expect(instance._testJournal.lifeCycleAtStartOfGetInitialState).toBe(
    
  408.       'UNMOUNTED',
    
  409.     );
    
  410. 
    
  411.     // componentWillMount
    
  412.     expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
    
  413.       instance._testJournal.returnedFromGetInitialState,
    
  414.     );
    
  415.     expect(instance._testJournal.lifeCycleAtStartOfWillMount).toBe('UNMOUNTED');
    
  416. 
    
  417.     // componentDidMount
    
  418.     expect(instance._testJournal.stateAtStartOfDidMount).toEqual(
    
  419.       DID_MOUNT_STATE,
    
  420.     );
    
  421.     expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe('MOUNTED');
    
  422. 
    
  423.     // initial render
    
  424.     expect(instance._testJournal.stateInInitialRender).toEqual(
    
  425.       INIT_RENDER_STATE,
    
  426.     );
    
  427.     expect(instance._testJournal.lifeCycleInInitialRender).toBe('UNMOUNTED');
    
  428. 
    
  429.     expect(getLifeCycleState(instance)).toBe('MOUNTED');
    
  430. 
    
  431.     // Now *update the component*
    
  432.     instance.forceUpdate();
    
  433. 
    
  434.     // render 2nd time
    
  435.     expect(instance._testJournal.stateInLaterRender).toEqual(NEXT_RENDER_STATE);
    
  436.     expect(instance._testJournal.lifeCycleInLaterRender).toBe('MOUNTED');
    
  437. 
    
  438.     expect(getLifeCycleState(instance)).toBe('MOUNTED');
    
  439. 
    
  440.     ReactDOM.unmountComponentAtNode(container);
    
  441. 
    
  442.     expect(instance._testJournal.stateAtStartOfWillUnmount).toEqual(
    
  443.       WILL_UNMOUNT_STATE,
    
  444.     );
    
  445.     // componentWillUnmount called right before unmount.
    
  446.     expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe('MOUNTED');
    
  447. 
    
  448.     // But the current lifecycle of the component is unmounted.
    
  449.     expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
    
  450.     expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
    
  451.   });
    
  452. 
    
  453.   it('should not throw when updating an auxiliary component', () => {
    
  454.     class Tooltip extends React.Component {
    
  455.       render() {
    
  456.         return <div>{this.props.children}</div>;
    
  457.       }
    
  458. 
    
  459.       componentDidMount() {
    
  460.         this.container = document.createElement('div');
    
  461.         this.updateTooltip();
    
  462.       }
    
  463. 
    
  464.       componentDidUpdate() {
    
  465.         this.updateTooltip();
    
  466.       }
    
  467. 
    
  468.       updateTooltip = () => {
    
  469.         // Even though this.props.tooltip has an owner, updating it shouldn't
    
  470.         // throw here because it's mounted as a root component
    
  471.         ReactDOM.render(this.props.tooltip, this.container);
    
  472.       };
    
  473.     }
    
  474. 
    
  475.     class Component extends React.Component {
    
  476.       render() {
    
  477.         return (
    
  478.           <Tooltip
    
  479.             ref={React.createRef()}
    
  480.             tooltip={<div>{this.props.tooltipText}</div>}>
    
  481.             {this.props.text}
    
  482.           </Tooltip>
    
  483.         );
    
  484.       }
    
  485.     }
    
  486. 
    
  487.     const container = document.createElement('div');
    
  488.     ReactDOM.render(<Component text="uno" tooltipText="one" />, container);
    
  489. 
    
  490.     // Since `instance` is a root component, we can set its props. This also
    
  491.     // makes Tooltip rerender the tooltip component, which shouldn't throw.
    
  492.     ReactDOM.render(<Component text="dos" tooltipText="two" />, container);
    
  493.   });
    
  494. 
    
  495.   it('should allow state updates in componentDidMount', () => {
    
  496.     /**
    
  497.      * calls setState in an componentDidMount.
    
  498.      */
    
  499.     class SetStateInComponentDidMount extends React.Component {
    
  500.       state = {
    
  501.         stateField: this.props.valueToUseInitially,
    
  502.       };
    
  503. 
    
  504.       componentDidMount() {
    
  505.         this.setState({stateField: this.props.valueToUseInOnDOMReady});
    
  506.       }
    
  507. 
    
  508.       render() {
    
  509.         return <div />;
    
  510.       }
    
  511.     }
    
  512. 
    
  513.     let instance = (
    
  514.       <SetStateInComponentDidMount
    
  515.         valueToUseInitially="hello"
    
  516.         valueToUseInOnDOMReady="goodbye"
    
  517.       />
    
  518.     );
    
  519.     instance = ReactTestUtils.renderIntoDocument(instance);
    
  520.     expect(instance.state.stateField).toBe('goodbye');
    
  521.   });
    
  522. 
    
  523.   it('should call nested legacy lifecycle methods in the right order', () => {
    
  524.     let log;
    
  525.     const logger = function (msg) {
    
  526.       return function () {
    
  527.         // return true for shouldComponentUpdate
    
  528.         log.push(msg);
    
  529.         return true;
    
  530.       };
    
  531.     };
    
  532.     class Outer extends React.Component {
    
  533.       UNSAFE_componentWillMount = logger('outer componentWillMount');
    
  534.       componentDidMount = logger('outer componentDidMount');
    
  535.       UNSAFE_componentWillReceiveProps = logger(
    
  536.         'outer componentWillReceiveProps',
    
  537.       );
    
  538.       shouldComponentUpdate = logger('outer shouldComponentUpdate');
    
  539.       UNSAFE_componentWillUpdate = logger('outer componentWillUpdate');
    
  540.       componentDidUpdate = logger('outer componentDidUpdate');
    
  541.       componentWillUnmount = logger('outer componentWillUnmount');
    
  542.       render() {
    
  543.         return (
    
  544.           <div>
    
  545.             <Inner x={this.props.x} />
    
  546.           </div>
    
  547.         );
    
  548.       }
    
  549.     }
    
  550. 
    
  551.     class Inner extends React.Component {
    
  552.       UNSAFE_componentWillMount = logger('inner componentWillMount');
    
  553.       componentDidMount = logger('inner componentDidMount');
    
  554.       UNSAFE_componentWillReceiveProps = logger(
    
  555.         'inner componentWillReceiveProps',
    
  556.       );
    
  557.       shouldComponentUpdate = logger('inner shouldComponentUpdate');
    
  558.       UNSAFE_componentWillUpdate = logger('inner componentWillUpdate');
    
  559.       componentDidUpdate = logger('inner componentDidUpdate');
    
  560.       componentWillUnmount = logger('inner componentWillUnmount');
    
  561.       render() {
    
  562.         return <span>{this.props.x}</span>;
    
  563.       }
    
  564.     }
    
  565. 
    
  566.     const container = document.createElement('div');
    
  567.     log = [];
    
  568.     ReactDOM.render(<Outer x={1} />, container);
    
  569.     expect(log).toEqual([
    
  570.       'outer componentWillMount',
    
  571.       'inner componentWillMount',
    
  572.       'inner componentDidMount',
    
  573.       'outer componentDidMount',
    
  574.     ]);
    
  575. 
    
  576.     // Dedup warnings
    
  577.     log = [];
    
  578.     ReactDOM.render(<Outer x={2} />, container);
    
  579.     expect(log).toEqual([
    
  580.       'outer componentWillReceiveProps',
    
  581.       'outer shouldComponentUpdate',
    
  582.       'outer componentWillUpdate',
    
  583.       'inner componentWillReceiveProps',
    
  584.       'inner shouldComponentUpdate',
    
  585.       'inner componentWillUpdate',
    
  586.       'inner componentDidUpdate',
    
  587.       'outer componentDidUpdate',
    
  588.     ]);
    
  589. 
    
  590.     log = [];
    
  591.     ReactDOM.unmountComponentAtNode(container);
    
  592.     expect(log).toEqual([
    
  593.       'outer componentWillUnmount',
    
  594.       'inner componentWillUnmount',
    
  595.     ]);
    
  596.   });
    
  597. 
    
  598.   it('should call nested new lifecycle methods in the right order', () => {
    
  599.     let log;
    
  600.     const logger = function (msg) {
    
  601.       return function () {
    
  602.         // return true for shouldComponentUpdate
    
  603.         log.push(msg);
    
  604.         return true;
    
  605.       };
    
  606.     };
    
  607.     class Outer extends React.Component {
    
  608.       state = {};
    
  609.       static getDerivedStateFromProps(props, prevState) {
    
  610.         log.push('outer getDerivedStateFromProps');
    
  611.         return null;
    
  612.       }
    
  613.       componentDidMount = logger('outer componentDidMount');
    
  614.       shouldComponentUpdate = logger('outer shouldComponentUpdate');
    
  615.       getSnapshotBeforeUpdate = logger('outer getSnapshotBeforeUpdate');
    
  616.       componentDidUpdate = logger('outer componentDidUpdate');
    
  617.       componentWillUnmount = logger('outer componentWillUnmount');
    
  618.       render() {
    
  619.         return (
    
  620.           <div>
    
  621.             <Inner x={this.props.x} />
    
  622.           </div>
    
  623.         );
    
  624.       }
    
  625.     }
    
  626. 
    
  627.     class Inner extends React.Component {
    
  628.       state = {};
    
  629.       static getDerivedStateFromProps(props, prevState) {
    
  630.         log.push('inner getDerivedStateFromProps');
    
  631.         return null;
    
  632.       }
    
  633.       componentDidMount = logger('inner componentDidMount');
    
  634.       shouldComponentUpdate = logger('inner shouldComponentUpdate');
    
  635.       getSnapshotBeforeUpdate = logger('inner getSnapshotBeforeUpdate');
    
  636.       componentDidUpdate = logger('inner componentDidUpdate');
    
  637.       componentWillUnmount = logger('inner componentWillUnmount');
    
  638.       render() {
    
  639.         return <span>{this.props.x}</span>;
    
  640.       }
    
  641.     }
    
  642. 
    
  643.     const container = document.createElement('div');
    
  644.     log = [];
    
  645.     ReactDOM.render(<Outer x={1} />, container);
    
  646.     expect(log).toEqual([
    
  647.       'outer getDerivedStateFromProps',
    
  648.       'inner getDerivedStateFromProps',
    
  649.       'inner componentDidMount',
    
  650.       'outer componentDidMount',
    
  651.     ]);
    
  652. 
    
  653.     // Dedup warnings
    
  654.     log = [];
    
  655.     ReactDOM.render(<Outer x={2} />, container);
    
  656.     expect(log).toEqual([
    
  657.       'outer getDerivedStateFromProps',
    
  658.       'outer shouldComponentUpdate',
    
  659.       'inner getDerivedStateFromProps',
    
  660.       'inner shouldComponentUpdate',
    
  661.       'inner getSnapshotBeforeUpdate',
    
  662.       'outer getSnapshotBeforeUpdate',
    
  663.       'inner componentDidUpdate',
    
  664.       'outer componentDidUpdate',
    
  665.     ]);
    
  666. 
    
  667.     log = [];
    
  668.     ReactDOM.unmountComponentAtNode(container);
    
  669.     expect(log).toEqual([
    
  670.       'outer componentWillUnmount',
    
  671.       'inner componentWillUnmount',
    
  672.     ]);
    
  673.   });
    
  674. 
    
  675.   it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
    
  676.     class Component extends React.Component {
    
  677.       state = {};
    
  678.       static getDerivedStateFromProps() {
    
  679.         return null;
    
  680.       }
    
  681.       componentWillMount() {
    
  682.         throw Error('unexpected');
    
  683.       }
    
  684.       componentWillReceiveProps() {
    
  685.         throw Error('unexpected');
    
  686.       }
    
  687.       componentWillUpdate() {
    
  688.         throw Error('unexpected');
    
  689.       }
    
  690.       render() {
    
  691.         return null;
    
  692.       }
    
  693.     }
    
  694. 
    
  695.     const container = document.createElement('div');
    
  696.     expect(() => {
    
  697.       expect(() => ReactDOM.render(<Component />, container)).toErrorDev(
    
  698.         'Unsafe legacy lifecycles will not be called for components using new component APIs.',
    
  699.       );
    
  700.     }).toWarnDev(
    
  701.       [
    
  702.         'componentWillMount has been renamed',
    
  703.         'componentWillReceiveProps has been renamed',
    
  704.         'componentWillUpdate has been renamed',
    
  705.       ],
    
  706.       {withoutStack: true},
    
  707.     );
    
  708.   });
    
  709. 
    
  710.   it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
    
  711.     class Component extends React.Component {
    
  712.       state = {};
    
  713.       getSnapshotBeforeUpdate() {
    
  714.         return null;
    
  715.       }
    
  716.       componentWillMount() {
    
  717.         throw Error('unexpected');
    
  718.       }
    
  719.       componentWillReceiveProps() {
    
  720.         throw Error('unexpected');
    
  721.       }
    
  722.       componentWillUpdate() {
    
  723.         throw Error('unexpected');
    
  724.       }
    
  725.       componentDidUpdate() {}
    
  726.       render() {
    
  727.         return null;
    
  728.       }
    
  729.     }
    
  730. 
    
  731.     const container = document.createElement('div');
    
  732.     expect(() => {
    
  733.       expect(() =>
    
  734.         ReactDOM.render(<Component value={1} />, container),
    
  735.       ).toErrorDev(
    
  736.         'Unsafe legacy lifecycles will not be called for components using new component APIs.',
    
  737.       );
    
  738.     }).toWarnDev(
    
  739.       [
    
  740.         'componentWillMount has been renamed',
    
  741.         'componentWillReceiveProps has been renamed',
    
  742.         'componentWillUpdate has been renamed',
    
  743.       ],
    
  744.       {withoutStack: true},
    
  745.     );
    
  746.     ReactDOM.render(<Component value={2} />, container);
    
  747.   });
    
  748. 
    
  749.   it('should not invoke new unsafe lifecycles (cWM/cWRP/cWU) if static gDSFP is present', () => {
    
  750.     class Component extends React.Component {
    
  751.       state = {};
    
  752.       static getDerivedStateFromProps() {
    
  753.         return null;
    
  754.       }
    
  755.       UNSAFE_componentWillMount() {
    
  756.         throw Error('unexpected');
    
  757.       }
    
  758.       UNSAFE_componentWillReceiveProps() {
    
  759.         throw Error('unexpected');
    
  760.       }
    
  761.       UNSAFE_componentWillUpdate() {
    
  762.         throw Error('unexpected');
    
  763.       }
    
  764.       render() {
    
  765.         return null;
    
  766.       }
    
  767.     }
    
  768. 
    
  769.     const container = document.createElement('div');
    
  770.     expect(() =>
    
  771.       ReactDOM.render(<Component value={1} />, container),
    
  772.     ).toErrorDev(
    
  773.       'Unsafe legacy lifecycles will not be called for components using new component APIs.',
    
  774.     );
    
  775.     ReactDOM.render(<Component value={2} />, container);
    
  776.   });
    
  777. 
    
  778.   it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
    
  779.     const container = document.createElement('div');
    
  780. 
    
  781.     class AllLegacyLifecycles extends React.Component {
    
  782.       state = {};
    
  783.       static getDerivedStateFromProps() {
    
  784.         return null;
    
  785.       }
    
  786.       componentWillMount() {}
    
  787.       UNSAFE_componentWillReceiveProps() {}
    
  788.       componentWillUpdate() {}
    
  789.       render() {
    
  790.         return null;
    
  791.       }
    
  792.     }
    
  793. 
    
  794.     expect(() => {
    
  795.       expect(() =>
    
  796.         ReactDOM.render(<AllLegacyLifecycles />, container),
    
  797.       ).toErrorDev(
    
  798.         'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  799.           'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
    
  800.           '  componentWillMount\n' +
    
  801.           '  UNSAFE_componentWillReceiveProps\n' +
    
  802.           '  componentWillUpdate\n\n' +
    
  803.           'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  804.           'https://reactjs.org/link/unsafe-component-lifecycles',
    
  805.       );
    
  806.     }).toWarnDev(
    
  807.       [
    
  808.         'componentWillMount has been renamed',
    
  809.         'componentWillUpdate has been renamed',
    
  810.       ],
    
  811.       {withoutStack: true},
    
  812.     );
    
  813. 
    
  814.     class WillMount extends React.Component {
    
  815.       state = {};
    
  816.       static getDerivedStateFromProps() {
    
  817.         return null;
    
  818.       }
    
  819.       UNSAFE_componentWillMount() {}
    
  820.       render() {
    
  821.         return null;
    
  822.       }
    
  823.     }
    
  824. 
    
  825.     expect(() => ReactDOM.render(<WillMount />, container)).toErrorDev(
    
  826.       'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  827.         'WillMount uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
    
  828.         '  UNSAFE_componentWillMount\n\n' +
    
  829.         'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  830.         'https://reactjs.org/link/unsafe-component-lifecycles',
    
  831.     );
    
  832. 
    
  833.     class WillMountAndUpdate extends React.Component {
    
  834.       state = {};
    
  835.       static getDerivedStateFromProps() {
    
  836.         return null;
    
  837.       }
    
  838.       componentWillMount() {}
    
  839.       UNSAFE_componentWillUpdate() {}
    
  840.       render() {
    
  841.         return null;
    
  842.       }
    
  843.     }
    
  844. 
    
  845.     expect(() => {
    
  846.       expect(() =>
    
  847.         ReactDOM.render(<WillMountAndUpdate />, container),
    
  848.       ).toErrorDev(
    
  849.         'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  850.           'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
    
  851.           '  componentWillMount\n' +
    
  852.           '  UNSAFE_componentWillUpdate\n\n' +
    
  853.           'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  854.           'https://reactjs.org/link/unsafe-component-lifecycles',
    
  855.       );
    
  856.     }).toWarnDev(['componentWillMount has been renamed'], {
    
  857.       withoutStack: true,
    
  858.     });
    
  859. 
    
  860.     class WillReceiveProps extends React.Component {
    
  861.       state = {};
    
  862.       static getDerivedStateFromProps() {
    
  863.         return null;
    
  864.       }
    
  865.       componentWillReceiveProps() {}
    
  866.       render() {
    
  867.         return null;
    
  868.       }
    
  869.     }
    
  870. 
    
  871.     expect(() => {
    
  872.       expect(() => ReactDOM.render(<WillReceiveProps />, container)).toErrorDev(
    
  873.         'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  874.           'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
    
  875.           '  componentWillReceiveProps\n\n' +
    
  876.           'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  877.           'https://reactjs.org/link/unsafe-component-lifecycles',
    
  878.       );
    
  879.     }).toWarnDev(['componentWillReceiveProps has been renamed'], {
    
  880.       withoutStack: true,
    
  881.     });
    
  882.   });
    
  883. 
    
  884.   it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
    
  885.     const container = document.createElement('div');
    
  886. 
    
  887.     class AllLegacyLifecycles extends React.Component {
    
  888.       state = {};
    
  889.       getSnapshotBeforeUpdate() {}
    
  890.       componentWillMount() {}
    
  891.       UNSAFE_componentWillReceiveProps() {}
    
  892.       componentWillUpdate() {}
    
  893.       componentDidUpdate() {}
    
  894.       render() {
    
  895.         return null;
    
  896.       }
    
  897.     }
    
  898. 
    
  899.     expect(() => {
    
  900.       expect(() =>
    
  901.         ReactDOM.render(<AllLegacyLifecycles />, container),
    
  902.       ).toErrorDev(
    
  903.         'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  904.           'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
    
  905.           '  componentWillMount\n' +
    
  906.           '  UNSAFE_componentWillReceiveProps\n' +
    
  907.           '  componentWillUpdate\n\n' +
    
  908.           'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  909.           'https://reactjs.org/link/unsafe-component-lifecycles',
    
  910.       );
    
  911.     }).toWarnDev(
    
  912.       [
    
  913.         'componentWillMount has been renamed',
    
  914.         'componentWillUpdate has been renamed',
    
  915.       ],
    
  916.       {withoutStack: true},
    
  917.     );
    
  918. 
    
  919.     class WillMount extends React.Component {
    
  920.       state = {};
    
  921.       getSnapshotBeforeUpdate() {}
    
  922.       UNSAFE_componentWillMount() {}
    
  923.       componentDidUpdate() {}
    
  924.       render() {
    
  925.         return null;
    
  926.       }
    
  927.     }
    
  928. 
    
  929.     expect(() => ReactDOM.render(<WillMount />, container)).toErrorDev(
    
  930.       'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  931.         'WillMount uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
    
  932.         '  UNSAFE_componentWillMount\n\n' +
    
  933.         'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  934.         'https://reactjs.org/link/unsafe-component-lifecycles',
    
  935.     );
    
  936. 
    
  937.     class WillMountAndUpdate extends React.Component {
    
  938.       state = {};
    
  939.       getSnapshotBeforeUpdate() {}
    
  940.       componentWillMount() {}
    
  941.       UNSAFE_componentWillUpdate() {}
    
  942.       componentDidUpdate() {}
    
  943.       render() {
    
  944.         return null;
    
  945.       }
    
  946.     }
    
  947. 
    
  948.     expect(() => {
    
  949.       expect(() =>
    
  950.         ReactDOM.render(<WillMountAndUpdate />, container),
    
  951.       ).toErrorDev(
    
  952.         'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  953.           'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
    
  954.           '  componentWillMount\n' +
    
  955.           '  UNSAFE_componentWillUpdate\n\n' +
    
  956.           'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  957.           'https://reactjs.org/link/unsafe-component-lifecycles',
    
  958.       );
    
  959.     }).toWarnDev(['componentWillMount has been renamed'], {
    
  960.       withoutStack: true,
    
  961.     });
    
  962. 
    
  963.     class WillReceiveProps extends React.Component {
    
  964.       state = {};
    
  965.       getSnapshotBeforeUpdate() {}
    
  966.       componentWillReceiveProps() {}
    
  967.       componentDidUpdate() {}
    
  968.       render() {
    
  969.         return null;
    
  970.       }
    
  971.     }
    
  972. 
    
  973.     expect(() => {
    
  974.       expect(() => ReactDOM.render(<WillReceiveProps />, container)).toErrorDev(
    
  975.         'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
    
  976.           'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
    
  977.           '  componentWillReceiveProps\n\n' +
    
  978.           'The above lifecycles should be removed. Learn more about this warning here:\n' +
    
  979.           'https://reactjs.org/link/unsafe-component-lifecycles',
    
  980.       );
    
  981.     }).toWarnDev(['componentWillReceiveProps has been renamed'], {
    
  982.       withoutStack: true,
    
  983.     });
    
  984.   });
    
  985. 
    
  986.   if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
    
  987.     it('calls effects on module-pattern component', function () {
    
  988.       const log = [];
    
  989. 
    
  990.       function Parent() {
    
  991.         return {
    
  992.           render() {
    
  993.             expect(typeof this.props).toBe('object');
    
  994.             log.push('render');
    
  995.             return <Child />;
    
  996.           },
    
  997.           UNSAFE_componentWillMount() {
    
  998.             log.push('will mount');
    
  999.           },
    
  1000.           componentDidMount() {
    
  1001.             log.push('did mount');
    
  1002.           },
    
  1003.           componentDidUpdate() {
    
  1004.             log.push('did update');
    
  1005.           },
    
  1006.           getChildContext() {
    
  1007.             return {x: 2};
    
  1008.           },
    
  1009.         };
    
  1010.       }
    
  1011.       Parent.childContextTypes = {
    
  1012.         x: PropTypes.number,
    
  1013.       };
    
  1014.       function Child(props, context) {
    
  1015.         expect(context.x).toBe(2);
    
  1016.         return <div />;
    
  1017.       }
    
  1018.       Child.contextTypes = {
    
  1019.         x: PropTypes.number,
    
  1020.       };
    
  1021. 
    
  1022.       const div = document.createElement('div');
    
  1023.       expect(() =>
    
  1024.         ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
    
  1025.       ).toErrorDev(
    
  1026.         'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
    
  1027.           'Change Parent to a class that extends React.Component instead. ' +
    
  1028.           "If you can't use a class try assigning the prototype on the function as a workaround. " +
    
  1029.           '`Parent.prototype = React.Component.prototype`. ' +
    
  1030.           "Don't use an arrow function since it cannot be called with `new` by React.",
    
  1031.       );
    
  1032.       ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
    
  1033. 
    
  1034.       expect(log).toEqual([
    
  1035.         'will mount',
    
  1036.         'render',
    
  1037.         'did mount',
    
  1038.         'ref',
    
  1039. 
    
  1040.         'render',
    
  1041.         'did update',
    
  1042.         'ref',
    
  1043.       ]);
    
  1044.     });
    
  1045.   }
    
  1046. 
    
  1047.   it('should warn if getDerivedStateFromProps returns undefined', () => {
    
  1048.     class MyComponent extends React.Component {
    
  1049.       state = {};
    
  1050.       static getDerivedStateFromProps() {}
    
  1051.       render() {
    
  1052.         return null;
    
  1053.       }
    
  1054.     }
    
  1055. 
    
  1056.     const div = document.createElement('div');
    
  1057.     expect(() => ReactDOM.render(<MyComponent />, div)).toErrorDev(
    
  1058.       'MyComponent.getDerivedStateFromProps(): A valid state object (or null) must ' +
    
  1059.         'be returned. You have returned undefined.',
    
  1060.     );
    
  1061. 
    
  1062.     // De-duped
    
  1063.     ReactDOM.render(<MyComponent />, div);
    
  1064.   });
    
  1065. 
    
  1066.   it('should warn if state is not initialized before getDerivedStateFromProps', () => {
    
  1067.     class MyComponent extends React.Component {
    
  1068.       static getDerivedStateFromProps() {
    
  1069.         return null;
    
  1070.       }
    
  1071.       render() {
    
  1072.         return null;
    
  1073.       }
    
  1074.     }
    
  1075. 
    
  1076.     const div = document.createElement('div');
    
  1077.     expect(() => ReactDOM.render(<MyComponent />, div)).toErrorDev(
    
  1078.       '`MyComponent` uses `getDerivedStateFromProps` but its initial state is ' +
    
  1079.         'undefined. This is not recommended. Instead, define the initial state by ' +
    
  1080.         'assigning an object to `this.state` in the constructor of `MyComponent`. ' +
    
  1081.         'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
    
  1082.     );
    
  1083. 
    
  1084.     // De-duped
    
  1085.     ReactDOM.render(<MyComponent />, div);
    
  1086.   });
    
  1087. 
    
  1088.   it('should invoke both deprecated and new lifecycles if both are present', () => {
    
  1089.     const log = [];
    
  1090. 
    
  1091.     class MyComponent extends React.Component {
    
  1092.       componentWillMount() {
    
  1093.         log.push('componentWillMount');
    
  1094.       }
    
  1095.       componentWillReceiveProps() {
    
  1096.         log.push('componentWillReceiveProps');
    
  1097.       }
    
  1098.       componentWillUpdate() {
    
  1099.         log.push('componentWillUpdate');
    
  1100.       }
    
  1101.       UNSAFE_componentWillMount() {
    
  1102.         log.push('UNSAFE_componentWillMount');
    
  1103.       }
    
  1104.       UNSAFE_componentWillReceiveProps() {
    
  1105.         log.push('UNSAFE_componentWillReceiveProps');
    
  1106.       }
    
  1107.       UNSAFE_componentWillUpdate() {
    
  1108.         log.push('UNSAFE_componentWillUpdate');
    
  1109.       }
    
  1110.       render() {
    
  1111.         return null;
    
  1112.       }
    
  1113.     }
    
  1114. 
    
  1115.     const div = document.createElement('div');
    
  1116.     expect(() => ReactDOM.render(<MyComponent foo="bar" />, div)).toWarnDev(
    
  1117.       [
    
  1118.         'componentWillMount has been renamed',
    
  1119.         'componentWillReceiveProps has been renamed',
    
  1120.         'componentWillUpdate has been renamed',
    
  1121.       ],
    
  1122.       {withoutStack: true},
    
  1123.     );
    
  1124.     expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']);
    
  1125. 
    
  1126.     log.length = 0;
    
  1127. 
    
  1128.     ReactDOM.render(<MyComponent foo="baz" />, div);
    
  1129.     expect(log).toEqual([
    
  1130.       'componentWillReceiveProps',
    
  1131.       'UNSAFE_componentWillReceiveProps',
    
  1132.       'componentWillUpdate',
    
  1133.       'UNSAFE_componentWillUpdate',
    
  1134.     ]);
    
  1135.   });
    
  1136. 
    
  1137.   it('should not override state with stale values if prevState is spread within getDerivedStateFromProps', () => {
    
  1138.     const divRef = React.createRef();
    
  1139.     let childInstance;
    
  1140. 
    
  1141.     class Child extends React.Component {
    
  1142.       state = {local: 0};
    
  1143.       static getDerivedStateFromProps(nextProps, prevState) {
    
  1144.         return {...prevState, remote: nextProps.remote};
    
  1145.       }
    
  1146.       updateState = () => {
    
  1147.         this.setState(state => ({local: state.local + 1}));
    
  1148.         this.props.onChange(this.state.remote + 1);
    
  1149.       };
    
  1150.       render() {
    
  1151.         childInstance = this;
    
  1152.         return (
    
  1153.           <div
    
  1154.             onClick={this.updateState}
    
  1155.             ref={
    
  1156.               divRef
    
  1157.             }>{`remote:${this.state.remote}, local:${this.state.local}`}</div>
    
  1158.         );
    
  1159.       }
    
  1160.     }
    
  1161. 
    
  1162.     class Parent extends React.Component {
    
  1163.       state = {value: 0};
    
  1164.       handleChange = value => {
    
  1165.         this.setState({value});
    
  1166.       };
    
  1167.       render() {
    
  1168.         return <Child remote={this.state.value} onChange={this.handleChange} />;
    
  1169.       }
    
  1170.     }
    
  1171. 
    
  1172.     const container = document.createElement('div');
    
  1173.     document.body.appendChild(container);
    
  1174.     try {
    
  1175.       ReactDOM.render(<Parent />, container);
    
  1176.       expect(divRef.current.textContent).toBe('remote:0, local:0');
    
  1177. 
    
  1178.       // Trigger setState() calls
    
  1179.       childInstance.updateState();
    
  1180.       expect(divRef.current.textContent).toBe('remote:1, local:1');
    
  1181. 
    
  1182.       // Trigger batched setState() calls
    
  1183.       divRef.current.click();
    
  1184.       expect(divRef.current.textContent).toBe('remote:2, local:2');
    
  1185.     } finally {
    
  1186.       document.body.removeChild(container);
    
  1187.     }
    
  1188.   });
    
  1189. 
    
  1190.   it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {
    
  1191.     const log = [];
    
  1192. 
    
  1193.     class MyComponent extends React.Component {
    
  1194.       state = {
    
  1195.         value: 0,
    
  1196.       };
    
  1197.       static getDerivedStateFromProps(nextProps, prevState) {
    
  1198.         return {
    
  1199.           value: prevState.value + 1,
    
  1200.         };
    
  1201.       }
    
  1202.       getSnapshotBeforeUpdate(prevProps, prevState) {
    
  1203.         log.push(
    
  1204.           `getSnapshotBeforeUpdate() prevProps:${prevProps.value} prevState:${prevState.value}`,
    
  1205.         );
    
  1206.         return 'abc';
    
  1207.       }
    
  1208.       componentDidUpdate(prevProps, prevState, snapshot) {
    
  1209.         log.push(
    
  1210.           `componentDidUpdate() prevProps:${prevProps.value} prevState:${prevState.value} snapshot:${snapshot}`,
    
  1211.         );
    
  1212.       }
    
  1213.       render() {
    
  1214.         log.push('render');
    
  1215.         return null;
    
  1216.       }
    
  1217.     }
    
  1218. 
    
  1219.     const div = document.createElement('div');
    
  1220.     ReactDOM.render(
    
  1221.       <div>
    
  1222.         <MyComponent value="foo" />
    
  1223.       </div>,
    
  1224.       div,
    
  1225.     );
    
  1226.     expect(log).toEqual(['render']);
    
  1227.     log.length = 0;
    
  1228. 
    
  1229.     ReactDOM.render(
    
  1230.       <div>
    
  1231.         <MyComponent value="bar" />
    
  1232.       </div>,
    
  1233.       div,
    
  1234.     );
    
  1235.     expect(log).toEqual([
    
  1236.       'render',
    
  1237.       'getSnapshotBeforeUpdate() prevProps:foo prevState:1',
    
  1238.       'componentDidUpdate() prevProps:foo prevState:1 snapshot:abc',
    
  1239.     ]);
    
  1240.     log.length = 0;
    
  1241. 
    
  1242.     ReactDOM.render(
    
  1243.       <div>
    
  1244.         <MyComponent value="baz" />
    
  1245.       </div>,
    
  1246.       div,
    
  1247.     );
    
  1248.     expect(log).toEqual([
    
  1249.       'render',
    
  1250.       'getSnapshotBeforeUpdate() prevProps:bar prevState:2',
    
  1251.       'componentDidUpdate() prevProps:bar prevState:2 snapshot:abc',
    
  1252.     ]);
    
  1253.     log.length = 0;
    
  1254. 
    
  1255.     ReactDOM.render(<div />, div);
    
  1256.     expect(log).toEqual([]);
    
  1257.   });
    
  1258. 
    
  1259.   it('should pass previous state to shouldComponentUpdate even with getDerivedStateFromProps', () => {
    
  1260.     const divRef = React.createRef();
    
  1261.     class SimpleComponent extends React.Component {
    
  1262.       constructor(props) {
    
  1263.         super(props);
    
  1264.         this.state = {
    
  1265.           value: props.value,
    
  1266.         };
    
  1267.       }
    
  1268. 
    
  1269.       static getDerivedStateFromProps(nextProps, prevState) {
    
  1270.         if (nextProps.value === prevState.value) {
    
  1271.           return null;
    
  1272.         }
    
  1273.         return {value: nextProps.value};
    
  1274.       }
    
  1275. 
    
  1276.       shouldComponentUpdate(nextProps, nextState) {
    
  1277.         return nextState.value !== this.state.value;
    
  1278.       }
    
  1279. 
    
  1280.       render() {
    
  1281.         return <div ref={divRef}>value: {this.state.value}</div>;
    
  1282.       }
    
  1283.     }
    
  1284. 
    
  1285.     const div = document.createElement('div');
    
  1286. 
    
  1287.     ReactDOM.render(<SimpleComponent value="initial" />, div);
    
  1288.     expect(divRef.current.textContent).toBe('value: initial');
    
  1289.     ReactDOM.render(<SimpleComponent value="updated" />, div);
    
  1290.     expect(divRef.current.textContent).toBe('value: updated');
    
  1291.   });
    
  1292. 
    
  1293.   it('should call getSnapshotBeforeUpdate before mutations are committed', () => {
    
  1294.     const log = [];
    
  1295. 
    
  1296.     class MyComponent extends React.Component {
    
  1297.       divRef = React.createRef();
    
  1298.       getSnapshotBeforeUpdate(prevProps, prevState) {
    
  1299.         log.push('getSnapshotBeforeUpdate');
    
  1300.         expect(this.divRef.current.textContent).toBe(
    
  1301.           `value:${prevProps.value}`,
    
  1302.         );
    
  1303.         return 'foobar';
    
  1304.       }
    
  1305.       componentDidUpdate(prevProps, prevState, snapshot) {
    
  1306.         log.push('componentDidUpdate');
    
  1307.         expect(this.divRef.current.textContent).toBe(
    
  1308.           `value:${this.props.value}`,
    
  1309.         );
    
  1310.         expect(snapshot).toBe('foobar');
    
  1311.       }
    
  1312.       render() {
    
  1313.         log.push('render');
    
  1314.         return <div ref={this.divRef}>{`value:${this.props.value}`}</div>;
    
  1315.       }
    
  1316.     }
    
  1317. 
    
  1318.     const div = document.createElement('div');
    
  1319.     ReactDOM.render(<MyComponent value="foo" />, div);
    
  1320.     expect(log).toEqual(['render']);
    
  1321.     log.length = 0;
    
  1322. 
    
  1323.     ReactDOM.render(<MyComponent value="bar" />, div);
    
  1324.     expect(log).toEqual([
    
  1325.       'render',
    
  1326.       'getSnapshotBeforeUpdate',
    
  1327.       'componentDidUpdate',
    
  1328.     ]);
    
  1329.     log.length = 0;
    
  1330.   });
    
  1331. 
    
  1332.   it('should warn if getSnapshotBeforeUpdate returns undefined', () => {
    
  1333.     class MyComponent extends React.Component {
    
  1334.       getSnapshotBeforeUpdate() {}
    
  1335.       componentDidUpdate() {}
    
  1336.       render() {
    
  1337.         return null;
    
  1338.       }
    
  1339.     }
    
  1340. 
    
  1341.     const div = document.createElement('div');
    
  1342.     ReactDOM.render(<MyComponent value="foo" />, div);
    
  1343.     expect(() => ReactDOM.render(<MyComponent value="bar" />, div)).toErrorDev(
    
  1344.       'MyComponent.getSnapshotBeforeUpdate(): A snapshot value (or null) must ' +
    
  1345.         'be returned. You have returned undefined.',
    
  1346.     );
    
  1347. 
    
  1348.     // De-duped
    
  1349.     ReactDOM.render(<MyComponent value="baz" />, div);
    
  1350.   });
    
  1351. 
    
  1352.   it('should warn if getSnapshotBeforeUpdate is defined with no componentDidUpdate', () => {
    
  1353.     class MyComponent extends React.Component {
    
  1354.       getSnapshotBeforeUpdate() {
    
  1355.         return null;
    
  1356.       }
    
  1357.       render() {
    
  1358.         return null;
    
  1359.       }
    
  1360.     }
    
  1361. 
    
  1362.     const div = document.createElement('div');
    
  1363.     expect(() => ReactDOM.render(<MyComponent />, div)).toErrorDev(
    
  1364.       'MyComponent: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
    
  1365.         'This component defines getSnapshotBeforeUpdate() only.',
    
  1366.     );
    
  1367. 
    
  1368.     // De-duped
    
  1369.     ReactDOM.render(<MyComponent />, div);
    
  1370.   });
    
  1371. 
    
  1372.   it('warns about deprecated unsafe lifecycles', function () {
    
  1373.     class MyComponent extends React.Component {
    
  1374.       componentWillMount() {}
    
  1375.       componentWillReceiveProps() {}
    
  1376.       componentWillUpdate() {}
    
  1377.       render() {
    
  1378.         return null;
    
  1379.       }
    
  1380.     }
    
  1381. 
    
  1382.     const container = document.createElement('div');
    
  1383.     expect(() => ReactDOM.render(<MyComponent x={1} />, container)).toWarnDev(
    
  1384.       [
    
  1385.         /* eslint-disable max-len */
    
  1386.         `Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
    
  1387. 
    
  1388. * Move code with side effects to componentDidMount, and set initial state in the constructor.
    
  1389. * Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
    
  1390. 
    
  1391. Please update the following components: MyComponent`,
    
  1392.         `Warning: componentWillReceiveProps has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
    
  1393. 
    
  1394. * Move data fetching code or side effects to componentDidUpdate.
    
  1395. * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state
    
  1396. * Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
    
  1397. 
    
  1398. Please update the following components: MyComponent`,
    
  1399.         `Warning: componentWillUpdate has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
    
  1400. 
    
  1401. * Move data fetching code or side effects to componentDidUpdate.
    
  1402. * Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
    
  1403. 
    
  1404. Please update the following components: MyComponent`,
    
  1405.         /* eslint-enable max-len */
    
  1406.       ],
    
  1407.       {withoutStack: true},
    
  1408.     );
    
  1409. 
    
  1410.     // Dedupe check (update and instantiate new)
    
  1411.     ReactDOM.render(<MyComponent x={2} />, container);
    
  1412.     ReactDOM.render(<MyComponent key="new" x={1} />, container);
    
  1413.   });
    
  1414. 
    
  1415.   describe('react-lifecycles-compat', () => {
    
  1416.     const {polyfill} = require('react-lifecycles-compat');
    
  1417. 
    
  1418.     it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
    
  1419.       class PolyfilledComponent extends React.Component {
    
  1420.         state = {};
    
  1421.         static getDerivedStateFromProps() {
    
  1422.           return null;
    
  1423.         }
    
  1424.         render() {
    
  1425.           return null;
    
  1426.         }
    
  1427.       }
    
  1428. 
    
  1429.       polyfill(PolyfilledComponent);
    
  1430. 
    
  1431.       const container = document.createElement('div');
    
  1432.       ReactDOM.render(
    
  1433.         <React.StrictMode>
    
  1434.           <PolyfilledComponent />
    
  1435.         </React.StrictMode>,
    
  1436.         container,
    
  1437.       );
    
  1438.     });
    
  1439. 
    
  1440.     it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
    
  1441.       class PolyfilledComponent extends React.Component {
    
  1442.         getSnapshotBeforeUpdate() {
    
  1443.           return null;
    
  1444.         }
    
  1445.         componentDidUpdate() {}
    
  1446.         render() {
    
  1447.           return null;
    
  1448.         }
    
  1449.       }
    
  1450. 
    
  1451.       polyfill(PolyfilledComponent);
    
  1452. 
    
  1453.       const container = document.createElement('div');
    
  1454.       ReactDOM.render(
    
  1455.         <React.StrictMode>
    
  1456.           <PolyfilledComponent />
    
  1457.         </React.StrictMode>,
    
  1458.         container,
    
  1459.       );
    
  1460.     });
    
  1461.   });
    
  1462. });