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 TogglingComponent;
    
  16. 
    
  17. let log;
    
  18. 
    
  19. describe('ReactEmptyComponent', () => {
    
  20.   beforeEach(() => {
    
  21.     jest.resetModules();
    
  22. 
    
  23.     React = require('react');
    
  24.     ReactDOM = require('react-dom');
    
  25.     ReactTestUtils = require('react-dom/test-utils');
    
  26. 
    
  27.     log = jest.fn();
    
  28. 
    
  29.     TogglingComponent = class extends React.Component {
    
  30.       state = {component: this.props.firstComponent};
    
  31. 
    
  32.       componentDidMount() {
    
  33.         log(ReactDOM.findDOMNode(this));
    
  34.         this.setState({component: this.props.secondComponent});
    
  35.       }
    
  36. 
    
  37.       componentDidUpdate() {
    
  38.         log(ReactDOM.findDOMNode(this));
    
  39.       }
    
  40. 
    
  41.       render() {
    
  42.         const Component = this.state.component;
    
  43.         return Component ? <Component /> : null;
    
  44.       }
    
  45.     };
    
  46.   });
    
  47. 
    
  48.   describe.each([null, undefined])('when %s', nullORUndefined => {
    
  49.     it('should not throw when rendering', () => {
    
  50.       class Component extends React.Component {
    
  51.         render() {
    
  52.           return nullORUndefined;
    
  53.         }
    
  54.       }
    
  55. 
    
  56.       expect(function () {
    
  57.         ReactTestUtils.renderIntoDocument(<Component />);
    
  58.       }).not.toThrowError();
    
  59.     });
    
  60. 
    
  61.     it('should not produce child DOM nodes for nullish and false', () => {
    
  62.       class Component1 extends React.Component {
    
  63.         render() {
    
  64.           return nullORUndefined;
    
  65.         }
    
  66.       }
    
  67. 
    
  68.       class Component2 extends React.Component {
    
  69.         render() {
    
  70.           return false;
    
  71.         }
    
  72.       }
    
  73. 
    
  74.       const container1 = document.createElement('div');
    
  75.       ReactDOM.render(<Component1 />, container1);
    
  76.       expect(container1.children.length).toBe(0);
    
  77. 
    
  78.       const container2 = document.createElement('div');
    
  79.       ReactDOM.render(<Component2 />, container2);
    
  80.       expect(container2.children.length).toBe(0);
    
  81.     });
    
  82. 
    
  83.     it('should be able to switch between rendering nullish and a normal tag', () => {
    
  84.       const instance1 = (
    
  85.         <TogglingComponent
    
  86.           firstComponent={nullORUndefined}
    
  87.           secondComponent={'div'}
    
  88.         />
    
  89.       );
    
  90.       const instance2 = (
    
  91.         <TogglingComponent
    
  92.           firstComponent={'div'}
    
  93.           secondComponent={nullORUndefined}
    
  94.         />
    
  95.       );
    
  96. 
    
  97.       ReactTestUtils.renderIntoDocument(instance1);
    
  98.       ReactTestUtils.renderIntoDocument(instance2);
    
  99. 
    
  100.       expect(log).toHaveBeenCalledTimes(4);
    
  101.       expect(log).toHaveBeenNthCalledWith(1, null);
    
  102.       expect(log).toHaveBeenNthCalledWith(
    
  103.         2,
    
  104.         expect.objectContaining({tagName: 'DIV'}),
    
  105.       );
    
  106.       expect(log).toHaveBeenNthCalledWith(
    
  107.         3,
    
  108.         expect.objectContaining({tagName: 'DIV'}),
    
  109.       );
    
  110.       expect(log).toHaveBeenNthCalledWith(4, null);
    
  111.     });
    
  112. 
    
  113.     it('should be able to switch in a list of children', () => {
    
  114.       const instance1 = (
    
  115.         <TogglingComponent
    
  116.           firstComponent={nullORUndefined}
    
  117.           secondComponent={'div'}
    
  118.         />
    
  119.       );
    
  120. 
    
  121.       ReactTestUtils.renderIntoDocument(
    
  122.         <div>
    
  123.           {instance1}
    
  124.           {instance1}
    
  125.           {instance1}
    
  126.         </div>,
    
  127.       );
    
  128. 
    
  129.       expect(log).toHaveBeenCalledTimes(6);
    
  130.       expect(log).toHaveBeenNthCalledWith(1, null);
    
  131.       expect(log).toHaveBeenNthCalledWith(2, null);
    
  132.       expect(log).toHaveBeenNthCalledWith(3, null);
    
  133.       expect(log).toHaveBeenNthCalledWith(
    
  134.         4,
    
  135.         expect.objectContaining({tagName: 'DIV'}),
    
  136.       );
    
  137.       expect(log).toHaveBeenNthCalledWith(
    
  138.         5,
    
  139.         expect.objectContaining({tagName: 'DIV'}),
    
  140.       );
    
  141.       expect(log).toHaveBeenNthCalledWith(
    
  142.         6,
    
  143.         expect.objectContaining({tagName: 'DIV'}),
    
  144.       );
    
  145.     });
    
  146. 
    
  147.     it('should distinguish between a script placeholder and an actual script tag', () => {
    
  148.       const instance1 = (
    
  149.         <TogglingComponent
    
  150.           firstComponent={nullORUndefined}
    
  151.           secondComponent={'script'}
    
  152.         />
    
  153.       );
    
  154.       const instance2 = (
    
  155.         <TogglingComponent
    
  156.           firstComponent={'script'}
    
  157.           secondComponent={nullORUndefined}
    
  158.         />
    
  159.       );
    
  160. 
    
  161.       expect(function () {
    
  162.         ReactTestUtils.renderIntoDocument(instance1);
    
  163.       }).not.toThrow();
    
  164.       expect(function () {
    
  165.         ReactTestUtils.renderIntoDocument(instance2);
    
  166.       }).not.toThrow();
    
  167. 
    
  168.       expect(log).toHaveBeenCalledTimes(4);
    
  169.       expect(log).toHaveBeenNthCalledWith(1, null);
    
  170.       expect(log).toHaveBeenNthCalledWith(
    
  171.         2,
    
  172.         expect.objectContaining({tagName: 'SCRIPT'}),
    
  173.       );
    
  174.       expect(log).toHaveBeenNthCalledWith(
    
  175.         3,
    
  176.         expect.objectContaining({tagName: 'SCRIPT'}),
    
  177.       );
    
  178.       expect(log).toHaveBeenNthCalledWith(4, null);
    
  179.     });
    
  180. 
    
  181.     it(
    
  182.       'should have findDOMNode return null when multiple layers of composite ' +
    
  183.         'components render to the same nullish placeholder',
    
  184.       () => {
    
  185.         class GrandChild extends React.Component {
    
  186.           render() {
    
  187.             return nullORUndefined;
    
  188.           }
    
  189.         }
    
  190. 
    
  191.         class Child extends React.Component {
    
  192.           render() {
    
  193.             return <GrandChild />;
    
  194.           }
    
  195.         }
    
  196. 
    
  197.         const instance1 = (
    
  198.           <TogglingComponent firstComponent={'div'} secondComponent={Child} />
    
  199.         );
    
  200.         const instance2 = (
    
  201.           <TogglingComponent firstComponent={Child} secondComponent={'div'} />
    
  202.         );
    
  203. 
    
  204.         expect(function () {
    
  205.           ReactTestUtils.renderIntoDocument(instance1);
    
  206.         }).not.toThrow();
    
  207.         expect(function () {
    
  208.           ReactTestUtils.renderIntoDocument(instance2);
    
  209.         }).not.toThrow();
    
  210. 
    
  211.         expect(log).toHaveBeenCalledTimes(4);
    
  212.         expect(log).toHaveBeenNthCalledWith(
    
  213.           1,
    
  214.           expect.objectContaining({tagName: 'DIV'}),
    
  215.         );
    
  216.         expect(log).toHaveBeenNthCalledWith(2, null);
    
  217.         expect(log).toHaveBeenNthCalledWith(3, null);
    
  218.         expect(log).toHaveBeenNthCalledWith(
    
  219.           4,
    
  220.           expect.objectContaining({tagName: 'DIV'}),
    
  221.         );
    
  222.       },
    
  223.     );
    
  224. 
    
  225.     it('works when switching components', () => {
    
  226.       let assertions = 0;
    
  227. 
    
  228.       class Inner extends React.Component {
    
  229.         render() {
    
  230.           return <span />;
    
  231.         }
    
  232. 
    
  233.         componentDidMount() {
    
  234.           // Make sure the DOM node resolves properly even if we're replacing a
    
  235.           // `null` component
    
  236.           expect(ReactDOM.findDOMNode(this)).not.toBe(null);
    
  237.           assertions++;
    
  238.         }
    
  239. 
    
  240.         componentWillUnmount() {
    
  241.           // Even though we're getting replaced by `null`, we haven't been
    
  242.           // replaced yet!
    
  243.           expect(ReactDOM.findDOMNode(this)).not.toBe(null);
    
  244.           assertions++;
    
  245.         }
    
  246.       }
    
  247. 
    
  248.       class Wrapper extends React.Component {
    
  249.         render() {
    
  250.           return this.props.showInner ? <Inner /> : nullORUndefined;
    
  251.         }
    
  252.       }
    
  253. 
    
  254.       const el = document.createElement('div');
    
  255.       let component;
    
  256. 
    
  257.       // Render the <Inner /> component...
    
  258.       component = ReactDOM.render(<Wrapper showInner={true} />, el);
    
  259.       expect(ReactDOM.findDOMNode(component)).not.toBe(null);
    
  260. 
    
  261.       // Switch to null...
    
  262.       component = ReactDOM.render(<Wrapper showInner={false} />, el);
    
  263.       expect(ReactDOM.findDOMNode(component)).toBe(null);
    
  264. 
    
  265.       // ...then switch back.
    
  266.       component = ReactDOM.render(<Wrapper showInner={true} />, el);
    
  267.       expect(ReactDOM.findDOMNode(component)).not.toBe(null);
    
  268. 
    
  269.       expect(assertions).toBe(3);
    
  270.     });
    
  271. 
    
  272.     it('can render nullish at the top level', () => {
    
  273.       const div = document.createElement('div');
    
  274.       ReactDOM.render(nullORUndefined, div);
    
  275.       expect(div.innerHTML).toBe('');
    
  276.     });
    
  277. 
    
  278.     it('does not break when updating during mount', () => {
    
  279.       class Child extends React.Component {
    
  280.         componentDidMount() {
    
  281.           if (this.props.onMount) {
    
  282.             this.props.onMount();
    
  283.           }
    
  284.         }
    
  285. 
    
  286.         render() {
    
  287.           if (!this.props.visible) {
    
  288.             return nullORUndefined;
    
  289.           }
    
  290. 
    
  291.           return <div>hello world</div>;
    
  292.         }
    
  293.       }
    
  294. 
    
  295.       class Parent extends React.Component {
    
  296.         update = () => {
    
  297.           this.forceUpdate();
    
  298.         };
    
  299. 
    
  300.         render() {
    
  301.           return (
    
  302.             <div>
    
  303.               <Child key="1" visible={false} />
    
  304.               <Child key="0" visible={true} onMount={this.update} />
    
  305.               <Child key="2" visible={false} />
    
  306.             </div>
    
  307.           );
    
  308.         }
    
  309.       }
    
  310. 
    
  311.       expect(function () {
    
  312.         ReactTestUtils.renderIntoDocument(<Parent />);
    
  313.       }).not.toThrow();
    
  314.     });
    
  315. 
    
  316.     it('preserves the dom node during updates', () => {
    
  317.       class Empty extends React.Component {
    
  318.         render() {
    
  319.           return nullORUndefined;
    
  320.         }
    
  321.       }
    
  322. 
    
  323.       const container = document.createElement('div');
    
  324. 
    
  325.       ReactDOM.render(<Empty />, container);
    
  326.       const noscript1 = container.firstChild;
    
  327.       expect(noscript1).toBe(null);
    
  328. 
    
  329.       // This update shouldn't create a DOM node
    
  330.       ReactDOM.render(<Empty />, container);
    
  331.       const noscript2 = container.firstChild;
    
  332.       expect(noscript2).toBe(null);
    
  333.     });
    
  334. 
    
  335.     it('should not warn about React.forwardRef that returns nullish', () => {
    
  336.       const Empty = () => {
    
  337.         return nullORUndefined;
    
  338.       };
    
  339.       const EmptyForwardRef = React.forwardRef(Empty);
    
  340. 
    
  341.       expect(() => {
    
  342.         ReactTestUtils.renderIntoDocument(<EmptyForwardRef />);
    
  343.       }).not.toThrowError();
    
  344.     });
    
  345. 
    
  346.     it('should not warn about React.memo that returns nullish', () => {
    
  347.       const Empty = () => {
    
  348.         return nullORUndefined;
    
  349.       };
    
  350.       const EmptyMemo = React.memo(Empty);
    
  351. 
    
  352.       expect(() => {
    
  353.         ReactTestUtils.renderIntoDocument(<EmptyMemo />);
    
  354.       }).not.toThrowError();
    
  355.     });
    
  356.   });
    
  357. });