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.  * @jest-environment node
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. let React;
    
  14. let ReactNoop;
    
  15. let Scheduler;
    
  16. let waitForAll;
    
  17. let waitFor;
    
  18. let waitForPaint;
    
  19. 
    
  20. describe('ReactIncrementalSideEffects', () => {
    
  21.   beforeEach(() => {
    
  22.     jest.resetModules();
    
  23. 
    
  24.     React = require('react');
    
  25.     ReactNoop = require('react-noop-renderer');
    
  26.     Scheduler = require('scheduler');
    
  27. 
    
  28.     const InternalTestUtils = require('internal-test-utils');
    
  29.     waitForAll = InternalTestUtils.waitForAll;
    
  30.     waitFor = InternalTestUtils.waitFor;
    
  31.     waitForPaint = InternalTestUtils.waitForPaint;
    
  32.   });
    
  33. 
    
  34.   // Note: This is based on a similar component we use in www. We can delete
    
  35.   // once the extra div wrapper is no longer necessary.
    
  36.   function LegacyHiddenDiv({children, mode}) {
    
  37.     return (
    
  38.       <div hidden={mode === 'hidden'}>
    
  39.         <React.unstable_LegacyHidden
    
  40.           mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
    
  41.           {children}
    
  42.         </React.unstable_LegacyHidden>
    
  43.       </div>
    
  44.     );
    
  45.   }
    
  46. 
    
  47.   it('can update child nodes of a host instance', async () => {
    
  48.     function Bar(props) {
    
  49.       return <span>{props.text}</span>;
    
  50.     }
    
  51. 
    
  52.     function Foo(props) {
    
  53.       return (
    
  54.         <div>
    
  55.           <Bar text={props.text} />
    
  56.           {props.text === 'World' ? <Bar text={props.text} /> : null}
    
  57.         </div>
    
  58.       );
    
  59.     }
    
  60. 
    
  61.     ReactNoop.render(<Foo text="Hello" />);
    
  62.     await waitForAll([]);
    
  63.     expect(ReactNoop).toMatchRenderedOutput(
    
  64.       <div>
    
  65.         <span>Hello</span>
    
  66.       </div>,
    
  67.     );
    
  68. 
    
  69.     ReactNoop.render(<Foo text="World" />);
    
  70.     await waitForAll([]);
    
  71.     expect(ReactNoop).toMatchRenderedOutput(
    
  72.       <div>
    
  73.         <span>World</span>
    
  74.         <span>World</span>
    
  75.       </div>,
    
  76.     );
    
  77.   });
    
  78. 
    
  79.   it('can update child nodes of a fragment', async function () {
    
  80.     function Bar(props) {
    
  81.       return <span>{props.text}</span>;
    
  82.     }
    
  83. 
    
  84.     function Foo(props) {
    
  85.       return (
    
  86.         <div>
    
  87.           <Bar text={props.text} />
    
  88.           {props.text === 'World'
    
  89.             ? [<Bar key="a" text={props.text} />, <div key="b" />]
    
  90.             : props.text === 'Hi'
    
  91.             ? [<div key="b" />, <Bar key="a" text={props.text} />]
    
  92.             : null}
    
  93.           <span prop="test" />
    
  94.         </div>
    
  95.       );
    
  96.     }
    
  97. 
    
  98.     ReactNoop.render(<Foo text="Hello" />);
    
  99.     await waitForAll([]);
    
  100.     expect(ReactNoop).toMatchRenderedOutput(
    
  101.       <div>
    
  102.         <span>Hello</span>
    
  103.         <span prop="test" />
    
  104.       </div>,
    
  105.     );
    
  106. 
    
  107.     ReactNoop.render(<Foo text="World" />);
    
  108.     await waitForAll([]);
    
  109.     expect(ReactNoop).toMatchRenderedOutput(
    
  110.       <div>
    
  111.         <span>World</span>
    
  112.         <span>World</span>
    
  113.         <div />
    
  114.         <span prop="test" />
    
  115.       </div>,
    
  116.     );
    
  117. 
    
  118.     ReactNoop.render(<Foo text="Hi" />);
    
  119.     await waitForAll([]);
    
  120.     expect(ReactNoop).toMatchRenderedOutput(
    
  121.       <div>
    
  122.         <span>Hi</span>
    
  123.         <div />
    
  124.         <span>Hi</span>
    
  125.         <span prop="test" />
    
  126.       </div>,
    
  127.     );
    
  128.   });
    
  129. 
    
  130.   it('can update child nodes rendering into text nodes', async function () {
    
  131.     function Bar(props) {
    
  132.       return props.text;
    
  133.     }
    
  134. 
    
  135.     function Foo(props) {
    
  136.       return (
    
  137.         <div>
    
  138.           <Bar text={props.text} />
    
  139.           {props.text === 'World'
    
  140.             ? [<Bar key="a" text={props.text} />, '!']
    
  141.             : null}
    
  142.         </div>
    
  143.       );
    
  144.     }
    
  145. 
    
  146.     ReactNoop.render(<Foo text="Hello" />);
    
  147.     await waitForAll([]);
    
  148.     expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
    
  149. 
    
  150.     ReactNoop.render(<Foo text="World" />);
    
  151.     await waitForAll([]);
    
  152.     expect(ReactNoop).toMatchRenderedOutput(<div>WorldWorld!</div>);
    
  153.   });
    
  154. 
    
  155.   it('can deletes children either components, host or text', async function () {
    
  156.     function Bar(props) {
    
  157.       return <span prop={props.children} />;
    
  158.     }
    
  159. 
    
  160.     function Foo(props) {
    
  161.       return (
    
  162.         <div>
    
  163.           {props.show
    
  164.             ? [<div key="a" />, <Bar key="b">Hello</Bar>, 'World']
    
  165.             : []}
    
  166.         </div>
    
  167.       );
    
  168.     }
    
  169. 
    
  170.     ReactNoop.render(<Foo show={true} />);
    
  171.     await waitForAll([]);
    
  172.     expect(ReactNoop).toMatchRenderedOutput(
    
  173.       <div>
    
  174.         <div />
    
  175.         <span prop="Hello" />
    
  176.         World
    
  177.       </div>,
    
  178.     );
    
  179. 
    
  180.     ReactNoop.render(<Foo show={false} />);
    
  181.     await waitForAll([]);
    
  182.     expect(ReactNoop).toMatchRenderedOutput(<div />);
    
  183.   });
    
  184. 
    
  185.   it('can delete a child that changes type - implicit keys', async function () {
    
  186.     let unmounted = false;
    
  187. 
    
  188.     class ClassComponent extends React.Component {
    
  189.       componentWillUnmount() {
    
  190.         unmounted = true;
    
  191.       }
    
  192.       render() {
    
  193.         return <span prop="Class" />;
    
  194.       }
    
  195.     }
    
  196. 
    
  197.     function FunctionComponent(props) {
    
  198.       return <span prop="Function" />;
    
  199.     }
    
  200. 
    
  201.     function Foo(props) {
    
  202.       return (
    
  203.         <div>
    
  204.           {props.useClass ? (
    
  205.             <ClassComponent />
    
  206.           ) : props.useFunction ? (
    
  207.             <FunctionComponent />
    
  208.           ) : props.useText ? (
    
  209.             'Text'
    
  210.           ) : null}
    
  211.           Trail
    
  212.         </div>
    
  213.       );
    
  214.     }
    
  215. 
    
  216.     ReactNoop.render(<Foo useClass={true} />);
    
  217.     await waitForAll([]);
    
  218.     expect(ReactNoop).toMatchRenderedOutput(
    
  219.       <div>
    
  220.         <span prop="Class" />
    
  221.         Trail
    
  222.       </div>,
    
  223.     );
    
  224. 
    
  225.     expect(unmounted).toBe(false);
    
  226. 
    
  227.     ReactNoop.render(<Foo useFunction={true} />);
    
  228.     await waitForAll([]);
    
  229.     expect(ReactNoop).toMatchRenderedOutput(
    
  230.       <div>
    
  231.         <span prop="Function" />
    
  232.         Trail
    
  233.       </div>,
    
  234.     );
    
  235. 
    
  236.     expect(unmounted).toBe(true);
    
  237. 
    
  238.     ReactNoop.render(<Foo useText={true} />);
    
  239.     await waitForAll([]);
    
  240.     expect(ReactNoop).toMatchRenderedOutput(<div>TextTrail</div>);
    
  241. 
    
  242.     ReactNoop.render(<Foo />);
    
  243.     await waitForAll([]);
    
  244.     expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
    
  245.   });
    
  246. 
    
  247.   it('can delete a child that changes type - explicit keys', async function () {
    
  248.     let unmounted = false;
    
  249. 
    
  250.     class ClassComponent extends React.Component {
    
  251.       componentWillUnmount() {
    
  252.         unmounted = true;
    
  253.       }
    
  254.       render() {
    
  255.         return <span prop="Class" />;
    
  256.       }
    
  257.     }
    
  258. 
    
  259.     function FunctionComponent(props) {
    
  260.       return <span prop="Function" />;
    
  261.     }
    
  262. 
    
  263.     function Foo(props) {
    
  264.       return (
    
  265.         <div>
    
  266.           {props.useClass ? (
    
  267.             <ClassComponent key="a" />
    
  268.           ) : props.useFunction ? (
    
  269.             <FunctionComponent key="a" />
    
  270.           ) : null}
    
  271.           Trail
    
  272.         </div>
    
  273.       );
    
  274.     }
    
  275. 
    
  276.     ReactNoop.render(<Foo useClass={true} />);
    
  277.     await waitForAll([]);
    
  278.     expect(ReactNoop).toMatchRenderedOutput(
    
  279.       <div>
    
  280.         <span prop="Class" />
    
  281.         Trail
    
  282.       </div>,
    
  283.     );
    
  284. 
    
  285.     expect(unmounted).toBe(false);
    
  286. 
    
  287.     ReactNoop.render(<Foo useFunction={true} />);
    
  288.     await waitForAll([]);
    
  289.     expect(ReactNoop).toMatchRenderedOutput(
    
  290.       <div>
    
  291.         <span prop="Function" />
    
  292.         Trail
    
  293.       </div>,
    
  294.     );
    
  295. 
    
  296.     expect(unmounted).toBe(true);
    
  297. 
    
  298.     ReactNoop.render(<Foo />);
    
  299.     await waitForAll([]);
    
  300.     expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
    
  301.   });
    
  302. 
    
  303.   it('can delete a child when it unmounts inside a portal', async () => {
    
  304.     function Bar(props) {
    
  305.       return <span prop={props.children} />;
    
  306.     }
    
  307. 
    
  308.     const portalContainer =
    
  309.       ReactNoop.getOrCreateRootContainer('portalContainer');
    
  310.     function Foo(props) {
    
  311.       return ReactNoop.createPortal(
    
  312.         props.show ? [<div key="a" />, <Bar key="b">Hello</Bar>, 'World'] : [],
    
  313.         portalContainer,
    
  314.         null,
    
  315.       );
    
  316.     }
    
  317. 
    
  318.     ReactNoop.render(
    
  319.       <div>
    
  320.         <Foo show={true} />
    
  321.       </div>,
    
  322.     );
    
  323.     await waitForAll([]);
    
  324.     expect(ReactNoop).toMatchRenderedOutput(<div />);
    
  325.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
    
  326.       <>
    
  327.         <div />
    
  328.         <span prop="Hello" />
    
  329.         World
    
  330.       </>,
    
  331.     );
    
  332. 
    
  333.     ReactNoop.render(
    
  334.       <div>
    
  335.         <Foo show={false} />
    
  336.       </div>,
    
  337.     );
    
  338.     await waitForAll([]);
    
  339.     expect(ReactNoop).toMatchRenderedOutput(<div />);
    
  340.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
    
  341. 
    
  342.     ReactNoop.render(
    
  343.       <div>
    
  344.         <Foo show={true} />
    
  345.       </div>,
    
  346.     );
    
  347.     await waitForAll([]);
    
  348.     expect(ReactNoop).toMatchRenderedOutput(<div />);
    
  349.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
    
  350.       <>
    
  351.         <div />
    
  352.         <span prop="Hello" />
    
  353.         World
    
  354.       </>,
    
  355.     );
    
  356. 
    
  357.     ReactNoop.render(null);
    
  358.     await waitForAll([]);
    
  359.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  360.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
    
  361. 
    
  362.     ReactNoop.render(<Foo show={false} />);
    
  363.     await waitForAll([]);
    
  364.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  365.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
    
  366. 
    
  367.     ReactNoop.render(<Foo show={true} />);
    
  368.     await waitForAll([]);
    
  369.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  370.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
    
  371.       <>
    
  372.         <div />
    
  373.         <span prop="Hello" />
    
  374.         World
    
  375.       </>,
    
  376.     );
    
  377. 
    
  378.     ReactNoop.render(null);
    
  379.     await waitForAll([]);
    
  380.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  381.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
    
  382.   });
    
  383. 
    
  384.   it('can delete a child when it unmounts with a portal', async () => {
    
  385.     function Bar(props) {
    
  386.       return <span prop={props.children} />;
    
  387.     }
    
  388. 
    
  389.     const portalContainer =
    
  390.       ReactNoop.getOrCreateRootContainer('portalContainer');
    
  391.     function Foo(props) {
    
  392.       return ReactNoop.createPortal(
    
  393.         [<div key="a" />, <Bar key="b">Hello</Bar>, 'World'],
    
  394.         portalContainer,
    
  395.         null,
    
  396.       );
    
  397.     }
    
  398. 
    
  399.     ReactNoop.render(
    
  400.       <div>
    
  401.         <Foo />
    
  402.       </div>,
    
  403.     );
    
  404.     await waitForAll([]);
    
  405.     expect(ReactNoop).toMatchRenderedOutput(<div />);
    
  406.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
    
  407.       <>
    
  408.         <div />
    
  409.         <span prop="Hello" />
    
  410.         World
    
  411.       </>,
    
  412.     );
    
  413. 
    
  414.     ReactNoop.render(null);
    
  415.     await waitForAll([]);
    
  416.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  417.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
    
  418. 
    
  419.     ReactNoop.render(<Foo />);
    
  420.     await waitForAll([]);
    
  421.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  422.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
    
  423.       <>
    
  424.         <div />
    
  425.         <span prop="Hello" />
    
  426.         World
    
  427.       </>,
    
  428.     );
    
  429. 
    
  430.     ReactNoop.render(null);
    
  431.     await waitForAll([]);
    
  432.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  433.     expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
    
  434.   });
    
  435. 
    
  436.   it('does not update child nodes if a flush is aborted', async () => {
    
  437.     function Bar(props) {
    
  438.       Scheduler.log('Bar');
    
  439.       return <span prop={props.text} />;
    
  440.     }
    
  441. 
    
  442.     function Foo(props) {
    
  443.       Scheduler.log('Foo');
    
  444.       return (
    
  445.         <div>
    
  446.           <div>
    
  447.             <Bar text={props.text} />
    
  448.             {props.text === 'Hello' ? <Bar text={props.text} /> : null}
    
  449.           </div>
    
  450.           <Bar text="Yo" />
    
  451.         </div>
    
  452.       );
    
  453.     }
    
  454. 
    
  455.     ReactNoop.render(<Foo text="Hello" />);
    
  456.     await waitForAll(['Foo', 'Bar', 'Bar', 'Bar']);
    
  457.     expect(ReactNoop).toMatchRenderedOutput(
    
  458.       <div>
    
  459.         <div>
    
  460.           <span prop="Hello" />
    
  461.           <span prop="Hello" />
    
  462.         </div>
    
  463.         <span prop="Yo" />
    
  464.       </div>,
    
  465.     );
    
  466. 
    
  467.     React.startTransition(() => {
    
  468.       ReactNoop.render(<Foo text="World" />);
    
  469.     });
    
  470. 
    
  471.     // Flush some of the work without committing
    
  472.     await waitFor(['Foo', 'Bar']);
    
  473.     expect(ReactNoop).toMatchRenderedOutput(
    
  474.       <div>
    
  475.         <div>
    
  476.           <span prop="Hello" />
    
  477.           <span prop="Hello" />
    
  478.         </div>
    
  479.         <span prop="Yo" />
    
  480.       </div>,
    
  481.     );
    
  482.   });
    
  483. 
    
  484.   // @gate www
    
  485.   it('preserves a previously rendered node when deprioritized', async () => {
    
  486.     function Middle(props) {
    
  487.       Scheduler.log('Middle');
    
  488.       return <span prop={props.children} />;
    
  489.     }
    
  490. 
    
  491.     function Foo(props) {
    
  492.       Scheduler.log('Foo');
    
  493.       return (
    
  494.         <div>
    
  495.           <LegacyHiddenDiv mode="hidden">
    
  496.             <Middle>{props.text}</Middle>
    
  497.           </LegacyHiddenDiv>
    
  498.         </div>
    
  499.       );
    
  500.     }
    
  501. 
    
  502.     ReactNoop.render(<Foo text="foo" />);
    
  503.     await waitForAll(['Foo', 'Middle']);
    
  504. 
    
  505.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  506.       <div>
    
  507.         <div hidden={true}>
    
  508.           <span prop="foo" />
    
  509.         </div>
    
  510.       </div>,
    
  511.     );
    
  512. 
    
  513.     ReactNoop.render(<Foo text="bar" />, () => Scheduler.log('commit'));
    
  514.     await waitFor(['Foo', 'commit']);
    
  515.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  516.       <div>
    
  517.         <div hidden={true}>
    
  518.           <span prop="foo" />
    
  519.         </div>
    
  520.       </div>,
    
  521.     );
    
  522. 
    
  523.     await waitForAll(['Middle']);
    
  524.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  525.       <div>
    
  526.         <div hidden={true}>
    
  527.           <span prop="bar" />
    
  528.         </div>
    
  529.       </div>,
    
  530.     );
    
  531.   });
    
  532. 
    
  533.   // @gate www
    
  534.   it('can reuse side-effects after being preempted', async () => {
    
  535.     function Bar(props) {
    
  536.       Scheduler.log('Bar');
    
  537.       return <span prop={props.children} />;
    
  538.     }
    
  539. 
    
  540.     const middleContent = (
    
  541.       <div>
    
  542.         <Bar>Hello</Bar>
    
  543.         <Bar>World</Bar>
    
  544.       </div>
    
  545.     );
    
  546. 
    
  547.     function Foo(props) {
    
  548.       Scheduler.log('Foo');
    
  549.       return (
    
  550.         <LegacyHiddenDiv mode="hidden">
    
  551.           {props.step === 0 ? (
    
  552.             <div>
    
  553.               <Bar>Hi</Bar>
    
  554.               <Bar>{props.text}</Bar>
    
  555.             </div>
    
  556.           ) : (
    
  557.             middleContent
    
  558.           )}
    
  559.         </LegacyHiddenDiv>
    
  560.       );
    
  561.     }
    
  562. 
    
  563.     // Init
    
  564.     ReactNoop.render(<Foo text="foo" step={0} />);
    
  565.     await waitForAll(['Foo', 'Bar', 'Bar']);
    
  566. 
    
  567.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  568.       <div hidden={true}>
    
  569.         <div>
    
  570.           <span prop="Hi" />
    
  571.           <span prop="foo" />
    
  572.         </div>
    
  573.       </div>,
    
  574.     );
    
  575. 
    
  576.     // Make a quick update which will schedule low priority work to
    
  577.     // update the middle content.
    
  578.     ReactNoop.render(<Foo text="bar" step={1} />, () =>
    
  579.       Scheduler.log('commit'),
    
  580.     );
    
  581.     await waitFor(['Foo', 'commit', 'Bar']);
    
  582. 
    
  583.     // The tree remains unchanged.
    
  584.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  585.       <div hidden={true}>
    
  586.         <div>
    
  587.           <span prop="Hi" />
    
  588.           <span prop="foo" />
    
  589.         </div>
    
  590.       </div>,
    
  591.     );
    
  592. 
    
  593.     // The first Bar has already completed its update but we'll interrupt it to
    
  594.     // render some higher priority work. The middle content will bailout so
    
  595.     // it remains untouched which means that it should reuse it next time.
    
  596.     ReactNoop.render(<Foo text="foo" step={1} />);
    
  597.     await waitForAll(['Foo', 'Bar', 'Bar']);
    
  598. 
    
  599.     // Since we did nothing to the middle subtree during the interruption,
    
  600.     // we should be able to reuse the reconciliation work that we already did
    
  601.     // without restarting. The side-effects should still be replayed.
    
  602. 
    
  603.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  604.       <div hidden={true}>
    
  605.         <div>
    
  606.           <span prop="Hello" />
    
  607.           <span prop="World" />
    
  608.         </div>
    
  609.       </div>,
    
  610.     );
    
  611.   });
    
  612. 
    
  613.   // @gate www
    
  614.   it('can reuse side-effects after being preempted, if shouldComponentUpdate is false', async () => {
    
  615.     class Bar extends React.Component {
    
  616.       shouldComponentUpdate(nextProps) {
    
  617.         return this.props.children !== nextProps.children;
    
  618.       }
    
  619.       render() {
    
  620.         Scheduler.log('Bar');
    
  621.         return <span prop={this.props.children} />;
    
  622.       }
    
  623.     }
    
  624. 
    
  625.     class Content extends React.Component {
    
  626.       shouldComponentUpdate(nextProps) {
    
  627.         return this.props.step !== nextProps.step;
    
  628.       }
    
  629.       render() {
    
  630.         Scheduler.log('Content');
    
  631.         return (
    
  632.           <div>
    
  633.             <Bar>{this.props.step === 0 ? 'Hi' : 'Hello'}</Bar>
    
  634.             <Bar>{this.props.step === 0 ? this.props.text : 'World'}</Bar>
    
  635.           </div>
    
  636.         );
    
  637.       }
    
  638.     }
    
  639. 
    
  640.     function Foo(props) {
    
  641.       Scheduler.log('Foo');
    
  642.       return (
    
  643.         <LegacyHiddenDiv mode="hidden">
    
  644.           <Content step={props.step} text={props.text} />
    
  645.         </LegacyHiddenDiv>
    
  646.       );
    
  647.     }
    
  648. 
    
  649.     // Init
    
  650.     ReactNoop.render(<Foo text="foo" step={0} />);
    
  651.     await waitForAll(['Foo', 'Content', 'Bar', 'Bar']);
    
  652. 
    
  653.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  654.       <div hidden={true}>
    
  655.         <div>
    
  656.           <span prop="Hi" />
    
  657.           <span prop="foo" />
    
  658.         </div>
    
  659.       </div>,
    
  660.     );
    
  661. 
    
  662.     // Make a quick update which will schedule low priority work to
    
  663.     // update the middle content.
    
  664.     ReactNoop.render(<Foo text="bar" step={1} />);
    
  665.     await waitFor(['Foo', 'Content', 'Bar']);
    
  666. 
    
  667.     // The tree remains unchanged.
    
  668.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  669.       <div hidden={true}>
    
  670.         <div>
    
  671.           <span prop="Hi" />
    
  672.           <span prop="foo" />
    
  673.         </div>
    
  674.       </div>,
    
  675.     );
    
  676. 
    
  677.     // The first Bar has already completed its update but we'll interrupt it to
    
  678.     // render some higher priority work. The middle content will bailout so
    
  679.     // it remains untouched which means that it should reuse it next time.
    
  680.     ReactNoop.render(<Foo text="foo" step={1} />);
    
  681.     await waitForAll(['Foo', 'Content', 'Bar', 'Bar']);
    
  682. 
    
  683.     // Since we did nothing to the middle subtree during the interruption,
    
  684.     // we should be able to reuse the reconciliation work that we already did
    
  685.     // without restarting. The side-effects should still be replayed.
    
  686. 
    
  687.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  688.       <div hidden={true}>
    
  689.         <div>
    
  690.           <span prop="Hello" />
    
  691.           <span prop="World" />
    
  692.         </div>
    
  693.       </div>,
    
  694.     );
    
  695.   });
    
  696. 
    
  697.   it('can update a completed tree before it has a chance to commit', async () => {
    
  698.     function Foo(props) {
    
  699.       Scheduler.log('Foo ' + props.step);
    
  700.       return <span prop={props.step} />;
    
  701.     }
    
  702.     React.startTransition(() => {
    
  703.       ReactNoop.render(<Foo step={1} />);
    
  704.     });
    
  705.     // This should be just enough to complete the tree without committing it
    
  706.     await waitFor(['Foo 1']);
    
  707.     expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
    
  708.     // To confirm, perform one more unit of work. The tree should now
    
  709.     // be flushed.
    
  710.     await waitForPaint([]);
    
  711.     expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
    
  712. 
    
  713.     React.startTransition(() => {
    
  714.       ReactNoop.render(<Foo step={2} />);
    
  715.     });
    
  716.     // This should be just enough to complete the tree without committing it
    
  717.     await waitFor(['Foo 2']);
    
  718.     expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
    
  719.     // This time, before we commit the tree, we update the root component with
    
  720.     // new props
    
  721. 
    
  722.     React.startTransition(() => {
    
  723.       ReactNoop.render(<Foo step={3} />);
    
  724.     });
    
  725.     expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
    
  726.     // Now let's commit. We already had a commit that was pending, which will
    
  727.     // render 2.
    
  728.     await waitForPaint([]);
    
  729.     expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
    
  730.     // If we flush the rest of the work, we should get another commit that
    
  731.     // renders 3. If it renders 2 again, that means an update was dropped.
    
  732.     await waitForAll(['Foo 3']);
    
  733.     expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
    
  734.   });
    
  735. 
    
  736.   // @gate www
    
  737.   it('updates a child even though the old props is empty', async () => {
    
  738.     function Foo(props) {
    
  739.       return (
    
  740.         <LegacyHiddenDiv mode="hidden">
    
  741.           <span prop={1} />
    
  742.         </LegacyHiddenDiv>
    
  743.       );
    
  744.     }
    
  745. 
    
  746.     ReactNoop.render(<Foo />);
    
  747.     await waitForAll([]);
    
  748.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  749.       <div hidden={true}>
    
  750.         <span prop={1} />
    
  751.       </div>,
    
  752.     );
    
  753.   });
    
  754. 
    
  755.   xit('can defer side-effects and resume them later on', async () => {
    
  756.     class Bar extends React.Component {
    
  757.       shouldComponentUpdate(nextProps) {
    
  758.         return this.props.idx !== nextProps.idx;
    
  759.       }
    
  760.       render() {
    
  761.         return <span prop={this.props.idx} />;
    
  762.       }
    
  763.     }
    
  764.     function Foo(props) {
    
  765.       return (
    
  766.         <div>
    
  767.           <span prop={props.tick} />
    
  768.           <div hidden={true}>
    
  769.             <Bar idx={props.idx} />
    
  770.             <Bar idx={props.idx + 1} />
    
  771.           </div>
    
  772.         </div>
    
  773.       );
    
  774.     }
    
  775.     ReactNoop.render(<Foo tick={0} idx={0} />);
    
  776.     ReactNoop.flushDeferredPri(40 + 25);
    
  777.     expect(ReactNoop).toMatchRenderedOutput(
    
  778.       <div>
    
  779.         <span prop={0} />
    
  780.         <div />
    
  781.       </div>,
    
  782.     );
    
  783.     ReactNoop.render(<Foo tick={1} idx={0} />);
    
  784.     ReactNoop.flushDeferredPri(35 + 25);
    
  785.     expect(ReactNoop).toMatchRenderedOutput(
    
  786.       <div>
    
  787.         <span prop={1} />
    
  788.         <div>{/*still not rendered yet*/}</div>
    
  789.       </div>,
    
  790.     );
    
  791.     ReactNoop.flushDeferredPri(30 + 25);
    
  792.     expect(ReactNoop).toMatchRenderedOutput(
    
  793.       <div>
    
  794.         <span prop={1} />
    
  795.         <div>
    
  796.           {/* Now we had enough time to finish the spans. */}
    
  797.           <span prop={0} />
    
  798.           <span prop={1} />
    
  799.         </div>
    
  800.         ,
    
  801.       </div>,
    
  802.     );
    
  803.     const innerSpanA =
    
  804.       ReactNoop.dangerouslyGetChildren()[0].children[1].children[1];
    
  805.     ReactNoop.render(<Foo tick={2} idx={1} />);
    
  806.     ReactNoop.flushDeferredPri(30 + 25);
    
  807.     expect(ReactNoop).toMatchRenderedOutput(
    
  808.       <div>
    
  809.         <span prop={2} />
    
  810.         <div>
    
  811.           {/* Still same old numbers. */}
    
  812.           <span prop={0} />
    
  813.           <span prop={1} />
    
  814.         </div>
    
  815.       </div>,
    
  816.     );
    
  817.     ReactNoop.render(<Foo tick={3} idx={1} />);
    
  818.     await waitForAll([]);
    
  819.     expect(ReactNoop).toMatchRenderedOutput(
    
  820.       <div>
    
  821.         <span prop={3} />
    
  822.         <div>
    
  823.           {/* New numbers. */}
    
  824.           <span prop={1} />
    
  825.           <span prop={2} />
    
  826.         </div>
    
  827.       </div>,
    
  828.     );
    
  829. 
    
  830.     const innerSpanB =
    
  831.       ReactNoop.dangerouslyGetChildren()[0].children[1].children[1];
    
  832.     // This should have been an update to an existing instance, not recreation.
    
  833.     // We verify that by ensuring that the child instance was the same as
    
  834.     // before.
    
  835.     expect(innerSpanA).toBe(innerSpanB);
    
  836.   });
    
  837. 
    
  838.   xit('can defer side-effects and reuse them later - complex', async function () {
    
  839.     let ops = [];
    
  840. 
    
  841.     class Bar extends React.Component {
    
  842.       shouldComponentUpdate(nextProps) {
    
  843.         return this.props.idx !== nextProps.idx;
    
  844.       }
    
  845.       render() {
    
  846.         ops.push('Bar');
    
  847.         return <span prop={this.props.idx} />;
    
  848.       }
    
  849.     }
    
  850.     class Baz extends React.Component {
    
  851.       shouldComponentUpdate(nextProps) {
    
  852.         return this.props.idx !== nextProps.idx;
    
  853.       }
    
  854.       render() {
    
  855.         ops.push('Baz');
    
  856.         return [
    
  857.           <Bar key="a" idx={this.props.idx} />,
    
  858.           <Bar key="b" idx={this.props.idx} />,
    
  859.         ];
    
  860.       }
    
  861.     }
    
  862.     function Foo(props) {
    
  863.       ops.push('Foo');
    
  864.       return (
    
  865.         <div>
    
  866.           <span prop={props.tick} />
    
  867.           <div hidden={true}>
    
  868.             <Baz idx={props.idx} />
    
  869.             <Baz idx={props.idx} />
    
  870.             <Baz idx={props.idx} />
    
  871.           </div>
    
  872.         </div>
    
  873.       );
    
  874.     }
    
  875.     ReactNoop.render(<Foo tick={0} idx={0} />);
    
  876.     ReactNoop.flushDeferredPri(65 + 5);
    
  877.     expect(ReactNoop).toMatchRenderedOutput(
    
  878.       <div>
    
  879.         <span prop={0} />
    
  880.         {/*the spans are down-prioritized and not rendered yet*/}
    
  881.         <div />
    
  882.       </div>,
    
  883.     );
    
  884. 
    
  885.     expect(ops).toEqual(['Foo', 'Baz', 'Bar']);
    
  886.     ops = [];
    
  887. 
    
  888.     ReactNoop.render(<Foo tick={1} idx={0} />);
    
  889.     ReactNoop.flushDeferredPri(70);
    
  890.     expect(ReactNoop).toMatchRenderedOutput(
    
  891.       <div>
    
  892.         <span prop={1} />
    
  893.         {/*still not rendered yet*/}
    
  894.         <div />
    
  895.       </div>,
    
  896.     );
    
  897. 
    
  898.     expect(ops).toEqual(['Foo']);
    
  899.     ops = [];
    
  900. 
    
  901.     await waitForAll([]);
    
  902.     expect(ReactNoop).toMatchRenderedOutput([
    
  903.       <div>
    
  904.         <span prop={1} />,
    
  905.         <div>
    
  906.           {/* Now we had enough time to finish the spans. */}
    
  907.           <span prop={0} />,
    
  908.           <span prop={0} />,
    
  909.           <span prop={0} />,
    
  910.           <span prop={0} />,
    
  911.           <span prop={0} />,
    
  912.           <span prop={0} />,
    
  913.         </div>
    
  914.       </div>,
    
  915.     ]);
    
  916. 
    
  917.     expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar', 'Baz', 'Bar', 'Bar']);
    
  918.     ops = [];
    
  919. 
    
  920.     // Now we're going to update the index but we'll only let it finish half
    
  921.     // way through.
    
  922.     ReactNoop.render(<Foo tick={2} idx={1} />);
    
  923.     ReactNoop.flushDeferredPri(95);
    
  924.     expect(ReactNoop).toMatchRenderedOutput(
    
  925.       <div>
    
  926.         <span prop={2} />,
    
  927.         <div>
    
  928.           {/* Still same old numbers. */}
    
  929.           <span prop={0} />
    
  930.           <span prop={0} />
    
  931.           <span prop={0} />
    
  932.           <span prop={0} />
    
  933.           <span prop={0} />
    
  934.           <span prop={0} />
    
  935.         </div>
    
  936.       </div>,
    
  937.     );
    
  938. 
    
  939.     // We let it finish half way through. That means we'll have one fully
    
  940.     // completed Baz, one half-way completed Baz and one fully incomplete Baz.
    
  941.     expect(ops).toEqual(['Foo', 'Baz', 'Bar', 'Bar', 'Baz', 'Bar']);
    
  942.     ops = [];
    
  943. 
    
  944.     // We'll update again, without letting the new index update yet. Only half
    
  945.     // way through.
    
  946.     ReactNoop.render(<Foo tick={3} idx={1} />);
    
  947.     ReactNoop.flushDeferredPri(50);
    
  948.     expect(ReactNoop).toMatchRenderedOutput(
    
  949.       <div>
    
  950.         <span prop={3} />
    
  951.         <div>
    
  952.           {/* Old numbers. */}
    
  953.           <span prop={0} />
    
  954.           <span prop={0} />
    
  955.           <span prop={0} />
    
  956.           <span prop={0} />
    
  957.           <span prop={0} />
    
  958.           <span prop={0} />
    
  959.         </div>
    
  960.       </div>,
    
  961.     );
    
  962. 
    
  963.     expect(ops).toEqual(['Foo']);
    
  964.     ops = [];
    
  965. 
    
  966.     // We should now be able to reuse some of the work we've already done
    
  967.     // and replay those side-effects.
    
  968.     await waitForAll([]);
    
  969.     expect(ReactNoop).toMatchRenderedOutput([
    
  970.       <div>
    
  971.         <span prop={3} />,
    
  972.         <div>
    
  973.           {/* New numbers. */}
    
  974.           <span prop={1} />
    
  975.           <span prop={1} />
    
  976.           <span prop={1} />
    
  977.           <span prop={1} />
    
  978.           <span prop={1} />
    
  979.           <span prop={1} />
    
  980.         </div>
    
  981.       </div>,
    
  982.     ]);
    
  983. 
    
  984.     expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar']);
    
  985.   });
    
  986. 
    
  987.   // @gate www
    
  988.   it('deprioritizes setStates that happens within a deprioritized tree', async () => {
    
  989.     const barInstances = [];
    
  990. 
    
  991.     class Bar extends React.Component {
    
  992.       constructor() {
    
  993.         super();
    
  994.         this.state = {active: false};
    
  995.       }
    
  996.       activate() {
    
  997.         this.setState({active: true});
    
  998.       }
    
  999.       render() {
    
  1000.         barInstances.push(this);
    
  1001.         Scheduler.log('Bar');
    
  1002.         return <span prop={this.state.active ? 'X' : this.props.idx} />;
    
  1003.       }
    
  1004.     }
    
  1005.     function Foo(props) {
    
  1006.       Scheduler.log('Foo');
    
  1007.       return (
    
  1008.         <div>
    
  1009.           <span prop={props.tick} />
    
  1010.           <LegacyHiddenDiv mode="hidden">
    
  1011.             <Bar idx={props.idx} />
    
  1012.             <Bar idx={props.idx} />
    
  1013.             <Bar idx={props.idx} />
    
  1014.           </LegacyHiddenDiv>
    
  1015.         </div>
    
  1016.       );
    
  1017.     }
    
  1018.     ReactNoop.render(<Foo tick={0} idx={0} />);
    
  1019.     await waitForAll(['Foo', 'Bar', 'Bar', 'Bar']);
    
  1020.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  1021.       <div>
    
  1022.         <span prop={0} />
    
  1023.         <div hidden={true}>
    
  1024.           <span prop={0} />
    
  1025.           <span prop={0} />
    
  1026.           <span prop={0} />
    
  1027.         </div>
    
  1028.       </div>,
    
  1029.     );
    
  1030. 
    
  1031.     ReactNoop.render(<Foo tick={1} idx={1} />);
    
  1032.     await waitFor(['Foo', 'Bar', 'Bar']);
    
  1033.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  1034.       <div>
    
  1035.         {/* Updated */}
    
  1036.         <span prop={1} />
    
  1037.         <div hidden={true}>
    
  1038.           <span prop={0} />
    
  1039.           <span prop={0} />
    
  1040.           <span prop={0} />
    
  1041.         </div>
    
  1042.       </div>,
    
  1043.     );
    
  1044. 
    
  1045.     barInstances[0].activate();
    
  1046. 
    
  1047.     // This should not be enough time to render the content of all the hidden
    
  1048.     // items. Including the set state since that is deprioritized.
    
  1049.     // ReactNoop.flushDeferredPri(35);
    
  1050.     await waitFor(['Bar']);
    
  1051.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  1052.       <div>
    
  1053.         {/* Updated */}
    
  1054.         <span prop={1} />
    
  1055.         <div hidden={true}>
    
  1056.           {/* Still not updated */}
    
  1057.           <span prop={0} />
    
  1058.           <span prop={0} />
    
  1059.           <span prop={0} />
    
  1060.         </div>
    
  1061.       </div>,
    
  1062.     );
    
  1063. 
    
  1064.     // However, once we render fully, we will have enough time to finish it all
    
  1065.     // at once.
    
  1066.     await waitForAll(['Bar', 'Bar']);
    
  1067.     expect(ReactNoop.getChildrenAsJSX()).toEqual(
    
  1068.       <div>
    
  1069.         <span prop={1} />
    
  1070.         <div hidden={true}>
    
  1071.           {/* Now we had enough time to finish the spans. */}
    
  1072.           <span prop="X" />
    
  1073.           <span prop={1} />
    
  1074.           <span prop={1} />
    
  1075.         </div>
    
  1076.       </div>,
    
  1077.     );
    
  1078.   });
    
  1079.   // TODO: Test that side-effects are not cut off when a work in progress node
    
  1080.   // moves to "current" without flushing due to having lower priority. Does this
    
  1081.   // even happen? Maybe a child doesn't get processed because it is lower prio?
    
  1082. 
    
  1083.   it('calls callback after update is flushed', async () => {
    
  1084.     let instance;
    
  1085.     class Foo extends React.Component {
    
  1086.       constructor() {
    
  1087.         super();
    
  1088.         instance = this;
    
  1089.         this.state = {text: 'foo'};
    
  1090.       }
    
  1091.       render() {
    
  1092.         return <span prop={this.state.text} />;
    
  1093.       }
    
  1094.     }
    
  1095. 
    
  1096.     ReactNoop.render(<Foo />);
    
  1097.     await waitForAll([]);
    
  1098.     expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
    
  1099.     let called = false;
    
  1100.     instance.setState({text: 'bar'}, () => {
    
  1101.       expect(ReactNoop).toMatchRenderedOutput(<span prop="bar" />);
    
  1102.       called = true;
    
  1103.     });
    
  1104.     await waitForAll([]);
    
  1105.     expect(called).toBe(true);
    
  1106.   });
    
  1107. 
    
  1108.   it('calls setState callback even if component bails out', async () => {
    
  1109.     let instance;
    
  1110.     class Foo extends React.Component {
    
  1111.       constructor() {
    
  1112.         super();
    
  1113.         instance = this;
    
  1114.         this.state = {text: 'foo'};
    
  1115.       }
    
  1116.       shouldComponentUpdate(nextProps, nextState) {
    
  1117.         return this.state.text !== nextState.text;
    
  1118.       }
    
  1119.       render() {
    
  1120.         return <span prop={this.state.text} />;
    
  1121.       }
    
  1122.     }
    
  1123. 
    
  1124.     ReactNoop.render(<Foo />);
    
  1125.     await waitForAll([]);
    
  1126.     expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
    
  1127.     let called = false;
    
  1128.     instance.setState({}, () => {
    
  1129.       called = true;
    
  1130.     });
    
  1131.     await waitForAll([]);
    
  1132.     expect(called).toBe(true);
    
  1133.   });
    
  1134. 
    
  1135.   // TODO: Test that callbacks are not lost if an update is preempted.
    
  1136. 
    
  1137.   it('calls componentWillUnmount after a deletion, even if nested', async () => {
    
  1138.     const ops = [];
    
  1139. 
    
  1140.     class Bar extends React.Component {
    
  1141.       componentWillUnmount() {
    
  1142.         ops.push(this.props.name);
    
  1143.       }
    
  1144.       render() {
    
  1145.         return <span />;
    
  1146.       }
    
  1147.     }
    
  1148. 
    
  1149.     class Wrapper extends React.Component {
    
  1150.       componentWillUnmount() {
    
  1151.         ops.push('Wrapper');
    
  1152.       }
    
  1153.       render() {
    
  1154.         return <Bar name={this.props.name} />;
    
  1155.       }
    
  1156.     }
    
  1157. 
    
  1158.     function Foo(props) {
    
  1159.       return (
    
  1160.         <div>
    
  1161.           {props.show
    
  1162.             ? [
    
  1163.                 <Bar key="a" name="A" />,
    
  1164.                 <Wrapper key="b" name="B" />,
    
  1165.                 <div key="cd">
    
  1166.                   <Bar name="C" />
    
  1167.                   <Wrapper name="D" />,
    
  1168.                 </div>,
    
  1169.                 [<Bar key="e" name="E" />, <Bar key="f" name="F" />],
    
  1170.               ]
    
  1171.             : []}
    
  1172.           <div>{props.show ? <Bar key="g" name="G" /> : null}</div>
    
  1173.           <Bar name="this should not unmount" />
    
  1174.         </div>
    
  1175.       );
    
  1176.     }
    
  1177. 
    
  1178.     ReactNoop.render(<Foo show={true} />);
    
  1179.     await waitForAll([]);
    
  1180.     expect(ops).toEqual([]);
    
  1181. 
    
  1182.     ReactNoop.render(<Foo show={false} />);
    
  1183.     await waitForAll([]);
    
  1184.     expect(ops).toEqual([
    
  1185.       'A',
    
  1186.       'Wrapper',
    
  1187.       'B',
    
  1188.       'C',
    
  1189.       'Wrapper',
    
  1190.       'D',
    
  1191.       'E',
    
  1192.       'F',
    
  1193.       'G',
    
  1194.     ]);
    
  1195.   });
    
  1196. 
    
  1197.   it('calls componentDidMount/Update after insertion/update', async () => {
    
  1198.     let ops = [];
    
  1199. 
    
  1200.     class Bar extends React.Component {
    
  1201.       componentDidMount() {
    
  1202.         ops.push('mount:' + this.props.name);
    
  1203.       }
    
  1204.       componentDidUpdate() {
    
  1205.         ops.push('update:' + this.props.name);
    
  1206.       }
    
  1207.       render() {
    
  1208.         return <span />;
    
  1209.       }
    
  1210.     }
    
  1211. 
    
  1212.     class Wrapper extends React.Component {
    
  1213.       componentDidMount() {
    
  1214.         ops.push('mount:wrapper-' + this.props.name);
    
  1215.       }
    
  1216.       componentDidUpdate() {
    
  1217.         ops.push('update:wrapper-' + this.props.name);
    
  1218.       }
    
  1219.       render() {
    
  1220.         return <Bar name={this.props.name} />;
    
  1221.       }
    
  1222.     }
    
  1223. 
    
  1224.     function Foo(props) {
    
  1225.       return (
    
  1226.         <div>
    
  1227.           <Bar key="a" name="A" />
    
  1228.           <Wrapper key="b" name="B" />
    
  1229.           <div key="cd">
    
  1230.             <Bar name="C" />
    
  1231.             <Wrapper name="D" />
    
  1232.           </div>
    
  1233.           {[<Bar key="e" name="E" />, <Bar key="f" name="F" />]}
    
  1234.           <div>
    
  1235.             <Bar key="g" name="G" />
    
  1236.           </div>
    
  1237.         </div>
    
  1238.       );
    
  1239.     }
    
  1240. 
    
  1241.     ReactNoop.render(<Foo />);
    
  1242.     await waitForAll([]);
    
  1243.     expect(ops).toEqual([
    
  1244.       'mount:A',
    
  1245.       'mount:B',
    
  1246.       'mount:wrapper-B',
    
  1247.       'mount:C',
    
  1248.       'mount:D',
    
  1249.       'mount:wrapper-D',
    
  1250.       'mount:E',
    
  1251.       'mount:F',
    
  1252.       'mount:G',
    
  1253.     ]);
    
  1254. 
    
  1255.     ops = [];
    
  1256. 
    
  1257.     ReactNoop.render(<Foo />);
    
  1258.     await waitForAll([]);
    
  1259.     expect(ops).toEqual([
    
  1260.       'update:A',
    
  1261.       'update:B',
    
  1262.       'update:wrapper-B',
    
  1263.       'update:C',
    
  1264.       'update:D',
    
  1265.       'update:wrapper-D',
    
  1266.       'update:E',
    
  1267.       'update:F',
    
  1268.       'update:G',
    
  1269.     ]);
    
  1270.   });
    
  1271. 
    
  1272.   it('invokes ref callbacks after insertion/update/unmount', async () => {
    
  1273.     let classInstance = null;
    
  1274. 
    
  1275.     let ops = [];
    
  1276. 
    
  1277.     class ClassComponent extends React.Component {
    
  1278.       render() {
    
  1279.         classInstance = this;
    
  1280.         return <span />;
    
  1281.       }
    
  1282.     }
    
  1283. 
    
  1284.     function FunctionComponent(props) {
    
  1285.       return <span />;
    
  1286.     }
    
  1287. 
    
  1288.     function Foo(props) {
    
  1289.       return props.show ? (
    
  1290.         <div>
    
  1291.           <ClassComponent ref={n => ops.push(n)} />
    
  1292.           <FunctionComponent ref={n => ops.push(n)} />
    
  1293.           <div ref={n => ops.push(n)} />
    
  1294.         </div>
    
  1295.       ) : null;
    
  1296.     }
    
  1297. 
    
  1298.     ReactNoop.render(<Foo show={true} />);
    
  1299.     await expect(async () => await waitForAll([])).toErrorDev(
    
  1300.       'Warning: Function components cannot be given refs. ' +
    
  1301.         'Attempts to access this ref will fail. ' +
    
  1302.         'Did you mean to use React.forwardRef()?\n\n' +
    
  1303.         'Check the render method ' +
    
  1304.         'of `Foo`.\n' +
    
  1305.         '    in FunctionComponent (at **)\n' +
    
  1306.         '    in div (at **)\n' +
    
  1307.         '    in Foo (at **)',
    
  1308.     );
    
  1309.     expect(ops).toEqual([
    
  1310.       classInstance,
    
  1311.       // no call for function components
    
  1312.       {type: 'div', children: [], prop: undefined, hidden: false},
    
  1313.     ]);
    
  1314. 
    
  1315.     ops = [];
    
  1316. 
    
  1317.     // Refs that switch function instances get reinvoked
    
  1318.     ReactNoop.render(<Foo show={true} />);
    
  1319.     await waitForAll([]);
    
  1320.     expect(ops).toEqual([
    
  1321.       // detach all refs that switched handlers first.
    
  1322.       null,
    
  1323.       null,
    
  1324.       // reattach as a separate phase
    
  1325.       classInstance,
    
  1326.       {type: 'div', children: [], prop: undefined, hidden: false},
    
  1327.     ]);
    
  1328. 
    
  1329.     ops = [];
    
  1330. 
    
  1331.     ReactNoop.render(<Foo show={false} />);
    
  1332.     await waitForAll([]);
    
  1333.     expect(ops).toEqual([
    
  1334.       // unmount
    
  1335.       null,
    
  1336.       null,
    
  1337.     ]);
    
  1338.   });
    
  1339. 
    
  1340.   // TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
    
  1341.   // expected way for aborted and resumed render life-cycles.
    
  1342. 
    
  1343.   it('supports string refs', async () => {
    
  1344.     let fooInstance = null;
    
  1345. 
    
  1346.     class Bar extends React.Component {
    
  1347.       componentDidMount() {
    
  1348.         this.test = 'test';
    
  1349.       }
    
  1350.       render() {
    
  1351.         return <div />;
    
  1352.       }
    
  1353.     }
    
  1354. 
    
  1355.     class Foo extends React.Component {
    
  1356.       render() {
    
  1357.         fooInstance = this;
    
  1358.         return <Bar ref="bar" />;
    
  1359.       }
    
  1360.     }
    
  1361. 
    
  1362.     ReactNoop.render(<Foo />);
    
  1363.     await expect(async () => {
    
  1364.       await waitForAll([]);
    
  1365.     }).toErrorDev([
    
  1366.       'Warning: Component "Foo" contains the string ref "bar". ' +
    
  1367.         'Support for string refs will be removed in a future major release. ' +
    
  1368.         'We recommend using useRef() or createRef() instead. ' +
    
  1369.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  1370.         '    in Foo (at **)',
    
  1371.     ]);
    
  1372.     expect(fooInstance.refs.bar.test).toEqual('test');
    
  1373.   });
    
  1374. });