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 React = require('react');
    
  13. const ReactDOM = require('react-dom');
    
  14. const PropTypes = require('prop-types');
    
  15. 
    
  16. describe('ReactDOMFiber', () => {
    
  17.   let container;
    
  18. 
    
  19.   beforeEach(() => {
    
  20.     container = document.createElement('div');
    
  21.     document.body.appendChild(container);
    
  22.   });
    
  23. 
    
  24.   afterEach(() => {
    
  25.     document.body.removeChild(container);
    
  26.     container = null;
    
  27.     jest.restoreAllMocks();
    
  28.   });
    
  29. 
    
  30.   it('should render strings as children', () => {
    
  31.     const Box = ({value}) => <div>{value}</div>;
    
  32. 
    
  33.     ReactDOM.render(<Box value="foo" />, container);
    
  34.     expect(container.textContent).toEqual('foo');
    
  35.   });
    
  36. 
    
  37.   it('should render numbers as children', () => {
    
  38.     const Box = ({value}) => <div>{value}</div>;
    
  39. 
    
  40.     ReactDOM.render(<Box value={10} />, container);
    
  41. 
    
  42.     expect(container.textContent).toEqual('10');
    
  43.   });
    
  44. 
    
  45.   it('should be called a callback argument', () => {
    
  46.     // mounting phase
    
  47.     let called = false;
    
  48.     ReactDOM.render(<div>Foo</div>, container, () => (called = true));
    
  49.     expect(called).toEqual(true);
    
  50. 
    
  51.     // updating phase
    
  52.     called = false;
    
  53.     ReactDOM.render(<div>Foo</div>, container, () => (called = true));
    
  54.     expect(called).toEqual(true);
    
  55.   });
    
  56. 
    
  57.   it('should call a callback argument when the same element is re-rendered', () => {
    
  58.     class Foo extends React.Component {
    
  59.       render() {
    
  60.         return <div>Foo</div>;
    
  61.       }
    
  62.     }
    
  63.     const element = <Foo />;
    
  64. 
    
  65.     // mounting phase
    
  66.     let called = false;
    
  67.     ReactDOM.render(element, container, () => (called = true));
    
  68.     expect(called).toEqual(true);
    
  69. 
    
  70.     // updating phase
    
  71.     called = false;
    
  72.     ReactDOM.unstable_batchedUpdates(() => {
    
  73.       ReactDOM.render(element, container, () => (called = true));
    
  74.     });
    
  75.     expect(called).toEqual(true);
    
  76.   });
    
  77. 
    
  78.   it('should render a component returning strings directly from render', () => {
    
  79.     const Text = ({value}) => value;
    
  80. 
    
  81.     ReactDOM.render(<Text value="foo" />, container);
    
  82.     expect(container.textContent).toEqual('foo');
    
  83.   });
    
  84. 
    
  85.   it('should render a component returning numbers directly from render', () => {
    
  86.     const Text = ({value}) => value;
    
  87. 
    
  88.     ReactDOM.render(<Text value={10} />, container);
    
  89. 
    
  90.     expect(container.textContent).toEqual('10');
    
  91.   });
    
  92. 
    
  93.   it('finds the DOM Text node of a string child', () => {
    
  94.     class Text extends React.Component {
    
  95.       render() {
    
  96.         return this.props.value;
    
  97.       }
    
  98.     }
    
  99. 
    
  100.     let instance = null;
    
  101.     ReactDOM.render(
    
  102.       <Text value="foo" ref={ref => (instance = ref)} />,
    
  103.       container,
    
  104.     );
    
  105. 
    
  106.     const textNode = ReactDOM.findDOMNode(instance);
    
  107.     expect(textNode).toBe(container.firstChild);
    
  108.     expect(textNode.nodeType).toBe(3);
    
  109.     expect(textNode.nodeValue).toBe('foo');
    
  110.   });
    
  111. 
    
  112.   it('finds the first child when a component returns a fragment', () => {
    
  113.     class Fragment extends React.Component {
    
  114.       render() {
    
  115.         return [<div key="a" />, <span key="b" />];
    
  116.       }
    
  117.     }
    
  118. 
    
  119.     let instance = null;
    
  120.     ReactDOM.render(<Fragment ref={ref => (instance = ref)} />, container);
    
  121. 
    
  122.     expect(container.childNodes.length).toBe(2);
    
  123. 
    
  124.     const firstNode = ReactDOM.findDOMNode(instance);
    
  125.     expect(firstNode).toBe(container.firstChild);
    
  126.     expect(firstNode.tagName).toBe('DIV');
    
  127.   });
    
  128. 
    
  129.   it('finds the first child even when fragment is nested', () => {
    
  130.     class Wrapper extends React.Component {
    
  131.       render() {
    
  132.         return this.props.children;
    
  133.       }
    
  134.     }
    
  135. 
    
  136.     class Fragment extends React.Component {
    
  137.       render() {
    
  138.         return [
    
  139.           <Wrapper key="a">
    
  140.             <div />
    
  141.           </Wrapper>,
    
  142.           <span key="b" />,
    
  143.         ];
    
  144.       }
    
  145.     }
    
  146. 
    
  147.     let instance = null;
    
  148.     ReactDOM.render(<Fragment ref={ref => (instance = ref)} />, container);
    
  149. 
    
  150.     expect(container.childNodes.length).toBe(2);
    
  151. 
    
  152.     const firstNode = ReactDOM.findDOMNode(instance);
    
  153.     expect(firstNode).toBe(container.firstChild);
    
  154.     expect(firstNode.tagName).toBe('DIV');
    
  155.   });
    
  156. 
    
  157.   it('finds the first child even when first child renders null', () => {
    
  158.     class NullComponent extends React.Component {
    
  159.       render() {
    
  160.         return null;
    
  161.       }
    
  162.     }
    
  163. 
    
  164.     class Fragment extends React.Component {
    
  165.       render() {
    
  166.         return [<NullComponent key="a" />, <div key="b" />, <span key="c" />];
    
  167.       }
    
  168.     }
    
  169. 
    
  170.     let instance = null;
    
  171.     ReactDOM.render(<Fragment ref={ref => (instance = ref)} />, container);
    
  172. 
    
  173.     expect(container.childNodes.length).toBe(2);
    
  174. 
    
  175.     const firstNode = ReactDOM.findDOMNode(instance);
    
  176.     expect(firstNode).toBe(container.firstChild);
    
  177.     expect(firstNode.tagName).toBe('DIV');
    
  178.   });
    
  179. 
    
  180.   it('renders an empty fragment', () => {
    
  181.     const Div = () => <div />;
    
  182.     const EmptyFragment = () => <></>;
    
  183.     const NonEmptyFragment = () => (
    
  184.       <>
    
  185.         <Div />
    
  186.       </>
    
  187.     );
    
  188. 
    
  189.     ReactDOM.render(<EmptyFragment />, container);
    
  190.     expect(container.firstChild).toBe(null);
    
  191. 
    
  192.     ReactDOM.render(<NonEmptyFragment />, container);
    
  193.     expect(container.firstChild.tagName).toBe('DIV');
    
  194. 
    
  195.     ReactDOM.render(<EmptyFragment />, container);
    
  196.     expect(container.firstChild).toBe(null);
    
  197. 
    
  198.     ReactDOM.render(<Div />, container);
    
  199.     expect(container.firstChild.tagName).toBe('DIV');
    
  200. 
    
  201.     ReactDOM.render(<EmptyFragment />, container);
    
  202.     expect(container.firstChild).toBe(null);
    
  203.   });
    
  204. 
    
  205.   let svgEls, htmlEls, mathEls;
    
  206.   const expectSVG = {ref: el => svgEls.push(el)};
    
  207.   const expectHTML = {ref: el => htmlEls.push(el)};
    
  208.   const expectMath = {ref: el => mathEls.push(el)};
    
  209. 
    
  210.   const usePortal = function (tree) {
    
  211.     return ReactDOM.createPortal(tree, document.createElement('div'));
    
  212.   };
    
  213. 
    
  214.   const assertNamespacesMatch = function (tree) {
    
  215.     const testContainer = document.createElement('div');
    
  216.     svgEls = [];
    
  217.     htmlEls = [];
    
  218.     mathEls = [];
    
  219. 
    
  220.     ReactDOM.render(tree, testContainer);
    
  221.     svgEls.forEach(el => {
    
  222.       expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
    
  223.     });
    
  224.     htmlEls.forEach(el => {
    
  225.       expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
    
  226.     });
    
  227.     mathEls.forEach(el => {
    
  228.       expect(el.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML');
    
  229.     });
    
  230. 
    
  231.     ReactDOM.unmountComponentAtNode(testContainer);
    
  232.     expect(testContainer.innerHTML).toBe('');
    
  233.   };
    
  234. 
    
  235.   it('should render one portal', () => {
    
  236.     const portalContainer = document.createElement('div');
    
  237. 
    
  238.     ReactDOM.render(
    
  239.       <div>{ReactDOM.createPortal(<div>portal</div>, portalContainer)}</div>,
    
  240.       container,
    
  241.     );
    
  242.     expect(portalContainer.innerHTML).toBe('<div>portal</div>');
    
  243.     expect(container.innerHTML).toBe('<div></div>');
    
  244. 
    
  245.     ReactDOM.unmountComponentAtNode(container);
    
  246.     expect(portalContainer.innerHTML).toBe('');
    
  247.     expect(container.innerHTML).toBe('');
    
  248.   });
    
  249. 
    
  250.   it('should render many portals', () => {
    
  251.     const portalContainer1 = document.createElement('div');
    
  252.     const portalContainer2 = document.createElement('div');
    
  253. 
    
  254.     const ops = [];
    
  255.     class Child extends React.Component {
    
  256.       componentDidMount() {
    
  257.         ops.push(`${this.props.name} componentDidMount`);
    
  258.       }
    
  259.       componentDidUpdate() {
    
  260.         ops.push(`${this.props.name} componentDidUpdate`);
    
  261.       }
    
  262.       componentWillUnmount() {
    
  263.         ops.push(`${this.props.name} componentWillUnmount`);
    
  264.       }
    
  265.       render() {
    
  266.         return <div>{this.props.name}</div>;
    
  267.       }
    
  268.     }
    
  269. 
    
  270.     class Parent extends React.Component {
    
  271.       componentDidMount() {
    
  272.         ops.push(`Parent:${this.props.step} componentDidMount`);
    
  273.       }
    
  274.       componentDidUpdate() {
    
  275.         ops.push(`Parent:${this.props.step} componentDidUpdate`);
    
  276.       }
    
  277.       componentWillUnmount() {
    
  278.         ops.push(`Parent:${this.props.step} componentWillUnmount`);
    
  279.       }
    
  280.       render() {
    
  281.         const {step} = this.props;
    
  282.         return [
    
  283.           <Child key="a" name={`normal[0]:${step}`} />,
    
  284.           ReactDOM.createPortal(
    
  285.             <Child key="b" name={`portal1[0]:${step}`} />,
    
  286.             portalContainer1,
    
  287.           ),
    
  288.           <Child key="c" name={`normal[1]:${step}`} />,
    
  289.           ReactDOM.createPortal(
    
  290.             [
    
  291.               <Child key="d" name={`portal2[0]:${step}`} />,
    
  292.               <Child key="e" name={`portal2[1]:${step}`} />,
    
  293.             ],
    
  294.             portalContainer2,
    
  295.           ),
    
  296.         ];
    
  297.       }
    
  298.     }
    
  299. 
    
  300.     ReactDOM.render(<Parent step="a" />, container);
    
  301.     expect(portalContainer1.innerHTML).toBe('<div>portal1[0]:a</div>');
    
  302.     expect(portalContainer2.innerHTML).toBe(
    
  303.       '<div>portal2[0]:a</div><div>portal2[1]:a</div>',
    
  304.     );
    
  305.     expect(container.innerHTML).toBe(
    
  306.       '<div>normal[0]:a</div><div>normal[1]:a</div>',
    
  307.     );
    
  308.     expect(ops).toEqual([
    
  309.       'normal[0]:a componentDidMount',
    
  310.       'portal1[0]:a componentDidMount',
    
  311.       'normal[1]:a componentDidMount',
    
  312.       'portal2[0]:a componentDidMount',
    
  313.       'portal2[1]:a componentDidMount',
    
  314.       'Parent:a componentDidMount',
    
  315.     ]);
    
  316. 
    
  317.     ops.length = 0;
    
  318.     ReactDOM.render(<Parent step="b" />, container);
    
  319.     expect(portalContainer1.innerHTML).toBe('<div>portal1[0]:b</div>');
    
  320.     expect(portalContainer2.innerHTML).toBe(
    
  321.       '<div>portal2[0]:b</div><div>portal2[1]:b</div>',
    
  322.     );
    
  323.     expect(container.innerHTML).toBe(
    
  324.       '<div>normal[0]:b</div><div>normal[1]:b</div>',
    
  325.     );
    
  326.     expect(ops).toEqual([
    
  327.       'normal[0]:b componentDidUpdate',
    
  328.       'portal1[0]:b componentDidUpdate',
    
  329.       'normal[1]:b componentDidUpdate',
    
  330.       'portal2[0]:b componentDidUpdate',
    
  331.       'portal2[1]:b componentDidUpdate',
    
  332.       'Parent:b componentDidUpdate',
    
  333.     ]);
    
  334. 
    
  335.     ops.length = 0;
    
  336.     ReactDOM.unmountComponentAtNode(container);
    
  337.     expect(portalContainer1.innerHTML).toBe('');
    
  338.     expect(portalContainer2.innerHTML).toBe('');
    
  339.     expect(container.innerHTML).toBe('');
    
  340.     expect(ops).toEqual([
    
  341.       'Parent:b componentWillUnmount',
    
  342.       'normal[0]:b componentWillUnmount',
    
  343.       'portal1[0]:b componentWillUnmount',
    
  344.       'normal[1]:b componentWillUnmount',
    
  345.       'portal2[0]:b componentWillUnmount',
    
  346.       'portal2[1]:b componentWillUnmount',
    
  347.     ]);
    
  348.   });
    
  349. 
    
  350.   it('should render nested portals', () => {
    
  351.     const portalContainer1 = document.createElement('div');
    
  352.     const portalContainer2 = document.createElement('div');
    
  353.     const portalContainer3 = document.createElement('div');
    
  354. 
    
  355.     ReactDOM.render(
    
  356.       [
    
  357.         <div key="a">normal[0]</div>,
    
  358.         ReactDOM.createPortal(
    
  359.           [
    
  360.             <div key="b">portal1[0]</div>,
    
  361.             ReactDOM.createPortal(
    
  362.               <div key="c">portal2[0]</div>,
    
  363.               portalContainer2,
    
  364.             ),
    
  365.             ReactDOM.createPortal(
    
  366.               <div key="d">portal3[0]</div>,
    
  367.               portalContainer3,
    
  368.             ),
    
  369.             <div key="e">portal1[1]</div>,
    
  370.           ],
    
  371.           portalContainer1,
    
  372.         ),
    
  373.         <div key="f">normal[1]</div>,
    
  374.       ],
    
  375.       container,
    
  376.     );
    
  377.     expect(portalContainer1.innerHTML).toBe(
    
  378.       '<div>portal1[0]</div><div>portal1[1]</div>',
    
  379.     );
    
  380.     expect(portalContainer2.innerHTML).toBe('<div>portal2[0]</div>');
    
  381.     expect(portalContainer3.innerHTML).toBe('<div>portal3[0]</div>');
    
  382.     expect(container.innerHTML).toBe(
    
  383.       '<div>normal[0]</div><div>normal[1]</div>',
    
  384.     );
    
  385. 
    
  386.     ReactDOM.unmountComponentAtNode(container);
    
  387.     expect(portalContainer1.innerHTML).toBe('');
    
  388.     expect(portalContainer2.innerHTML).toBe('');
    
  389.     expect(portalContainer3.innerHTML).toBe('');
    
  390.     expect(container.innerHTML).toBe('');
    
  391.   });
    
  392. 
    
  393.   it('should reconcile portal children', () => {
    
  394.     const portalContainer = document.createElement('div');
    
  395. 
    
  396.     ReactDOM.render(
    
  397.       <div>{ReactDOM.createPortal(<div>portal:1</div>, portalContainer)}</div>,
    
  398.       container,
    
  399.     );
    
  400.     expect(portalContainer.innerHTML).toBe('<div>portal:1</div>');
    
  401.     expect(container.innerHTML).toBe('<div></div>');
    
  402. 
    
  403.     ReactDOM.render(
    
  404.       <div>{ReactDOM.createPortal(<div>portal:2</div>, portalContainer)}</div>,
    
  405.       container,
    
  406.     );
    
  407.     expect(portalContainer.innerHTML).toBe('<div>portal:2</div>');
    
  408.     expect(container.innerHTML).toBe('<div></div>');
    
  409. 
    
  410.     ReactDOM.render(
    
  411.       <div>{ReactDOM.createPortal(<p>portal:3</p>, portalContainer)}</div>,
    
  412.       container,
    
  413.     );
    
  414.     expect(portalContainer.innerHTML).toBe('<p>portal:3</p>');
    
  415.     expect(container.innerHTML).toBe('<div></div>');
    
  416. 
    
  417.     ReactDOM.render(
    
  418.       <div>{ReactDOM.createPortal(['Hi', 'Bye'], portalContainer)}</div>,
    
  419.       container,
    
  420.     );
    
  421.     expect(portalContainer.innerHTML).toBe('HiBye');
    
  422.     expect(container.innerHTML).toBe('<div></div>');
    
  423. 
    
  424.     ReactDOM.render(
    
  425.       <div>{ReactDOM.createPortal(['Bye', 'Hi'], portalContainer)}</div>,
    
  426.       container,
    
  427.     );
    
  428.     expect(portalContainer.innerHTML).toBe('ByeHi');
    
  429.     expect(container.innerHTML).toBe('<div></div>');
    
  430. 
    
  431.     ReactDOM.render(
    
  432.       <div>{ReactDOM.createPortal(null, portalContainer)}</div>,
    
  433.       container,
    
  434.     );
    
  435.     expect(portalContainer.innerHTML).toBe('');
    
  436.     expect(container.innerHTML).toBe('<div></div>');
    
  437.   });
    
  438. 
    
  439.   it('should unmount empty portal component wherever it appears', () => {
    
  440.     const portalContainer = document.createElement('div');
    
  441. 
    
  442.     class Wrapper extends React.Component {
    
  443.       constructor(props) {
    
  444.         super(props);
    
  445.         this.state = {
    
  446.           show: true,
    
  447.         };
    
  448.       }
    
  449.       render() {
    
  450.         return (
    
  451.           <div>
    
  452.             {this.state.show && (
    
  453.               <>
    
  454.                 {ReactDOM.createPortal(null, portalContainer)}
    
  455.                 <div>child</div>
    
  456.               </>
    
  457.             )}
    
  458.             <div>parent</div>
    
  459.           </div>
    
  460.         );
    
  461.       }
    
  462.     }
    
  463. 
    
  464.     const instance = ReactDOM.render(<Wrapper />, container);
    
  465.     expect(container.innerHTML).toBe(
    
  466.       '<div><div>child</div><div>parent</div></div>',
    
  467.     );
    
  468.     instance.setState({show: false});
    
  469.     expect(instance.state.show).toBe(false);
    
  470.     expect(container.innerHTML).toBe('<div><div>parent</div></div>');
    
  471.   });
    
  472. 
    
  473.   it('should keep track of namespace across portals (simple)', () => {
    
  474.     assertNamespacesMatch(
    
  475.       <svg {...expectSVG}>
    
  476.         <image {...expectSVG} />
    
  477.         {usePortal(<div {...expectHTML} />)}
    
  478.         <image {...expectSVG} />
    
  479.       </svg>,
    
  480.     );
    
  481.     assertNamespacesMatch(
    
  482.       <math {...expectMath}>
    
  483.         <mi {...expectMath} />
    
  484.         {usePortal(<div {...expectHTML} />)}
    
  485.         <mi {...expectMath} />
    
  486.       </math>,
    
  487.     );
    
  488.     assertNamespacesMatch(
    
  489.       <div {...expectHTML}>
    
  490.         <p {...expectHTML} />
    
  491.         {usePortal(
    
  492.           <svg {...expectSVG}>
    
  493.             <image {...expectSVG} />
    
  494.           </svg>,
    
  495.         )}
    
  496.         <p {...expectHTML} />
    
  497.       </div>,
    
  498.     );
    
  499.   });
    
  500. 
    
  501.   it('should keep track of namespace across portals (medium)', () => {
    
  502.     assertNamespacesMatch(
    
  503.       <svg {...expectSVG}>
    
  504.         <image {...expectSVG} />
    
  505.         {usePortal(<div {...expectHTML} />)}
    
  506.         <image {...expectSVG} />
    
  507.         {usePortal(<div {...expectHTML} />)}
    
  508.         <image {...expectSVG} />
    
  509.       </svg>,
    
  510.     );
    
  511.     assertNamespacesMatch(
    
  512.       <div {...expectHTML}>
    
  513.         <math {...expectMath}>
    
  514.           <mi {...expectMath} />
    
  515.           {usePortal(
    
  516.             <svg {...expectSVG}>
    
  517.               <image {...expectSVG} />
    
  518.             </svg>,
    
  519.           )}
    
  520.         </math>
    
  521.         <p {...expectHTML} />
    
  522.       </div>,
    
  523.     );
    
  524.     assertNamespacesMatch(
    
  525.       <math {...expectMath}>
    
  526.         <mi {...expectMath} />
    
  527.         {usePortal(
    
  528.           <svg {...expectSVG}>
    
  529.             <image {...expectSVG} />
    
  530.             <foreignObject {...expectSVG}>
    
  531.               <p {...expectHTML} />
    
  532.               <math {...expectMath}>
    
  533.                 <mi {...expectMath} />
    
  534.               </math>
    
  535.               <p {...expectHTML} />
    
  536.             </foreignObject>
    
  537.             <image {...expectSVG} />
    
  538.           </svg>,
    
  539.         )}
    
  540.         <mi {...expectMath} />
    
  541.       </math>,
    
  542.     );
    
  543.     assertNamespacesMatch(
    
  544.       <div {...expectHTML}>
    
  545.         {usePortal(
    
  546.           <svg {...expectSVG}>
    
  547.             {usePortal(<div {...expectHTML} />)}
    
  548.             <image {...expectSVG} />
    
  549.           </svg>,
    
  550.         )}
    
  551.         <p {...expectHTML} />
    
  552.       </div>,
    
  553.     );
    
  554.     assertNamespacesMatch(
    
  555.       <svg {...expectSVG}>
    
  556.         <svg {...expectSVG}>
    
  557.           {usePortal(<div {...expectHTML} />)}
    
  558.           <image {...expectSVG} />
    
  559.         </svg>
    
  560.         <image {...expectSVG} />
    
  561.       </svg>,
    
  562.     );
    
  563.   });
    
  564. 
    
  565.   it('should keep track of namespace across portals (complex)', () => {
    
  566.     assertNamespacesMatch(
    
  567.       <div {...expectHTML}>
    
  568.         {usePortal(
    
  569.           <svg {...expectSVG}>
    
  570.             <image {...expectSVG} />
    
  571.           </svg>,
    
  572.         )}
    
  573.         <p {...expectHTML} />
    
  574.         <svg {...expectSVG}>
    
  575.           <image {...expectSVG} />
    
  576.         </svg>
    
  577.         <svg {...expectSVG}>
    
  578.           <svg {...expectSVG}>
    
  579.             <image {...expectSVG} />
    
  580.           </svg>
    
  581.           <image {...expectSVG} />
    
  582.         </svg>
    
  583.         <p {...expectHTML} />
    
  584.       </div>,
    
  585.     );
    
  586.     assertNamespacesMatch(
    
  587.       <div {...expectHTML}>
    
  588.         <svg {...expectSVG}>
    
  589.           <svg {...expectSVG}>
    
  590.             <image {...expectSVG} />
    
  591.             {usePortal(
    
  592.               <svg {...expectSVG}>
    
  593.                 <image {...expectSVG} />
    
  594.                 <svg {...expectSVG}>
    
  595.                   <image {...expectSVG} />
    
  596.                 </svg>
    
  597.                 <image {...expectSVG} />
    
  598.               </svg>,
    
  599.             )}
    
  600.             <image {...expectSVG} />
    
  601.             <foreignObject {...expectSVG}>
    
  602.               <p {...expectHTML} />
    
  603.               {usePortal(<p {...expectHTML} />)}
    
  604.               <p {...expectHTML} />
    
  605.             </foreignObject>
    
  606.           </svg>
    
  607.           <image {...expectSVG} />
    
  608.         </svg>
    
  609.         <p {...expectHTML} />
    
  610.       </div>,
    
  611.     );
    
  612.     assertNamespacesMatch(
    
  613.       <div {...expectHTML}>
    
  614.         <svg {...expectSVG}>
    
  615.           <foreignObject {...expectSVG}>
    
  616.             <p {...expectHTML} />
    
  617.             {usePortal(
    
  618.               <svg {...expectSVG}>
    
  619.                 <image {...expectSVG} />
    
  620.                 <svg {...expectSVG}>
    
  621.                   <image {...expectSVG} />
    
  622.                   <foreignObject {...expectSVG}>
    
  623.                     <p {...expectHTML} />
    
  624.                   </foreignObject>
    
  625.                   {usePortal(<p {...expectHTML} />)}
    
  626.                 </svg>
    
  627.                 <image {...expectSVG} />
    
  628.               </svg>,
    
  629.             )}
    
  630.             <p {...expectHTML} />
    
  631.           </foreignObject>
    
  632.           <image {...expectSVG} />
    
  633.         </svg>
    
  634.         <p {...expectHTML} />
    
  635.       </div>,
    
  636.     );
    
  637.   });
    
  638. 
    
  639.   it('should unwind namespaces on uncaught errors', () => {
    
  640.     function BrokenRender() {
    
  641.       throw new Error('Hello');
    
  642.     }
    
  643. 
    
  644.     expect(() => {
    
  645.       assertNamespacesMatch(
    
  646.         <svg {...expectSVG}>
    
  647.           <BrokenRender />
    
  648.         </svg>,
    
  649.       );
    
  650.     }).toThrow('Hello');
    
  651.     assertNamespacesMatch(<div {...expectHTML} />);
    
  652.   });
    
  653. 
    
  654.   it('should unwind namespaces on caught errors', () => {
    
  655.     function BrokenRender() {
    
  656.       throw new Error('Hello');
    
  657.     }
    
  658. 
    
  659.     class ErrorBoundary extends React.Component {
    
  660.       state = {error: null};
    
  661.       componentDidCatch(error) {
    
  662.         this.setState({error});
    
  663.       }
    
  664.       render() {
    
  665.         if (this.state.error) {
    
  666.           return <p {...expectHTML} />;
    
  667.         }
    
  668.         return this.props.children;
    
  669.       }
    
  670.     }
    
  671. 
    
  672.     assertNamespacesMatch(
    
  673.       <svg {...expectSVG}>
    
  674.         <foreignObject {...expectSVG}>
    
  675.           <ErrorBoundary>
    
  676.             <math {...expectMath}>
    
  677.               <BrokenRender />
    
  678.             </math>
    
  679.           </ErrorBoundary>
    
  680.         </foreignObject>
    
  681.         <image {...expectSVG} />
    
  682.       </svg>,
    
  683.     );
    
  684.     assertNamespacesMatch(<div {...expectHTML} />);
    
  685.   });
    
  686. 
    
  687.   it('should unwind namespaces on caught errors in a portal', () => {
    
  688.     function BrokenRender() {
    
  689.       throw new Error('Hello');
    
  690.     }
    
  691. 
    
  692.     class ErrorBoundary extends React.Component {
    
  693.       state = {error: null};
    
  694.       componentDidCatch(error) {
    
  695.         this.setState({error});
    
  696.       }
    
  697.       render() {
    
  698.         if (this.state.error) {
    
  699.           return <image {...expectSVG} />;
    
  700.         }
    
  701.         return this.props.children;
    
  702.       }
    
  703.     }
    
  704. 
    
  705.     assertNamespacesMatch(
    
  706.       <svg {...expectSVG}>
    
  707.         <ErrorBoundary>
    
  708.           {usePortal(
    
  709.             <div {...expectHTML}>
    
  710.               <math {...expectMath}>
    
  711.                 <BrokenRender />)
    
  712.               </math>
    
  713.             </div>,
    
  714.           )}
    
  715.         </ErrorBoundary>
    
  716.         {usePortal(<div {...expectHTML} />)}
    
  717.       </svg>,
    
  718.     );
    
  719.   });
    
  720. 
    
  721.   // @gate !disableLegacyContext
    
  722.   it('should pass portal context when rendering subtree elsewhere', () => {
    
  723.     const portalContainer = document.createElement('div');
    
  724. 
    
  725.     class Component extends React.Component {
    
  726.       static contextTypes = {
    
  727.         foo: PropTypes.string.isRequired,
    
  728.       };
    
  729. 
    
  730.       render() {
    
  731.         return <div>{this.context.foo}</div>;
    
  732.       }
    
  733.     }
    
  734. 
    
  735.     class Parent extends React.Component {
    
  736.       static childContextTypes = {
    
  737.         foo: PropTypes.string.isRequired,
    
  738.       };
    
  739. 
    
  740.       getChildContext() {
    
  741.         return {
    
  742.           foo: 'bar',
    
  743.         };
    
  744.       }
    
  745. 
    
  746.       render() {
    
  747.         return ReactDOM.createPortal(<Component />, portalContainer);
    
  748.       }
    
  749.     }
    
  750. 
    
  751.     ReactDOM.render(<Parent />, container);
    
  752.     expect(container.innerHTML).toBe('');
    
  753.     expect(portalContainer.innerHTML).toBe('<div>bar</div>');
    
  754.   });
    
  755. 
    
  756.   // @gate !disableLegacyContext
    
  757.   it('should update portal context if it changes due to setState', () => {
    
  758.     const portalContainer = document.createElement('div');
    
  759. 
    
  760.     class Component extends React.Component {
    
  761.       static contextTypes = {
    
  762.         foo: PropTypes.string.isRequired,
    
  763.         getFoo: PropTypes.func.isRequired,
    
  764.       };
    
  765. 
    
  766.       render() {
    
  767.         return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
    
  768.       }
    
  769.     }
    
  770. 
    
  771.     class Parent extends React.Component {
    
  772.       static childContextTypes = {
    
  773.         foo: PropTypes.string.isRequired,
    
  774.         getFoo: PropTypes.func.isRequired,
    
  775.       };
    
  776. 
    
  777.       state = {
    
  778.         bar: 'initial',
    
  779.       };
    
  780. 
    
  781.       getChildContext() {
    
  782.         return {
    
  783.           foo: this.state.bar,
    
  784.           getFoo: () => this.state.bar,
    
  785.         };
    
  786.       }
    
  787. 
    
  788.       render() {
    
  789.         return ReactDOM.createPortal(<Component />, portalContainer);
    
  790.       }
    
  791.     }
    
  792. 
    
  793.     const instance = ReactDOM.render(<Parent />, container);
    
  794.     expect(portalContainer.innerHTML).toBe('<div>initial-initial</div>');
    
  795.     expect(container.innerHTML).toBe('');
    
  796.     instance.setState({bar: 'changed'});
    
  797.     expect(portalContainer.innerHTML).toBe('<div>changed-changed</div>');
    
  798.     expect(container.innerHTML).toBe('');
    
  799.   });
    
  800. 
    
  801.   // @gate !disableLegacyContext
    
  802.   it('should update portal context if it changes due to re-render', () => {
    
  803.     const portalContainer = document.createElement('div');
    
  804. 
    
  805.     class Component extends React.Component {
    
  806.       static contextTypes = {
    
  807.         foo: PropTypes.string.isRequired,
    
  808.         getFoo: PropTypes.func.isRequired,
    
  809.       };
    
  810. 
    
  811.       render() {
    
  812.         return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
    
  813.       }
    
  814.     }
    
  815. 
    
  816.     class Parent extends React.Component {
    
  817.       static childContextTypes = {
    
  818.         foo: PropTypes.string.isRequired,
    
  819.         getFoo: PropTypes.func.isRequired,
    
  820.       };
    
  821. 
    
  822.       getChildContext() {
    
  823.         return {
    
  824.           foo: this.props.bar,
    
  825.           getFoo: () => this.props.bar,
    
  826.         };
    
  827.       }
    
  828. 
    
  829.       render() {
    
  830.         return ReactDOM.createPortal(<Component />, portalContainer);
    
  831.       }
    
  832.     }
    
  833. 
    
  834.     ReactDOM.render(<Parent bar="initial" />, container);
    
  835.     expect(portalContainer.innerHTML).toBe('<div>initial-initial</div>');
    
  836.     expect(container.innerHTML).toBe('');
    
  837.     ReactDOM.render(<Parent bar="changed" />, container);
    
  838.     expect(portalContainer.innerHTML).toBe('<div>changed-changed</div>');
    
  839.     expect(container.innerHTML).toBe('');
    
  840.   });
    
  841. 
    
  842.   it('findDOMNode should find dom element after expanding a fragment', () => {
    
  843.     class MyNode extends React.Component {
    
  844.       render() {
    
  845.         return !this.props.flag
    
  846.           ? [<div key="a" />]
    
  847.           : [<span key="b" />, <div key="a" />];
    
  848.       }
    
  849.     }
    
  850. 
    
  851.     const myNodeA = ReactDOM.render(<MyNode />, container);
    
  852.     const a = ReactDOM.findDOMNode(myNodeA);
    
  853.     expect(a.tagName).toBe('DIV');
    
  854. 
    
  855.     const myNodeB = ReactDOM.render(<MyNode flag={true} />, container);
    
  856.     expect(myNodeA === myNodeB).toBe(true);
    
  857. 
    
  858.     const b = ReactDOM.findDOMNode(myNodeB);
    
  859.     expect(b.tagName).toBe('SPAN');
    
  860.   });
    
  861. 
    
  862.   it('should bubble events from the portal to the parent', () => {
    
  863.     const portalContainer = document.createElement('div');
    
  864.     document.body.appendChild(portalContainer);
    
  865.     try {
    
  866.       const ops = [];
    
  867.       let portal = null;
    
  868. 
    
  869.       ReactDOM.render(
    
  870.         <div onClick={() => ops.push('parent clicked')}>
    
  871.           {ReactDOM.createPortal(
    
  872.             <div
    
  873.               onClick={() => ops.push('portal clicked')}
    
  874.               ref={n => (portal = n)}>
    
  875.               portal
    
  876.             </div>,
    
  877.             portalContainer,
    
  878.           )}
    
  879.         </div>,
    
  880.         container,
    
  881.       );
    
  882. 
    
  883.       expect(portal.tagName).toBe('DIV');
    
  884. 
    
  885.       portal.click();
    
  886. 
    
  887.       expect(ops).toEqual(['portal clicked', 'parent clicked']);
    
  888.     } finally {
    
  889.       document.body.removeChild(portalContainer);
    
  890.     }
    
  891.   });
    
  892. 
    
  893.   it('should not onMouseLeave when staying in the portal', () => {
    
  894.     const portalContainer = document.createElement('div');
    
  895.     document.body.appendChild(portalContainer);
    
  896. 
    
  897.     let ops = [];
    
  898.     let firstTarget = null;
    
  899.     let secondTarget = null;
    
  900.     let thirdTarget = null;
    
  901. 
    
  902.     function simulateMouseMove(from, to) {
    
  903.       if (from) {
    
  904.         from.dispatchEvent(
    
  905.           new MouseEvent('mouseout', {
    
  906.             bubbles: true,
    
  907.             cancelable: true,
    
  908.             relatedTarget: to,
    
  909.           }),
    
  910.         );
    
  911.       }
    
  912.       if (to) {
    
  913.         to.dispatchEvent(
    
  914.           new MouseEvent('mouseover', {
    
  915.             bubbles: true,
    
  916.             cancelable: true,
    
  917.             relatedTarget: from,
    
  918.           }),
    
  919.         );
    
  920.       }
    
  921.     }
    
  922. 
    
  923.     try {
    
  924.       ReactDOM.render(
    
  925.         <div>
    
  926.           <div
    
  927.             onMouseEnter={() => ops.push('enter parent')}
    
  928.             onMouseLeave={() => ops.push('leave parent')}>
    
  929.             <div ref={n => (firstTarget = n)} />
    
  930.             {ReactDOM.createPortal(
    
  931.               <div
    
  932.                 onMouseEnter={() => ops.push('enter portal')}
    
  933.                 onMouseLeave={() => ops.push('leave portal')}
    
  934.                 ref={n => (secondTarget = n)}>
    
  935.                 portal
    
  936.               </div>,
    
  937.               portalContainer,
    
  938.             )}
    
  939.           </div>
    
  940.           <div ref={n => (thirdTarget = n)} />
    
  941.         </div>,
    
  942.         container,
    
  943.       );
    
  944. 
    
  945.       simulateMouseMove(null, firstTarget);
    
  946.       expect(ops).toEqual(['enter parent']);
    
  947. 
    
  948.       ops = [];
    
  949. 
    
  950.       simulateMouseMove(firstTarget, secondTarget);
    
  951.       expect(ops).toEqual([
    
  952.         // Parent did not invoke leave because we're still inside the portal.
    
  953.         'enter portal',
    
  954.       ]);
    
  955. 
    
  956.       ops = [];
    
  957. 
    
  958.       simulateMouseMove(secondTarget, thirdTarget);
    
  959.       expect(ops).toEqual([
    
  960.         'leave portal',
    
  961.         'leave parent', // Only when we leave the portal does onMouseLeave fire.
    
  962.       ]);
    
  963.     } finally {
    
  964.       document.body.removeChild(portalContainer);
    
  965.     }
    
  966.   });
    
  967. 
    
  968.   // Regression test for https://github.com/facebook/react/issues/19562
    
  969.   it('does not fire mouseEnter twice when relatedTarget is the root node', () => {
    
  970.     let ops = [];
    
  971.     let target = null;
    
  972. 
    
  973.     function simulateMouseMove(from, to) {
    
  974.       if (from) {
    
  975.         from.dispatchEvent(
    
  976.           new MouseEvent('mouseout', {
    
  977.             bubbles: true,
    
  978.             cancelable: true,
    
  979.             relatedTarget: to,
    
  980.           }),
    
  981.         );
    
  982.       }
    
  983.       if (to) {
    
  984.         to.dispatchEvent(
    
  985.           new MouseEvent('mouseover', {
    
  986.             bubbles: true,
    
  987.             cancelable: true,
    
  988.             relatedTarget: from,
    
  989.           }),
    
  990.         );
    
  991.       }
    
  992.     }
    
  993. 
    
  994.     ReactDOM.render(
    
  995.       <div
    
  996.         ref={n => (target = n)}
    
  997.         onMouseEnter={() => ops.push('enter')}
    
  998.         onMouseLeave={() => ops.push('leave')}
    
  999.       />,
    
  1000.       container,
    
  1001.     );
    
  1002. 
    
  1003.     simulateMouseMove(null, container);
    
  1004.     expect(ops).toEqual([]);
    
  1005. 
    
  1006.     ops = [];
    
  1007.     simulateMouseMove(container, target);
    
  1008.     expect(ops).toEqual(['enter']);
    
  1009. 
    
  1010.     ops = [];
    
  1011.     simulateMouseMove(target, container);
    
  1012.     expect(ops).toEqual(['leave']);
    
  1013. 
    
  1014.     ops = [];
    
  1015.     simulateMouseMove(container, null);
    
  1016.     expect(ops).toEqual([]);
    
  1017.   });
    
  1018. 
    
  1019.   it('listens to events that do not exist in the Portal subtree', () => {
    
  1020.     const onClick = jest.fn();
    
  1021. 
    
  1022.     const ref = React.createRef();
    
  1023.     ReactDOM.render(
    
  1024.       <div onClick={onClick}>
    
  1025.         {ReactDOM.createPortal(<button ref={ref}>click</button>, document.body)}
    
  1026.       </div>,
    
  1027.       container,
    
  1028.     );
    
  1029.     const event = new MouseEvent('click', {
    
  1030.       bubbles: true,
    
  1031.     });
    
  1032.     ref.current.dispatchEvent(event);
    
  1033. 
    
  1034.     expect(onClick).toHaveBeenCalledTimes(1);
    
  1035.   });
    
  1036. 
    
  1037.   it('should throw on bad createPortal argument', () => {
    
  1038.     expect(() => {
    
  1039.       ReactDOM.createPortal(<div>portal</div>, null);
    
  1040.     }).toThrow('Target container is not a DOM element.');
    
  1041.     expect(() => {
    
  1042.       ReactDOM.createPortal(<div>portal</div>, document.createTextNode('hi'));
    
  1043.     }).toThrow('Target container is not a DOM element.');
    
  1044.   });
    
  1045. 
    
  1046.   it('should warn for non-functional event listeners', () => {
    
  1047.     class Example extends React.Component {
    
  1048.       render() {
    
  1049.         return <div onClick="woops" />;
    
  1050.       }
    
  1051.     }
    
  1052.     expect(() => ReactDOM.render(<Example />, container)).toErrorDev(
    
  1053.       'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
    
  1054.         '    in div (at **)\n' +
    
  1055.         '    in Example (at **)',
    
  1056.     );
    
  1057.   });
    
  1058. 
    
  1059.   it('should warn with a special message for `false` event listeners', () => {
    
  1060.     class Example extends React.Component {
    
  1061.       render() {
    
  1062.         return <div onClick={false} />;
    
  1063.       }
    
  1064.     }
    
  1065.     expect(() => ReactDOM.render(<Example />, container)).toErrorDev(
    
  1066.       'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
    
  1067.         'If you used to conditionally omit it with onClick={condition && value}, ' +
    
  1068.         'pass onClick={condition ? value : undefined} instead.\n' +
    
  1069.         '    in div (at **)\n' +
    
  1070.         '    in Example (at **)',
    
  1071.     );
    
  1072.   });
    
  1073. 
    
  1074.   it('should not update event handlers until commit', () => {
    
  1075.     spyOnDev(console, 'error');
    
  1076. 
    
  1077.     let ops = [];
    
  1078.     const handlerA = () => ops.push('A');
    
  1079.     const handlerB = () => ops.push('B');
    
  1080. 
    
  1081.     function click() {
    
  1082.       const event = new MouseEvent('click', {
    
  1083.         bubbles: true,
    
  1084.         cancelable: true,
    
  1085.       });
    
  1086.       Object.defineProperty(event, 'timeStamp', {
    
  1087.         value: 0,
    
  1088.       });
    
  1089.       node.dispatchEvent(event);
    
  1090.     }
    
  1091. 
    
  1092.     class Example extends React.Component {
    
  1093.       state = {flip: false, count: 0};
    
  1094.       flip() {
    
  1095.         this.setState({flip: true, count: this.state.count + 1});
    
  1096.       }
    
  1097.       tick() {
    
  1098.         this.setState({count: this.state.count + 1});
    
  1099.       }
    
  1100.       render() {
    
  1101.         const useB = !this.props.forceA && this.state.flip;
    
  1102.         return <div onClick={useB ? handlerB : handlerA} />;
    
  1103.       }
    
  1104.     }
    
  1105. 
    
  1106.     class Click extends React.Component {
    
  1107.       constructor() {
    
  1108.         super();
    
  1109.         node.click();
    
  1110.       }
    
  1111.       render() {
    
  1112.         return null;
    
  1113.       }
    
  1114.     }
    
  1115. 
    
  1116.     let inst;
    
  1117.     ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
    
  1118.     const node = container.firstChild;
    
  1119.     expect(node.tagName).toEqual('DIV');
    
  1120. 
    
  1121.     click();
    
  1122. 
    
  1123.     expect(ops).toEqual(['A']);
    
  1124.     ops = [];
    
  1125. 
    
  1126.     // Render with the other event handler.
    
  1127.     inst.flip();
    
  1128. 
    
  1129.     click();
    
  1130. 
    
  1131.     expect(ops).toEqual(['B']);
    
  1132.     ops = [];
    
  1133. 
    
  1134.     // Rerender without changing any props.
    
  1135.     inst.tick();
    
  1136. 
    
  1137.     click();
    
  1138. 
    
  1139.     expect(ops).toEqual(['B']);
    
  1140.     ops = [];
    
  1141. 
    
  1142.     // Render a flip back to the A handler. The second component invokes the
    
  1143.     // click handler during render to simulate a click during an aborted
    
  1144.     // render. I use this hack because at current time we don't have a way to
    
  1145.     // test aborted ReactDOM renders.
    
  1146.     ReactDOM.render(
    
  1147.       [<Example key="a" forceA={true} />, <Click key="b" />],
    
  1148.       container,
    
  1149.     );
    
  1150. 
    
  1151.     // Because the new click handler has not yet committed, we should still
    
  1152.     // invoke B.
    
  1153.     expect(ops).toEqual(['B']);
    
  1154.     ops = [];
    
  1155. 
    
  1156.     // Any click that happens after commit, should invoke A.
    
  1157.     click();
    
  1158.     expect(ops).toEqual(['A']);
    
  1159. 
    
  1160.     if (__DEV__) {
    
  1161.       expect(console.error).toHaveBeenCalledTimes(2);
    
  1162.       expect(console.error.mock.calls[0][0]).toMatch(
    
  1163.         'ReactDOM.render is no longer supported in React 18',
    
  1164.       );
    
  1165.       expect(console.error.mock.calls[1][0]).toMatch(
    
  1166.         'ReactDOM.render is no longer supported in React 18',
    
  1167.       );
    
  1168.     }
    
  1169.   });
    
  1170. 
    
  1171.   it('should not crash encountering low-priority tree', () => {
    
  1172.     ReactDOM.render(
    
  1173.       <div hidden={true}>
    
  1174.         <div />
    
  1175.       </div>,
    
  1176.       container,
    
  1177.     );
    
  1178.   });
    
  1179. 
    
  1180.   it('should not warn when rendering into an empty container', () => {
    
  1181.     ReactDOM.render(<div>foo</div>, container);
    
  1182.     expect(container.innerHTML).toBe('<div>foo</div>');
    
  1183.     ReactDOM.render(null, container);
    
  1184.     expect(container.innerHTML).toBe('');
    
  1185.     ReactDOM.render(<div>bar</div>, container);
    
  1186.     expect(container.innerHTML).toBe('<div>bar</div>');
    
  1187.   });
    
  1188. 
    
  1189.   it('should warn when replacing a container which was manually updated outside of React', () => {
    
  1190.     // when not messing with the DOM outside of React
    
  1191.     ReactDOM.render(<div key="1">foo</div>, container);
    
  1192.     ReactDOM.render(<div key="1">bar</div>, container);
    
  1193.     expect(container.innerHTML).toBe('<div>bar</div>');
    
  1194.     // then we mess with the DOM before an update
    
  1195.     // we know this will error - that is expected right now
    
  1196.     // It's an error of type 'NotFoundError' with no message
    
  1197.     container.innerHTML = '<div>MEOW.</div>';
    
  1198. 
    
  1199.     expect(() => {
    
  1200.       expect(() =>
    
  1201.         ReactDOM.render(<div key="2">baz</div>, container),
    
  1202.       ).toErrorDev(
    
  1203.         'render(...): ' +
    
  1204.           'It looks like the React-rendered content of this container was ' +
    
  1205.           'removed without using React. This is not supported and will ' +
    
  1206.           'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
    
  1207.           'to empty a container.',
    
  1208.         {withoutStack: true},
    
  1209.       );
    
  1210.     }).toThrowError();
    
  1211.   });
    
  1212. 
    
  1213.   it('should warn when doing an update to a container manually updated outside of React', () => {
    
  1214.     // when not messing with the DOM outside of React
    
  1215.     ReactDOM.render(<div>foo</div>, container);
    
  1216.     ReactDOM.render(<div>bar</div>, container);
    
  1217.     expect(container.innerHTML).toBe('<div>bar</div>');
    
  1218.     // then we mess with the DOM before an update
    
  1219.     container.innerHTML = '<div>MEOW.</div>';
    
  1220.     expect(() => ReactDOM.render(<div>baz</div>, container)).toErrorDev(
    
  1221.       'render(...): ' +
    
  1222.         'It looks like the React-rendered content of this container was ' +
    
  1223.         'removed without using React. This is not supported and will ' +
    
  1224.         'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
    
  1225.         'to empty a container.',
    
  1226.       {withoutStack: true},
    
  1227.     );
    
  1228.   });
    
  1229. 
    
  1230.   it('should warn when doing an update to a container manually cleared outside of React', () => {
    
  1231.     // when not messing with the DOM outside of React
    
  1232.     ReactDOM.render(<div>foo</div>, container);
    
  1233.     ReactDOM.render(<div>bar</div>, container);
    
  1234.     expect(container.innerHTML).toBe('<div>bar</div>');
    
  1235.     // then we mess with the DOM before an update
    
  1236.     container.innerHTML = '';
    
  1237.     expect(() => ReactDOM.render(<div>baz</div>, container)).toErrorDev(
    
  1238.       'render(...): ' +
    
  1239.         'It looks like the React-rendered content of this container was ' +
    
  1240.         'removed without using React. This is not supported and will ' +
    
  1241.         'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
    
  1242.         'to empty a container.',
    
  1243.       {withoutStack: true},
    
  1244.     );
    
  1245.   });
    
  1246. 
    
  1247.   it('should render a text component with a text DOM node on the same document as the container', () => {
    
  1248.     // 1. Create a new document through the use of iframe
    
  1249.     // 2. Set up the spy to make asserts when a text component
    
  1250.     //    is rendered inside the iframe container
    
  1251.     const textContent = 'Hello world';
    
  1252.     const iframe = document.createElement('iframe');
    
  1253.     document.body.appendChild(iframe);
    
  1254.     const iframeDocument = iframe.contentDocument;
    
  1255.     iframeDocument.write(
    
  1256.       '<!DOCTYPE html><html><head></head><body><div></div></body></html>',
    
  1257.     );
    
  1258.     iframeDocument.close();
    
  1259.     const iframeContainer = iframeDocument.body.firstChild;
    
  1260. 
    
  1261.     let actualDocument;
    
  1262.     let textNode;
    
  1263. 
    
  1264.     spyOnDevAndProd(iframeContainer, 'appendChild').mockImplementation(node => {
    
  1265.       actualDocument = node.ownerDocument;
    
  1266.       textNode = node;
    
  1267.     });
    
  1268. 
    
  1269.     ReactDOM.render(textContent, iframeContainer);
    
  1270. 
    
  1271.     expect(textNode.textContent).toBe(textContent);
    
  1272.     expect(actualDocument).not.toBe(document);
    
  1273.     expect(actualDocument).toBe(iframeDocument);
    
  1274.     expect(iframeContainer.appendChild).toHaveBeenCalledTimes(1);
    
  1275.   });
    
  1276. 
    
  1277.   it('should mount into a document fragment', () => {
    
  1278.     const fragment = document.createDocumentFragment();
    
  1279.     ReactDOM.render(<div>foo</div>, fragment);
    
  1280.     expect(container.innerHTML).toBe('');
    
  1281.     container.appendChild(fragment);
    
  1282.     expect(container.innerHTML).toBe('<div>foo</div>');
    
  1283.   });
    
  1284. 
    
  1285.   // Regression test for https://github.com/facebook/react/issues/12643#issuecomment-413727104
    
  1286.   it('should not diff memoized host components', () => {
    
  1287.     const inputRef = React.createRef();
    
  1288.     let didCallOnChange = false;
    
  1289. 
    
  1290.     class Child extends React.Component {
    
  1291.       state = {};
    
  1292.       componentDidMount() {
    
  1293.         document.addEventListener('click', this.update, true);
    
  1294.       }
    
  1295.       componentWillUnmount() {
    
  1296.         document.removeEventListener('click', this.update, true);
    
  1297.       }
    
  1298.       update = () => {
    
  1299.         // We're testing that this setState()
    
  1300.         // doesn't cause React to commit updates
    
  1301.         // to the input outside (which would itself
    
  1302.         // prevent the parent's onChange parent handler
    
  1303.         // from firing).
    
  1304.         this.setState({});
    
  1305.         // Note that onChange was always broken when there was an
    
  1306.         // earlier setState() in a manual document capture phase
    
  1307.         // listener *in the same component*. But that's very rare.
    
  1308.         // Here we're testing that a *child* component doesn't break
    
  1309.         // the parent if this happens.
    
  1310.       };
    
  1311.       render() {
    
  1312.         return <div />;
    
  1313.       }
    
  1314.     }
    
  1315. 
    
  1316.     class Parent extends React.Component {
    
  1317.       handleChange = val => {
    
  1318.         didCallOnChange = true;
    
  1319.       };
    
  1320.       render() {
    
  1321.         return (
    
  1322.           <div>
    
  1323.             <Child />
    
  1324.             <input
    
  1325.               ref={inputRef}
    
  1326.               type="checkbox"
    
  1327.               checked={true}
    
  1328.               onChange={this.handleChange}
    
  1329.             />
    
  1330.           </div>
    
  1331.         );
    
  1332.       }
    
  1333.     }
    
  1334. 
    
  1335.     ReactDOM.render(<Parent />, container);
    
  1336.     inputRef.current.dispatchEvent(
    
  1337.       new MouseEvent('click', {
    
  1338.         bubbles: true,
    
  1339.       }),
    
  1340.     );
    
  1341.     expect(didCallOnChange).toBe(true);
    
  1342.   });
    
  1343. 
    
  1344.   it('unmounted legacy roots should never clear newer root content from a container', () => {
    
  1345.     const ref = React.createRef();
    
  1346. 
    
  1347.     function OldApp() {
    
  1348.       const hideOnFocus = () => {
    
  1349.         // This app unmounts itself inside of a focus event.
    
  1350.         ReactDOM.unmountComponentAtNode(container);
    
  1351.       };
    
  1352. 
    
  1353.       return (
    
  1354.         <button onFocus={hideOnFocus} ref={ref}>
    
  1355.           old
    
  1356.         </button>
    
  1357.       );
    
  1358.     }
    
  1359. 
    
  1360.     function NewApp() {
    
  1361.       return <button ref={ref}>new</button>;
    
  1362.     }
    
  1363. 
    
  1364.     ReactDOM.render(<OldApp />, container);
    
  1365.     ref.current.focus();
    
  1366. 
    
  1367.     ReactDOM.render(<NewApp />, container);
    
  1368. 
    
  1369.     // Calling focus again will flush previously scheduled discrete work for the old root-
    
  1370.     // but this should not clear out the newly mounted app.
    
  1371.     ref.current.focus();
    
  1372. 
    
  1373.     expect(container.textContent).toBe('new');
    
  1374.   });
    
  1375. });