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. const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
    
  13. 
    
  14. let React;
    
  15. let ReactDOMServer;
    
  16. 
    
  17. function initModules() {
    
  18.   // Reset warning cache.
    
  19.   jest.resetModules();
    
  20.   React = require('react');
    
  21.   ReactDOMServer = require('react-dom/server');
    
  22. 
    
  23.   // Make them available to the helpers.
    
  24.   return {
    
  25.     ReactDOMServer,
    
  26.   };
    
  27. }
    
  28. 
    
  29. const {resetModules} = ReactDOMServerIntegrationUtils(initModules);
    
  30. 
    
  31. describe('ReactDOMServerLifecycles', () => {
    
  32.   beforeEach(() => {
    
  33.     resetModules();
    
  34.   });
    
  35. 
    
  36.   it('should invoke the correct legacy lifecycle hooks', () => {
    
  37.     const log = [];
    
  38. 
    
  39.     class Outer extends React.Component {
    
  40.       UNSAFE_componentWillMount() {
    
  41.         log.push('outer componentWillMount');
    
  42.       }
    
  43.       render() {
    
  44.         log.push('outer render');
    
  45.         return <Inner />;
    
  46.       }
    
  47.     }
    
  48. 
    
  49.     class Inner extends React.Component {
    
  50.       UNSAFE_componentWillMount() {
    
  51.         log.push('inner componentWillMount');
    
  52.       }
    
  53.       render() {
    
  54.         log.push('inner render');
    
  55.         return null;
    
  56.       }
    
  57.     }
    
  58. 
    
  59.     ReactDOMServer.renderToString(<Outer />);
    
  60.     expect(log).toEqual([
    
  61.       'outer componentWillMount',
    
  62.       'outer render',
    
  63.       'inner componentWillMount',
    
  64.       'inner render',
    
  65.     ]);
    
  66.   });
    
  67. 
    
  68.   it('should invoke the correct new lifecycle hooks', () => {
    
  69.     const log = [];
    
  70. 
    
  71.     class Outer extends React.Component {
    
  72.       state = {};
    
  73.       static getDerivedStateFromProps() {
    
  74.         log.push('outer getDerivedStateFromProps');
    
  75.         return null;
    
  76.       }
    
  77.       render() {
    
  78.         log.push('outer render');
    
  79.         return <Inner />;
    
  80.       }
    
  81.     }
    
  82. 
    
  83.     class Inner extends React.Component {
    
  84.       state = {};
    
  85.       static getDerivedStateFromProps() {
    
  86.         log.push('inner getDerivedStateFromProps');
    
  87.         return null;
    
  88.       }
    
  89.       render() {
    
  90.         log.push('inner render');
    
  91.         return null;
    
  92.       }
    
  93.     }
    
  94. 
    
  95.     ReactDOMServer.renderToString(<Outer />);
    
  96.     expect(log).toEqual([
    
  97.       'outer getDerivedStateFromProps',
    
  98.       'outer render',
    
  99.       'inner getDerivedStateFromProps',
    
  100.       'inner render',
    
  101.     ]);
    
  102.   });
    
  103. 
    
  104.   it('should not invoke unsafe cWM if static gDSFP is present', () => {
    
  105.     class Component extends React.Component {
    
  106.       state = {};
    
  107.       static getDerivedStateFromProps() {
    
  108.         return null;
    
  109.       }
    
  110.       UNSAFE_componentWillMount() {
    
  111.         throw Error('unexpected');
    
  112.       }
    
  113.       render() {
    
  114.         return null;
    
  115.       }
    
  116.     }
    
  117. 
    
  118.     expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
    
  119.       'Unsafe legacy lifecycles will not be called for components using new component APIs.',
    
  120.     );
    
  121.   });
    
  122. 
    
  123.   it('should update instance.state with value returned from getDerivedStateFromProps', () => {
    
  124.     class Grandparent extends React.Component {
    
  125.       state = {
    
  126.         foo: 'foo',
    
  127.       };
    
  128.       render() {
    
  129.         return (
    
  130.           <div>
    
  131.             {`Grandparent: ${this.state.foo}`}
    
  132.             <Parent />
    
  133.           </div>
    
  134.         );
    
  135.       }
    
  136.     }
    
  137. 
    
  138.     class Parent extends React.Component {
    
  139.       state = {
    
  140.         bar: 'bar',
    
  141.         baz: 'baz',
    
  142.       };
    
  143.       static getDerivedStateFromProps(props, prevState) {
    
  144.         return {
    
  145.           bar: `not ${prevState.bar}`,
    
  146.         };
    
  147.       }
    
  148.       render() {
    
  149.         return (
    
  150.           <div>
    
  151.             {`Parent: ${this.state.bar}, ${this.state.baz}`}
    
  152.             <Child />;
    
  153.           </div>
    
  154.         );
    
  155.       }
    
  156.     }
    
  157. 
    
  158.     class Child extends React.Component {
    
  159.       state = {};
    
  160.       static getDerivedStateFromProps() {
    
  161.         return {
    
  162.           qux: 'qux',
    
  163.         };
    
  164.       }
    
  165.       render() {
    
  166.         return `Child: ${this.state.qux}`;
    
  167.       }
    
  168.     }
    
  169. 
    
  170.     const markup = ReactDOMServer.renderToString(<Grandparent />);
    
  171.     expect(markup).toContain('Grandparent: foo');
    
  172.     expect(markup).toContain('Parent: not bar, baz');
    
  173.     expect(markup).toContain('Child: qux');
    
  174.   });
    
  175. 
    
  176.   it('should warn if getDerivedStateFromProps returns undefined', () => {
    
  177.     class Component extends React.Component {
    
  178.       state = {};
    
  179.       static getDerivedStateFromProps() {}
    
  180.       render() {
    
  181.         return null;
    
  182.       }
    
  183.     }
    
  184. 
    
  185.     expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
    
  186.       'Component.getDerivedStateFromProps(): A valid state object (or null) must ' +
    
  187.         'be returned. You have returned undefined.',
    
  188.     );
    
  189. 
    
  190.     // De-duped
    
  191.     ReactDOMServer.renderToString(<Component />);
    
  192.   });
    
  193. 
    
  194.   it('should warn if state is not initialized before getDerivedStateFromProps', () => {
    
  195.     class Component extends React.Component {
    
  196.       static getDerivedStateFromProps() {
    
  197.         return null;
    
  198.       }
    
  199.       render() {
    
  200.         return null;
    
  201.       }
    
  202.     }
    
  203. 
    
  204.     expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
    
  205.       '`Component` uses `getDerivedStateFromProps` but its initial state is ' +
    
  206.         'undefined. This is not recommended. Instead, define the initial state by ' +
    
  207.         'assigning an object to `this.state` in the constructor of `Component`. ' +
    
  208.         'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
    
  209.     );
    
  210. 
    
  211.     // De-duped
    
  212.     ReactDOMServer.renderToString(<Component />);
    
  213.   });
    
  214. 
    
  215.   it('should invoke both deprecated and new lifecycles if both are present', () => {
    
  216.     const log = [];
    
  217. 
    
  218.     class Component extends React.Component {
    
  219.       componentWillMount() {
    
  220.         log.push('componentWillMount');
    
  221.       }
    
  222.       UNSAFE_componentWillMount() {
    
  223.         log.push('UNSAFE_componentWillMount');
    
  224.       }
    
  225.       render() {
    
  226.         return null;
    
  227.       }
    
  228.     }
    
  229. 
    
  230.     expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
    
  231.       'componentWillMount has been renamed',
    
  232.     );
    
  233.     expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']);
    
  234.   });
    
  235. 
    
  236.   it('tracks state updates across components', () => {
    
  237.     class Outer extends React.Component {
    
  238.       UNSAFE_componentWillMount() {
    
  239.         this.setState({x: 1});
    
  240.       }
    
  241.       render() {
    
  242.         return <Inner updateParent={this.updateParent}>{this.state.x}</Inner>;
    
  243.       }
    
  244.       updateParent = () => {
    
  245.         this.setState({x: 3});
    
  246.       };
    
  247.     }
    
  248.     class Inner extends React.Component {
    
  249.       UNSAFE_componentWillMount() {
    
  250.         this.setState({x: 2});
    
  251.         this.props.updateParent();
    
  252.       }
    
  253.       render() {
    
  254.         return <div>{this.props.children + '-' + this.state.x}</div>;
    
  255.       }
    
  256.     }
    
  257.     expect(() => {
    
  258.       // Shouldn't be 1-3.
    
  259.       expect(ReactDOMServer.renderToStaticMarkup(<Outer />)).toBe(
    
  260.         '<div>1-2</div>',
    
  261.       );
    
  262.     }).toErrorDev(
    
  263.       'Warning: setState(...): Can only update a mounting component. This ' +
    
  264.         'usually means you called setState() outside componentWillMount() on ' +
    
  265.         'the server. This is a no-op.\n\n' +
    
  266.         'Please check the code for the Outer component.',
    
  267.     );
    
  268.   });
    
  269. 
    
  270.   it('should not invoke cWM if static gDSFP is present', () => {
    
  271.     class Component extends React.Component {
    
  272.       state = {};
    
  273.       static getDerivedStateFromProps() {
    
  274.         return null;
    
  275.       }
    
  276.       componentWillMount() {
    
  277.         throw Error('unexpected');
    
  278.       }
    
  279.       render() {
    
  280.         return null;
    
  281.       }
    
  282.     }
    
  283. 
    
  284.     expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
    
  285.       'Unsafe legacy lifecycles will not be called for components using new component APIs.',
    
  286.     );
    
  287.   });
    
  288. 
    
  289.   it('should warn about deprecated lifecycle hooks', () => {
    
  290.     class MyComponent extends React.Component {
    
  291.       componentWillMount() {}
    
  292.       render() {
    
  293.         return null;
    
  294.       }
    
  295.     }
    
  296. 
    
  297.     expect(() => ReactDOMServer.renderToString(<MyComponent />)).toWarnDev(
    
  298.       'componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
    
  299.         '* Move code from componentWillMount to componentDidMount (preferred in most cases) or the constructor.\n\n' +
    
  300.         'Please update the following components: MyComponent',
    
  301.     );
    
  302. 
    
  303.     // De-duped
    
  304.     ReactDOMServer.renderToString(<MyComponent />);
    
  305.   });
    
  306. 
    
  307.   describe('react-lifecycles-compat', () => {
    
  308.     const {polyfill} = require('react-lifecycles-compat');
    
  309. 
    
  310.     it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
    
  311.       class PolyfilledComponent extends React.Component {
    
  312.         state = {};
    
  313.         static getDerivedStateFromProps() {
    
  314.           return null;
    
  315.         }
    
  316.         render() {
    
  317.           return null;
    
  318.         }
    
  319.       }
    
  320. 
    
  321.       polyfill(PolyfilledComponent);
    
  322. 
    
  323.       const container = document.createElement('div');
    
  324.       ReactDOMServer.renderToString(
    
  325.         <React.StrictMode>
    
  326.           <PolyfilledComponent />
    
  327.         </React.StrictMode>,
    
  328.         container,
    
  329.       );
    
  330.     });
    
  331. 
    
  332.     it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
    
  333.       class PolyfilledComponent extends React.Component {
    
  334.         getSnapshotBeforeUpdate() {
    
  335.           return null;
    
  336.         }
    
  337.         componentDidUpdate() {}
    
  338.         render() {
    
  339.           return null;
    
  340.         }
    
  341.       }
    
  342. 
    
  343.       polyfill(PolyfilledComponent);
    
  344. 
    
  345.       const container = document.createElement('div');
    
  346.       ReactDOMServer.renderToString(
    
  347.         <React.StrictMode>
    
  348.           <PolyfilledComponent />
    
  349.         </React.StrictMode>,
    
  350.         container,
    
  351.       );
    
  352.     });
    
  353.   });
    
  354. });