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 PropTypes;
    
  13. let React;
    
  14. let ReactDOM;
    
  15. let ReactTestUtils;
    
  16. 
    
  17. function FunctionComponent(props) {
    
  18.   return <div>{props.name}</div>;
    
  19. }
    
  20. 
    
  21. describe('ReactFunctionComponent', () => {
    
  22.   beforeEach(() => {
    
  23.     jest.resetModules();
    
  24.     PropTypes = require('prop-types');
    
  25.     React = require('react');
    
  26.     ReactDOM = require('react-dom');
    
  27.     ReactTestUtils = require('react-dom/test-utils');
    
  28.   });
    
  29. 
    
  30.   it('should render stateless component', () => {
    
  31.     const el = document.createElement('div');
    
  32.     ReactDOM.render(<FunctionComponent name="A" />, el);
    
  33. 
    
  34.     expect(el.textContent).toBe('A');
    
  35.   });
    
  36. 
    
  37.   it('should update stateless component', () => {
    
  38.     class Parent extends React.Component {
    
  39.       render() {
    
  40.         return <FunctionComponent {...this.props} />;
    
  41.       }
    
  42.     }
    
  43. 
    
  44.     const el = document.createElement('div');
    
  45.     ReactDOM.render(<Parent name="A" />, el);
    
  46.     expect(el.textContent).toBe('A');
    
  47. 
    
  48.     ReactDOM.render(<Parent name="B" />, el);
    
  49.     expect(el.textContent).toBe('B');
    
  50.   });
    
  51. 
    
  52.   it('should unmount stateless component', () => {
    
  53.     const container = document.createElement('div');
    
  54. 
    
  55.     ReactDOM.render(<FunctionComponent name="A" />, container);
    
  56.     expect(container.textContent).toBe('A');
    
  57. 
    
  58.     ReactDOM.unmountComponentAtNode(container);
    
  59.     expect(container.textContent).toBe('');
    
  60.   });
    
  61. 
    
  62.   // @gate !disableLegacyContext
    
  63.   it('should pass context thru stateless component', () => {
    
  64.     class Child extends React.Component {
    
  65.       static contextTypes = {
    
  66.         test: PropTypes.string.isRequired,
    
  67.       };
    
  68. 
    
  69.       render() {
    
  70.         return <div>{this.context.test}</div>;
    
  71.       }
    
  72.     }
    
  73. 
    
  74.     function Parent() {
    
  75.       return <Child />;
    
  76.     }
    
  77. 
    
  78.     class GrandParent extends React.Component {
    
  79.       static childContextTypes = {
    
  80.         test: PropTypes.string.isRequired,
    
  81.       };
    
  82. 
    
  83.       getChildContext() {
    
  84.         return {test: this.props.test};
    
  85.       }
    
  86. 
    
  87.       render() {
    
  88.         return <Parent />;
    
  89.       }
    
  90.     }
    
  91. 
    
  92.     const el = document.createElement('div');
    
  93.     ReactDOM.render(<GrandParent test="test" />, el);
    
  94. 
    
  95.     expect(el.textContent).toBe('test');
    
  96. 
    
  97.     ReactDOM.render(<GrandParent test="mest" />, el);
    
  98. 
    
  99.     expect(el.textContent).toBe('mest');
    
  100.   });
    
  101. 
    
  102.   it('should warn for getDerivedStateFromProps on a function component', () => {
    
  103.     function FunctionComponentWithChildContext() {
    
  104.       return null;
    
  105.     }
    
  106.     FunctionComponentWithChildContext.getDerivedStateFromProps = function () {};
    
  107. 
    
  108.     const container = document.createElement('div');
    
  109. 
    
  110.     expect(() =>
    
  111.       ReactDOM.render(<FunctionComponentWithChildContext />, container),
    
  112.     ).toErrorDev(
    
  113.       'FunctionComponentWithChildContext: Function ' +
    
  114.         'components do not support getDerivedStateFromProps.',
    
  115.     );
    
  116.   });
    
  117. 
    
  118.   it('should warn for childContextTypes on a function component', () => {
    
  119.     function FunctionComponentWithChildContext(props) {
    
  120.       return <div>{props.name}</div>;
    
  121.     }
    
  122. 
    
  123.     FunctionComponentWithChildContext.childContextTypes = {
    
  124.       foo: PropTypes.string,
    
  125.     };
    
  126. 
    
  127.     const container = document.createElement('div');
    
  128. 
    
  129.     expect(() =>
    
  130.       ReactDOM.render(
    
  131.         <FunctionComponentWithChildContext name="A" />,
    
  132.         container,
    
  133.       ),
    
  134.     ).toErrorDev(
    
  135.       'FunctionComponentWithChildContext(...): childContextTypes cannot ' +
    
  136.         'be defined on a function component.',
    
  137.     );
    
  138.   });
    
  139. 
    
  140.   it('should not throw when stateless component returns undefined', () => {
    
  141.     function NotAComponent() {}
    
  142.     expect(function () {
    
  143.       ReactTestUtils.renderIntoDocument(
    
  144.         <div>
    
  145.           <NotAComponent />
    
  146.         </div>,
    
  147.       );
    
  148.     }).not.toThrowError();
    
  149.   });
    
  150. 
    
  151.   it('should throw on string refs in pure functions', () => {
    
  152.     function Child() {
    
  153.       return <div ref="me" />;
    
  154.     }
    
  155. 
    
  156.     expect(function () {
    
  157.       ReactTestUtils.renderIntoDocument(<Child test="test" />);
    
  158.     }).toThrowError(
    
  159.       __DEV__
    
  160.         ? 'Function components cannot have string refs. We recommend using useRef() instead.'
    
  161.         : // It happens because we don't save _owner in production for
    
  162.           // function components.
    
  163.           'Element ref was specified as a string (me) but no owner was set. This could happen for one of' +
    
  164.             ' the following reasons:\n' +
    
  165.             '1. You may be adding a ref to a function component\n' +
    
  166.             "2. You may be adding a ref to a component that was not created inside a component's render method\n" +
    
  167.             '3. You have multiple copies of React loaded\n' +
    
  168.             'See https://reactjs.org/link/refs-must-have-owner for more information.',
    
  169.     );
    
  170.   });
    
  171. 
    
  172.   it('should warn when given a string ref', () => {
    
  173.     function Indirection(props) {
    
  174.       return <div>{props.children}</div>;
    
  175.     }
    
  176. 
    
  177.     class ParentUsingStringRef extends React.Component {
    
  178.       render() {
    
  179.         return (
    
  180.           <Indirection>
    
  181.             <FunctionComponent name="A" ref="stateless" />
    
  182.           </Indirection>
    
  183.         );
    
  184.       }
    
  185.     }
    
  186. 
    
  187.     expect(() =>
    
  188.       ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />),
    
  189.     ).toErrorDev(
    
  190.       'Warning: Function components cannot be given refs. ' +
    
  191.         'Attempts to access this ref will fail. ' +
    
  192.         'Did you mean to use React.forwardRef()?\n\n' +
    
  193.         'Check the render method ' +
    
  194.         'of `ParentUsingStringRef`.\n' +
    
  195.         '    in FunctionComponent (at **)\n' +
    
  196.         '    in div (at **)\n' +
    
  197.         '    in Indirection (at **)\n' +
    
  198.         '    in ParentUsingStringRef (at **)',
    
  199.     );
    
  200. 
    
  201.     // No additional warnings should be logged
    
  202.     ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />);
    
  203.   });
    
  204. 
    
  205.   it('should warn when given a function ref', () => {
    
  206.     function Indirection(props) {
    
  207.       return <div>{props.children}</div>;
    
  208.     }
    
  209. 
    
  210.     class ParentUsingFunctionRef extends React.Component {
    
  211.       render() {
    
  212.         return (
    
  213.           <Indirection>
    
  214.             <FunctionComponent
    
  215.               name="A"
    
  216.               ref={arg => {
    
  217.                 expect(arg).toBe(null);
    
  218.               }}
    
  219.             />
    
  220.           </Indirection>
    
  221.         );
    
  222.       }
    
  223.     }
    
  224. 
    
  225.     expect(() =>
    
  226.       ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />),
    
  227.     ).toErrorDev(
    
  228.       'Warning: Function components cannot be given refs. ' +
    
  229.         'Attempts to access this ref will fail. ' +
    
  230.         'Did you mean to use React.forwardRef()?\n\n' +
    
  231.         'Check the render method ' +
    
  232.         'of `ParentUsingFunctionRef`.\n' +
    
  233.         '    in FunctionComponent (at **)\n' +
    
  234.         '    in div (at **)\n' +
    
  235.         '    in Indirection (at **)\n' +
    
  236.         '    in ParentUsingFunctionRef (at **)',
    
  237.     );
    
  238. 
    
  239.     // No additional warnings should be logged
    
  240.     ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />);
    
  241.   });
    
  242. 
    
  243.   it('deduplicates ref warnings based on element or owner', () => {
    
  244.     // When owner uses JSX, we can use exact line location to dedupe warnings
    
  245.     class AnonymousParentUsingJSX extends React.Component {
    
  246.       render() {
    
  247.         return <FunctionComponent name="A" ref={() => {}} />;
    
  248.       }
    
  249.     }
    
  250.     Object.defineProperty(AnonymousParentUsingJSX, 'name', {value: undefined});
    
  251. 
    
  252.     let instance1;
    
  253. 
    
  254.     expect(() => {
    
  255.       instance1 = ReactTestUtils.renderIntoDocument(
    
  256.         <AnonymousParentUsingJSX />,
    
  257.       );
    
  258.     }).toErrorDev('Warning: Function components cannot be given refs.');
    
  259.     // Should be deduped (offending element is on the same line):
    
  260.     instance1.forceUpdate();
    
  261.     // Should also be deduped (offending element is on the same line):
    
  262.     ReactTestUtils.renderIntoDocument(<AnonymousParentUsingJSX />);
    
  263. 
    
  264.     // When owner doesn't use JSX, and is anonymous, we warn once per internal instance.
    
  265.     class AnonymousParentNotUsingJSX extends React.Component {
    
  266.       render() {
    
  267.         return React.createElement(FunctionComponent, {
    
  268.           name: 'A',
    
  269.           ref: () => {},
    
  270.         });
    
  271.       }
    
  272.     }
    
  273.     Object.defineProperty(AnonymousParentNotUsingJSX, 'name', {
    
  274.       value: undefined,
    
  275.     });
    
  276. 
    
  277.     let instance2;
    
  278.     expect(() => {
    
  279.       instance2 = ReactTestUtils.renderIntoDocument(
    
  280.         <AnonymousParentNotUsingJSX />,
    
  281.       );
    
  282.     }).toErrorDev('Warning: Function components cannot be given refs.');
    
  283.     // Should be deduped (same internal instance, no additional warnings)
    
  284.     instance2.forceUpdate();
    
  285.     // Could not be differentiated (since owner is anonymous and no source location)
    
  286.     ReactTestUtils.renderIntoDocument(<AnonymousParentNotUsingJSX />);
    
  287. 
    
  288.     // When owner doesn't use JSX, but is named, we warn once per owner name
    
  289.     class NamedParentNotUsingJSX extends React.Component {
    
  290.       render() {
    
  291.         return React.createElement(FunctionComponent, {
    
  292.           name: 'A',
    
  293.           ref: () => {},
    
  294.         });
    
  295.       }
    
  296.     }
    
  297.     let instance3;
    
  298.     expect(() => {
    
  299.       instance3 = ReactTestUtils.renderIntoDocument(<NamedParentNotUsingJSX />);
    
  300.     }).toErrorDev('Warning: Function components cannot be given refs.');
    
  301.     // Should be deduped (same owner name, no additional warnings):
    
  302.     instance3.forceUpdate();
    
  303.     // Should also be deduped (same owner name, no additional warnings):
    
  304.     ReactTestUtils.renderIntoDocument(<NamedParentNotUsingJSX />);
    
  305.   });
    
  306. 
    
  307.   // This guards against a regression caused by clearing the current debug fiber.
    
  308.   // https://github.com/facebook/react/issues/10831
    
  309.   // @gate !disableLegacyContext || !__DEV__
    
  310.   it('should warn when giving a function ref with context', () => {
    
  311.     function Child() {
    
  312.       return null;
    
  313.     }
    
  314.     Child.contextTypes = {
    
  315.       foo: PropTypes.string,
    
  316.     };
    
  317. 
    
  318.     class Parent extends React.Component {
    
  319.       static childContextTypes = {
    
  320.         foo: PropTypes.string,
    
  321.       };
    
  322.       getChildContext() {
    
  323.         return {
    
  324.           foo: 'bar',
    
  325.         };
    
  326.       }
    
  327.       render() {
    
  328.         return <Child ref={function () {}} />;
    
  329.       }
    
  330.     }
    
  331. 
    
  332.     expect(() => ReactTestUtils.renderIntoDocument(<Parent />)).toErrorDev(
    
  333.       'Warning: Function components cannot be given refs. ' +
    
  334.         'Attempts to access this ref will fail. ' +
    
  335.         'Did you mean to use React.forwardRef()?\n\n' +
    
  336.         'Check the render method ' +
    
  337.         'of `Parent`.\n' +
    
  338.         '    in Child (at **)\n' +
    
  339.         '    in Parent (at **)',
    
  340.     );
    
  341.   });
    
  342. 
    
  343.   it('should provide a null ref', () => {
    
  344.     function Child() {
    
  345.       return <div />;
    
  346.     }
    
  347. 
    
  348.     const comp = ReactTestUtils.renderIntoDocument(<Child />);
    
  349.     expect(comp).toBe(null);
    
  350.   });
    
  351. 
    
  352.   it('should use correct name in key warning', () => {
    
  353.     function Child() {
    
  354.       return <div>{[<span />]}</div>;
    
  355.     }
    
  356. 
    
  357.     expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toErrorDev(
    
  358.       'Each child in a list should have a unique "key" prop.\n\n' +
    
  359.         'Check the render method of `Child`.',
    
  360.     );
    
  361.   });
    
  362. 
    
  363.   // TODO: change this test after we deprecate default props support
    
  364.   // for function components
    
  365.   it('should support default props and prop types', () => {
    
  366.     function Child(props) {
    
  367.       return <div>{props.test}</div>;
    
  368.     }
    
  369.     Child.defaultProps = {test: 2};
    
  370.     Child.propTypes = {test: PropTypes.string};
    
  371. 
    
  372.     expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toErrorDev([
    
  373.       'Warning: Child: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
    
  374.       'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
    
  375.         'supplied to `Child`, expected `string`.\n' +
    
  376.         '    in Child (at **)',
    
  377.     ]);
    
  378.   });
    
  379. 
    
  380.   // @gate !disableLegacyContext
    
  381.   it('should receive context', () => {
    
  382.     class Parent extends React.Component {
    
  383.       static childContextTypes = {
    
  384.         lang: PropTypes.string,
    
  385.       };
    
  386. 
    
  387.       getChildContext() {
    
  388.         return {lang: 'en'};
    
  389.       }
    
  390. 
    
  391.       render() {
    
  392.         return <Child />;
    
  393.       }
    
  394.     }
    
  395. 
    
  396.     function Child(props, context) {
    
  397.       return <div>{context.lang}</div>;
    
  398.     }
    
  399.     Child.contextTypes = {lang: PropTypes.string};
    
  400. 
    
  401.     const el = document.createElement('div');
    
  402.     ReactDOM.render(<Parent />, el);
    
  403.     expect(el.textContent).toBe('en');
    
  404.   });
    
  405. 
    
  406.   it('should work with arrow functions', () => {
    
  407.     let Child = function () {
    
  408.       return <div />;
    
  409.     };
    
  410.     // Will create a new bound function without a prototype, much like a native
    
  411.     // arrow function.
    
  412.     Child = Child.bind(this);
    
  413. 
    
  414.     expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
    
  415.   });
    
  416. 
    
  417.   it('should allow simple functions to return null', () => {
    
  418.     const Child = function () {
    
  419.       return null;
    
  420.     };
    
  421.     expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
    
  422.   });
    
  423. 
    
  424.   it('should allow simple functions to return false', () => {
    
  425.     function Child() {
    
  426.       return false;
    
  427.     }
    
  428.     expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
    
  429.   });
    
  430. });