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 ReactDOMServer;
    
  15. let ReactTestUtils;
    
  16. 
    
  17. describe('ReactComponent', () => {
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20. 
    
  21.     React = require('react');
    
  22.     ReactDOM = require('react-dom');
    
  23.     ReactDOMServer = require('react-dom/server');
    
  24.     ReactTestUtils = require('react-dom/test-utils');
    
  25.   });
    
  26. 
    
  27.   it('should throw on invalid render targets', () => {
    
  28.     const container = document.createElement('div');
    
  29.     // jQuery objects are basically arrays; people often pass them in by mistake
    
  30.     expect(function () {
    
  31.       ReactDOM.render(<div />, [container]);
    
  32.     }).toThrowError(/Target container is not a DOM element./);
    
  33. 
    
  34.     expect(function () {
    
  35.       ReactDOM.render(<div />, null);
    
  36.     }).toThrowError(/Target container is not a DOM element./);
    
  37.   });
    
  38. 
    
  39.   it('should throw when supplying a string ref outside of render method', () => {
    
  40.     let instance = <div ref="badDiv" />;
    
  41.     expect(function () {
    
  42.       instance = ReactTestUtils.renderIntoDocument(instance);
    
  43.     }).toThrow();
    
  44.   });
    
  45. 
    
  46.   it('should throw (in dev) when children are mutated during render', () => {
    
  47.     function Wrapper(props) {
    
  48.       props.children[1] = <p key={1} />; // Mutation is illegal
    
  49.       return <div>{props.children}</div>;
    
  50.     }
    
  51.     if (__DEV__) {
    
  52.       expect(() => {
    
  53.         ReactTestUtils.renderIntoDocument(
    
  54.           <Wrapper>
    
  55.             <span key={0} />
    
  56.             <span key={1} />
    
  57.             <span key={2} />
    
  58.           </Wrapper>,
    
  59.         );
    
  60.       }).toThrowError(/Cannot assign to read only property.*/);
    
  61.     } else {
    
  62.       ReactTestUtils.renderIntoDocument(
    
  63.         <Wrapper>
    
  64.           <span key={0} />
    
  65.           <span key={1} />
    
  66.           <span key={2} />
    
  67.         </Wrapper>,
    
  68.       );
    
  69.     }
    
  70.   });
    
  71. 
    
  72.   it('should throw (in dev) when children are mutated during update', () => {
    
  73.     class Wrapper extends React.Component {
    
  74.       componentDidMount() {
    
  75.         this.props.children[1] = <p key={1} />; // Mutation is illegal
    
  76.         this.forceUpdate();
    
  77.       }
    
  78. 
    
  79.       render() {
    
  80.         return <div>{this.props.children}</div>;
    
  81.       }
    
  82.     }
    
  83. 
    
  84.     if (__DEV__) {
    
  85.       expect(() => {
    
  86.         ReactTestUtils.renderIntoDocument(
    
  87.           <Wrapper>
    
  88.             <span key={0} />
    
  89.             <span key={1} />
    
  90.             <span key={2} />
    
  91.           </Wrapper>,
    
  92.         );
    
  93.       }).toThrowError(/Cannot assign to read only property.*/);
    
  94.     } else {
    
  95.       ReactTestUtils.renderIntoDocument(
    
  96.         <Wrapper>
    
  97.           <span key={0} />
    
  98.           <span key={1} />
    
  99.           <span key={2} />
    
  100.         </Wrapper>,
    
  101.       );
    
  102.     }
    
  103.   });
    
  104. 
    
  105.   it('should support string refs on owned components', () => {
    
  106.     const innerObj = {};
    
  107.     const outerObj = {};
    
  108. 
    
  109.     class Wrapper extends React.Component {
    
  110.       getObject = () => {
    
  111.         return this.props.object;
    
  112.       };
    
  113. 
    
  114.       render() {
    
  115.         return <div>{this.props.children}</div>;
    
  116.       }
    
  117.     }
    
  118. 
    
  119.     class Component extends React.Component {
    
  120.       render() {
    
  121.         const inner = <Wrapper object={innerObj} ref="inner" />;
    
  122.         const outer = (
    
  123.           <Wrapper object={outerObj} ref="outer">
    
  124.             {inner}
    
  125.           </Wrapper>
    
  126.         );
    
  127.         return outer;
    
  128.       }
    
  129. 
    
  130.       componentDidMount() {
    
  131.         expect(this.refs.inner.getObject()).toEqual(innerObj);
    
  132.         expect(this.refs.outer.getObject()).toEqual(outerObj);
    
  133.       }
    
  134.     }
    
  135. 
    
  136.     expect(() => {
    
  137.       ReactTestUtils.renderIntoDocument(<Component />);
    
  138.     }).toErrorDev([
    
  139.       'Warning: Component "div" contains the string ref "inner". ' +
    
  140.         'Support for string refs will be removed in a future major release. ' +
    
  141.         'We recommend using useRef() or createRef() instead. ' +
    
  142.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  143.         '    in div (at **)\n' +
    
  144.         '    in Wrapper (at **)\n' +
    
  145.         '    in Component (at **)',
    
  146.       'Warning: Component "Component" contains the string ref "outer". ' +
    
  147.         'Support for string refs will be removed in a future major release. ' +
    
  148.         'We recommend using useRef() or createRef() instead. ' +
    
  149.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  150.         '    in Component (at **)',
    
  151.     ]);
    
  152.   });
    
  153. 
    
  154.   it('should not have string refs on unmounted components', () => {
    
  155.     class Parent extends React.Component {
    
  156.       render() {
    
  157.         return (
    
  158.           <Child>
    
  159.             <div ref="test" />
    
  160.           </Child>
    
  161.         );
    
  162.       }
    
  163. 
    
  164.       componentDidMount() {
    
  165.         expect(this.refs && this.refs.test).toEqual(undefined);
    
  166.       }
    
  167.     }
    
  168. 
    
  169.     class Child extends React.Component {
    
  170.       render() {
    
  171.         return <div />;
    
  172.       }
    
  173.     }
    
  174. 
    
  175.     ReactTestUtils.renderIntoDocument(<Parent child={<span />} />);
    
  176.   });
    
  177. 
    
  178.   it('should support callback-style refs', () => {
    
  179.     const innerObj = {};
    
  180.     const outerObj = {};
    
  181. 
    
  182.     class Wrapper extends React.Component {
    
  183.       getObject = () => {
    
  184.         return this.props.object;
    
  185.       };
    
  186. 
    
  187.       render() {
    
  188.         return <div>{this.props.children}</div>;
    
  189.       }
    
  190.     }
    
  191. 
    
  192.     let mounted = false;
    
  193. 
    
  194.     class Component extends React.Component {
    
  195.       render() {
    
  196.         const inner = (
    
  197.           <Wrapper object={innerObj} ref={c => (this.innerRef = c)} />
    
  198.         );
    
  199.         const outer = (
    
  200.           <Wrapper object={outerObj} ref={c => (this.outerRef = c)}>
    
  201.             {inner}
    
  202.           </Wrapper>
    
  203.         );
    
  204.         return outer;
    
  205.       }
    
  206. 
    
  207.       componentDidMount() {
    
  208.         expect(this.innerRef.getObject()).toEqual(innerObj);
    
  209.         expect(this.outerRef.getObject()).toEqual(outerObj);
    
  210.         mounted = true;
    
  211.       }
    
  212.     }
    
  213. 
    
  214.     ReactTestUtils.renderIntoDocument(<Component />);
    
  215.     expect(mounted).toBe(true);
    
  216.   });
    
  217. 
    
  218.   it('should support object-style refs', () => {
    
  219.     const innerObj = {};
    
  220.     const outerObj = {};
    
  221. 
    
  222.     class Wrapper extends React.Component {
    
  223.       getObject = () => {
    
  224.         return this.props.object;
    
  225.       };
    
  226. 
    
  227.       render() {
    
  228.         return <div>{this.props.children}</div>;
    
  229.       }
    
  230.     }
    
  231. 
    
  232.     let mounted = false;
    
  233. 
    
  234.     class Component extends React.Component {
    
  235.       constructor() {
    
  236.         super();
    
  237.         this.innerRef = React.createRef();
    
  238.         this.outerRef = React.createRef();
    
  239.       }
    
  240.       render() {
    
  241.         const inner = <Wrapper object={innerObj} ref={this.innerRef} />;
    
  242.         const outer = (
    
  243.           <Wrapper object={outerObj} ref={this.outerRef}>
    
  244.             {inner}
    
  245.           </Wrapper>
    
  246.         );
    
  247.         return outer;
    
  248.       }
    
  249. 
    
  250.       componentDidMount() {
    
  251.         expect(this.innerRef.current.getObject()).toEqual(innerObj);
    
  252.         expect(this.outerRef.current.getObject()).toEqual(outerObj);
    
  253.         mounted = true;
    
  254.       }
    
  255.     }
    
  256. 
    
  257.     ReactTestUtils.renderIntoDocument(<Component />);
    
  258.     expect(mounted).toBe(true);
    
  259.   });
    
  260. 
    
  261.   it('should support new-style refs with mixed-up owners', () => {
    
  262.     class Wrapper extends React.Component {
    
  263.       getTitle = () => {
    
  264.         return this.props.title;
    
  265.       };
    
  266. 
    
  267.       render() {
    
  268.         return this.props.getContent();
    
  269.       }
    
  270.     }
    
  271. 
    
  272.     let mounted = false;
    
  273. 
    
  274.     class Component extends React.Component {
    
  275.       getInner = () => {
    
  276.         // (With old-style refs, it's impossible to get a ref to this div
    
  277.         // because Wrapper is the current owner when this function is called.)
    
  278.         return <div className="inner" ref={c => (this.innerRef = c)} />;
    
  279.       };
    
  280. 
    
  281.       render() {
    
  282.         return (
    
  283.           <Wrapper
    
  284.             title="wrapper"
    
  285.             ref={c => (this.wrapperRef = c)}
    
  286.             getContent={this.getInner}
    
  287.           />
    
  288.         );
    
  289.       }
    
  290. 
    
  291.       componentDidMount() {
    
  292.         // Check .props.title to make sure we got the right elements back
    
  293.         expect(this.wrapperRef.getTitle()).toBe('wrapper');
    
  294.         expect(this.innerRef.className).toBe('inner');
    
  295.         mounted = true;
    
  296.       }
    
  297.     }
    
  298. 
    
  299.     ReactTestUtils.renderIntoDocument(<Component />);
    
  300.     expect(mounted).toBe(true);
    
  301.   });
    
  302. 
    
  303.   it('should call refs at the correct time', () => {
    
  304.     const log = [];
    
  305. 
    
  306.     class Inner extends React.Component {
    
  307.       render() {
    
  308.         log.push(`inner ${this.props.id} render`);
    
  309.         return <div />;
    
  310.       }
    
  311. 
    
  312.       componentDidMount() {
    
  313.         log.push(`inner ${this.props.id} componentDidMount`);
    
  314.       }
    
  315. 
    
  316.       componentDidUpdate() {
    
  317.         log.push(`inner ${this.props.id} componentDidUpdate`);
    
  318.       }
    
  319. 
    
  320.       componentWillUnmount() {
    
  321.         log.push(`inner ${this.props.id} componentWillUnmount`);
    
  322.       }
    
  323.     }
    
  324. 
    
  325.     class Outer extends React.Component {
    
  326.       render() {
    
  327.         return (
    
  328.           <div>
    
  329.             <Inner
    
  330.               id={1}
    
  331.               ref={c => {
    
  332.                 log.push(`ref 1 got ${c ? `instance ${c.props.id}` : 'null'}`);
    
  333.               }}
    
  334.             />
    
  335.             <Inner
    
  336.               id={2}
    
  337.               ref={c => {
    
  338.                 log.push(`ref 2 got ${c ? `instance ${c.props.id}` : 'null'}`);
    
  339.               }}
    
  340.             />
    
  341.           </div>
    
  342.         );
    
  343.       }
    
  344. 
    
  345.       componentDidMount() {
    
  346.         log.push('outer componentDidMount');
    
  347.       }
    
  348. 
    
  349.       componentDidUpdate() {
    
  350.         log.push('outer componentDidUpdate');
    
  351.       }
    
  352. 
    
  353.       componentWillUnmount() {
    
  354.         log.push('outer componentWillUnmount');
    
  355.       }
    
  356.     }
    
  357. 
    
  358.     // mount, update, unmount
    
  359.     const el = document.createElement('div');
    
  360.     log.push('start mount');
    
  361.     ReactDOM.render(<Outer />, el);
    
  362.     log.push('start update');
    
  363.     ReactDOM.render(<Outer />, el);
    
  364.     log.push('start unmount');
    
  365.     ReactDOM.unmountComponentAtNode(el);
    
  366. 
    
  367.     /* eslint-disable indent */
    
  368.     expect(log).toEqual([
    
  369.       'start mount',
    
  370.       'inner 1 render',
    
  371.       'inner 2 render',
    
  372.       'inner 1 componentDidMount',
    
  373.       'ref 1 got instance 1',
    
  374.       'inner 2 componentDidMount',
    
  375.       'ref 2 got instance 2',
    
  376.       'outer componentDidMount',
    
  377.       'start update',
    
  378.       // Previous (equivalent) refs get cleared
    
  379.       // Fiber renders first, resets refs later
    
  380.       'inner 1 render',
    
  381.       'inner 2 render',
    
  382.       'ref 1 got null',
    
  383.       'ref 2 got null',
    
  384.       'inner 1 componentDidUpdate',
    
  385.       'ref 1 got instance 1',
    
  386.       'inner 2 componentDidUpdate',
    
  387.       'ref 2 got instance 2',
    
  388.       'outer componentDidUpdate',
    
  389.       'start unmount',
    
  390.       'outer componentWillUnmount',
    
  391.       'ref 1 got null',
    
  392.       'inner 1 componentWillUnmount',
    
  393.       'ref 2 got null',
    
  394.       'inner 2 componentWillUnmount',
    
  395.     ]);
    
  396.     /* eslint-enable indent */
    
  397.   });
    
  398. 
    
  399.   it('fires the callback after a component is rendered', () => {
    
  400.     const callback = jest.fn();
    
  401.     const container = document.createElement('div');
    
  402.     ReactDOM.render(<div />, container, callback);
    
  403.     expect(callback).toHaveBeenCalledTimes(1);
    
  404.     ReactDOM.render(<div className="foo" />, container, callback);
    
  405.     expect(callback).toHaveBeenCalledTimes(2);
    
  406.     ReactDOM.render(<span />, container, callback);
    
  407.     expect(callback).toHaveBeenCalledTimes(3);
    
  408.   });
    
  409. 
    
  410.   it('throws usefully when rendering badly-typed elements', () => {
    
  411.     const X = undefined;
    
  412.     expect(() => {
    
  413.       expect(() => ReactTestUtils.renderIntoDocument(<X />)).toErrorDev(
    
  414.         'React.createElement: type is invalid -- expected a string (for built-in components) ' +
    
  415.           'or a class/function (for composite components) but got: undefined.',
    
  416.       );
    
  417.     }).toThrowError(
    
  418.       'Element type is invalid: expected a string (for built-in components) ' +
    
  419.         'or a class/function (for composite components) but got: undefined.' +
    
  420.         (__DEV__
    
  421.           ? " You likely forgot to export your component from the file it's " +
    
  422.             'defined in, or you might have mixed up default and named imports.'
    
  423.           : ''),
    
  424.     );
    
  425. 
    
  426.     const Y = null;
    
  427.     expect(() => {
    
  428.       expect(() => ReactTestUtils.renderIntoDocument(<Y />)).toErrorDev(
    
  429.         'React.createElement: type is invalid -- expected a string (for built-in components) ' +
    
  430.           'or a class/function (for composite components) but got: null.',
    
  431.       );
    
  432.     }).toThrowError(
    
  433.       'Element type is invalid: expected a string (for built-in components) ' +
    
  434.         'or a class/function (for composite components) but got: null.',
    
  435.     );
    
  436.   });
    
  437. 
    
  438.   it('includes owner name in the error about badly-typed elements', () => {
    
  439.     const X = undefined;
    
  440. 
    
  441.     function Indirection(props) {
    
  442.       return <div>{props.children}</div>;
    
  443.     }
    
  444. 
    
  445.     function Bar() {
    
  446.       return (
    
  447.         <Indirection>
    
  448.           <X />
    
  449.         </Indirection>
    
  450.       );
    
  451.     }
    
  452. 
    
  453.     function Foo() {
    
  454.       return <Bar />;
    
  455.     }
    
  456. 
    
  457.     expect(() => {
    
  458.       expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toErrorDev(
    
  459.         'React.createElement: type is invalid -- expected a string (for built-in components) ' +
    
  460.           'or a class/function (for composite components) but got: undefined.',
    
  461.       );
    
  462.     }).toThrowError(
    
  463.       'Element type is invalid: expected a string (for built-in components) ' +
    
  464.         'or a class/function (for composite components) but got: undefined.' +
    
  465.         (__DEV__
    
  466.           ? " You likely forgot to export your component from the file it's " +
    
  467.             'defined in, or you might have mixed up default and named imports.' +
    
  468.             '\n\nCheck the render method of `Bar`.'
    
  469.           : ''),
    
  470.     );
    
  471.   });
    
  472. 
    
  473.   it('throws if a plain object is used as a child', () => {
    
  474.     const children = {
    
  475.       x: <span />,
    
  476.       y: <span />,
    
  477.       z: <span />,
    
  478.     };
    
  479.     const element = <div>{[children]}</div>;
    
  480.     const container = document.createElement('div');
    
  481.     expect(() => {
    
  482.       ReactDOM.render(element, container);
    
  483.     }).toThrowError(
    
  484.       'Objects are not valid as a React child (found: object with keys {x, y, z}). ' +
    
  485.         'If you meant to render a collection of children, use an array instead.',
    
  486.     );
    
  487.   });
    
  488. 
    
  489.   it('throws if a plain object even if it is in an owner', () => {
    
  490.     class Foo extends React.Component {
    
  491.       render() {
    
  492.         const children = {
    
  493.           a: <span />,
    
  494.           b: <span />,
    
  495.           c: <span />,
    
  496.         };
    
  497.         return <div>{[children]}</div>;
    
  498.       }
    
  499.     }
    
  500.     const container = document.createElement('div');
    
  501.     expect(() => {
    
  502.       ReactDOM.render(<Foo />, container);
    
  503.     }).toThrowError(
    
  504.       'Objects are not valid as a React child (found: object with keys {a, b, c}).' +
    
  505.         ' If you meant to render a collection of children, use an array ' +
    
  506.         'instead.',
    
  507.     );
    
  508.   });
    
  509. 
    
  510.   it('throws if a plain object is used as a child when using SSR', async () => {
    
  511.     const children = {
    
  512.       x: <span />,
    
  513.       y: <span />,
    
  514.       z: <span />,
    
  515.     };
    
  516.     const element = <div>{[children]}</div>;
    
  517.     expect(() => {
    
  518.       ReactDOMServer.renderToString(element);
    
  519.     }).toThrowError(
    
  520.       'Objects are not valid as a React child (found: object with keys {x, y, z}). ' +
    
  521.         'If you meant to render a collection of children, use ' +
    
  522.         'an array instead.',
    
  523.     );
    
  524.   });
    
  525. 
    
  526.   it('throws if a plain object even if it is in an owner when using SSR', async () => {
    
  527.     class Foo extends React.Component {
    
  528.       render() {
    
  529.         const children = {
    
  530.           a: <span />,
    
  531.           b: <span />,
    
  532.           c: <span />,
    
  533.         };
    
  534.         return <div>{[children]}</div>;
    
  535.       }
    
  536.     }
    
  537.     const container = document.createElement('div');
    
  538.     expect(() => {
    
  539.       ReactDOMServer.renderToString(<Foo />, container);
    
  540.     }).toThrowError(
    
  541.       'Objects are not valid as a React child (found: object with keys {a, b, c}). ' +
    
  542.         'If you meant to render a collection of children, use ' +
    
  543.         'an array instead.',
    
  544.     );
    
  545.   });
    
  546. 
    
  547.   describe('with new features', () => {
    
  548.     it('warns on function as a return value from a function', () => {
    
  549.       function Foo() {
    
  550.         return Foo;
    
  551.       }
    
  552.       const container = document.createElement('div');
    
  553.       expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
    
  554.         'Warning: Functions are not valid as a React child. This may happen if ' +
    
  555.           'you return a Component instead of <Component /> from render. ' +
    
  556.           'Or maybe you meant to call this function rather than return it.\n' +
    
  557.           '    in Foo (at **)',
    
  558.       );
    
  559.     });
    
  560. 
    
  561.     it('warns on function as a return value from a class', () => {
    
  562.       class Foo extends React.Component {
    
  563.         render() {
    
  564.           return Foo;
    
  565.         }
    
  566.       }
    
  567.       const container = document.createElement('div');
    
  568.       expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
    
  569.         'Warning: Functions are not valid as a React child. This may happen if ' +
    
  570.           'you return a Component instead of <Component /> from render. ' +
    
  571.           'Or maybe you meant to call this function rather than return it.\n' +
    
  572.           '    in Foo (at **)',
    
  573.       );
    
  574.     });
    
  575. 
    
  576.     it('warns on function as a child to host component', () => {
    
  577.       function Foo() {
    
  578.         return (
    
  579.           <div>
    
  580.             <span>{Foo}</span>
    
  581.           </div>
    
  582.         );
    
  583.       }
    
  584.       const container = document.createElement('div');
    
  585.       expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
    
  586.         'Warning: Functions are not valid as a React child. This may happen if ' +
    
  587.           'you return a Component instead of <Component /> from render. ' +
    
  588.           'Or maybe you meant to call this function rather than return it.\n' +
    
  589.           '    in span (at **)\n' +
    
  590.           '    in div (at **)\n' +
    
  591.           '    in Foo (at **)',
    
  592.       );
    
  593.     });
    
  594. 
    
  595.     it('does not warn for function-as-a-child that gets resolved', () => {
    
  596.       function Bar(props) {
    
  597.         return props.children();
    
  598.       }
    
  599.       function Foo() {
    
  600.         return <Bar>{() => 'Hello'}</Bar>;
    
  601.       }
    
  602.       const container = document.createElement('div');
    
  603.       ReactDOM.render(<Foo />, container);
    
  604.       expect(container.innerHTML).toBe('Hello');
    
  605.     });
    
  606. 
    
  607.     it('deduplicates function type warnings based on component type', () => {
    
  608.       class Foo extends React.PureComponent {
    
  609.         constructor() {
    
  610.           super();
    
  611.           this.state = {type: 'mushrooms'};
    
  612.         }
    
  613.         render() {
    
  614.           return (
    
  615.             <div>
    
  616.               {Foo}
    
  617.               {Foo}
    
  618.               <span>
    
  619.                 {Foo}
    
  620.                 {Foo}
    
  621.               </span>
    
  622.             </div>
    
  623.           );
    
  624.         }
    
  625.       }
    
  626.       const container = document.createElement('div');
    
  627.       let component;
    
  628.       expect(() => {
    
  629.         component = ReactDOM.render(<Foo />, container);
    
  630.       }).toErrorDev([
    
  631.         'Warning: Functions are not valid as a React child. This may happen if ' +
    
  632.           'you return a Component instead of <Component /> from render. ' +
    
  633.           'Or maybe you meant to call this function rather than return it.\n' +
    
  634.           '    in div (at **)\n' +
    
  635.           '    in Foo (at **)',
    
  636.         'Warning: Functions are not valid as a React child. This may happen if ' +
    
  637.           'you return a Component instead of <Component /> from render. ' +
    
  638.           'Or maybe you meant to call this function rather than return it.\n' +
    
  639.           '    in span (at **)\n' +
    
  640.           '    in div (at **)\n' +
    
  641.           '    in Foo (at **)',
    
  642.       ]);
    
  643.       component.setState({type: 'portobello mushrooms'});
    
  644.     });
    
  645.   });
    
  646. });