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 PropTypes;
    
  17. let waitForAll;
    
  18. let waitFor;
    
  19. let waitForThrow;
    
  20. let assertLog;
    
  21. 
    
  22. describe('ReactIncremental', () => {
    
  23.   beforeEach(() => {
    
  24.     jest.resetModules();
    
  25.     React = require('react');
    
  26.     ReactNoop = require('react-noop-renderer');
    
  27.     Scheduler = require('scheduler');
    
  28.     PropTypes = require('prop-types');
    
  29. 
    
  30.     const InternalTestUtils = require('internal-test-utils');
    
  31.     waitForAll = InternalTestUtils.waitForAll;
    
  32.     waitFor = InternalTestUtils.waitFor;
    
  33.     waitForThrow = InternalTestUtils.waitForThrow;
    
  34.     assertLog = InternalTestUtils.assertLog;
    
  35.   });
    
  36. 
    
  37.   // Note: This is based on a similar component we use in www. We can delete
    
  38.   // once the extra div wrapper is no longer necessary.
    
  39.   function LegacyHiddenDiv({children, mode}) {
    
  40.     return (
    
  41.       <div hidden={mode === 'hidden'}>
    
  42.         <React.unstable_LegacyHidden
    
  43.           mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
    
  44.           {children}
    
  45.         </React.unstable_LegacyHidden>
    
  46.       </div>
    
  47.     );
    
  48.   }
    
  49. 
    
  50.   it('should render a simple component', async () => {
    
  51.     function Bar() {
    
  52.       return <div>Hello World</div>;
    
  53.     }
    
  54. 
    
  55.     function Foo() {
    
  56.       return <Bar isBar={true} />;
    
  57.     }
    
  58. 
    
  59.     ReactNoop.render(<Foo />);
    
  60.     await waitForAll([]);
    
  61.   });
    
  62. 
    
  63.   it('should render a simple component, in steps if needed', async () => {
    
  64.     function Bar() {
    
  65.       Scheduler.log('Bar');
    
  66.       return (
    
  67.         <span>
    
  68.           <div>Hello World</div>
    
  69.         </span>
    
  70.       );
    
  71.     }
    
  72. 
    
  73.     function Foo() {
    
  74.       Scheduler.log('Foo');
    
  75.       return [<Bar key="a" isBar={true} />, <Bar key="b" isBar={true} />];
    
  76.     }
    
  77. 
    
  78.     React.startTransition(() => {
    
  79.       ReactNoop.render(<Foo />, () => Scheduler.log('callback'));
    
  80.     });
    
  81.     // Do one step of work.
    
  82.     await waitFor(['Foo']);
    
  83. 
    
  84.     // Do the rest of the work.
    
  85.     await waitForAll(['Bar', 'Bar', 'callback']);
    
  86.   });
    
  87. 
    
  88.   it('updates a previous render', async () => {
    
  89.     function Header() {
    
  90.       Scheduler.log('Header');
    
  91.       return <h1>Hi</h1>;
    
  92.     }
    
  93. 
    
  94.     function Content(props) {
    
  95.       Scheduler.log('Content');
    
  96.       return <div>{props.children}</div>;
    
  97.     }
    
  98. 
    
  99.     function Footer() {
    
  100.       Scheduler.log('Footer');
    
  101.       return <footer>Bye</footer>;
    
  102.     }
    
  103. 
    
  104.     const header = <Header />;
    
  105.     const footer = <Footer />;
    
  106. 
    
  107.     function Foo(props) {
    
  108.       Scheduler.log('Foo');
    
  109.       return (
    
  110.         <div>
    
  111.           {header}
    
  112.           <Content>{props.text}</Content>
    
  113.           {footer}
    
  114.         </div>
    
  115.       );
    
  116.     }
    
  117. 
    
  118.     ReactNoop.render(<Foo text="foo" />, () =>
    
  119.       Scheduler.log('renderCallbackCalled'),
    
  120.     );
    
  121.     await waitForAll([
    
  122.       'Foo',
    
  123.       'Header',
    
  124.       'Content',
    
  125.       'Footer',
    
  126.       'renderCallbackCalled',
    
  127.     ]);
    
  128. 
    
  129.     ReactNoop.render(<Foo text="bar" />, () =>
    
  130.       Scheduler.log('firstRenderCallbackCalled'),
    
  131.     );
    
  132.     ReactNoop.render(<Foo text="bar" />, () =>
    
  133.       Scheduler.log('secondRenderCallbackCalled'),
    
  134.     );
    
  135.     // TODO: Test bail out of host components. This is currently unobservable.
    
  136. 
    
  137.     // Since this is an update, it should bail out and reuse the work from
    
  138.     // Header and Content.
    
  139.     await waitForAll([
    
  140.       'Foo',
    
  141.       'Content',
    
  142.       'firstRenderCallbackCalled',
    
  143.       'secondRenderCallbackCalled',
    
  144.     ]);
    
  145.   });
    
  146. 
    
  147.   it('can cancel partially rendered work and restart', async () => {
    
  148.     function Bar(props) {
    
  149.       Scheduler.log('Bar');
    
  150.       return <div>{props.children}</div>;
    
  151.     }
    
  152. 
    
  153.     function Foo(props) {
    
  154.       Scheduler.log('Foo');
    
  155.       return (
    
  156.         <div>
    
  157.           <Bar>{props.text}</Bar>
    
  158.           <Bar>{props.text}</Bar>
    
  159.         </div>
    
  160.       );
    
  161.     }
    
  162. 
    
  163.     // Init
    
  164.     ReactNoop.render(<Foo text="foo" />);
    
  165.     await waitForAll(['Foo', 'Bar', 'Bar']);
    
  166. 
    
  167.     React.startTransition(() => {
    
  168.       ReactNoop.render(<Foo text="bar" />);
    
  169.     });
    
  170.     // Flush part of the work
    
  171.     await waitFor(['Foo', 'Bar']);
    
  172. 
    
  173.     // This will abort the previous work and restart
    
  174.     ReactNoop.flushSync(() => ReactNoop.render(null));
    
  175. 
    
  176.     React.startTransition(() => {
    
  177.       ReactNoop.render(<Foo text="baz" />);
    
  178.     });
    
  179. 
    
  180.     // Flush part of the new work
    
  181.     await waitFor(['Foo', 'Bar']);
    
  182. 
    
  183.     // Flush the rest of the work which now includes the low priority
    
  184.     await waitForAll(['Bar']);
    
  185.   });
    
  186. 
    
  187.   it('should call callbacks even if updates are aborted', async () => {
    
  188.     let inst;
    
  189. 
    
  190.     class Foo extends React.Component {
    
  191.       constructor(props) {
    
  192.         super(props);
    
  193.         this.state = {
    
  194.           text: 'foo',
    
  195.           text2: 'foo',
    
  196.         };
    
  197.         inst = this;
    
  198.       }
    
  199.       render() {
    
  200.         return (
    
  201.           <div>
    
  202.             <div>{this.state.text}</div>
    
  203.             <div>{this.state.text2}</div>
    
  204.           </div>
    
  205.         );
    
  206.       }
    
  207.     }
    
  208. 
    
  209.     ReactNoop.render(<Foo />);
    
  210.     await waitForAll([]);
    
  211. 
    
  212.     React.startTransition(() => {
    
  213.       inst.setState(
    
  214.         () => {
    
  215.           Scheduler.log('setState1');
    
  216.           return {text: 'bar'};
    
  217.         },
    
  218.         () => Scheduler.log('callback1'),
    
  219.       );
    
  220.     });
    
  221. 
    
  222.     // Flush part of the work
    
  223.     await waitFor(['setState1']);
    
  224. 
    
  225.     // This will abort the previous work and restart
    
  226.     ReactNoop.flushSync(() => ReactNoop.render(<Foo />));
    
  227.     React.startTransition(() => {
    
  228.       inst.setState(
    
  229.         () => {
    
  230.           Scheduler.log('setState2');
    
  231.           return {text2: 'baz'};
    
  232.         },
    
  233.         () => Scheduler.log('callback2'),
    
  234.       );
    
  235.     });
    
  236. 
    
  237.     // Flush the rest of the work which now includes the low priority
    
  238.     await waitForAll(['setState1', 'setState2', 'callback1', 'callback2']);
    
  239.     expect(inst.state).toEqual({text: 'bar', text2: 'baz'});
    
  240.   });
    
  241. 
    
  242.   // @gate www
    
  243.   it('can deprioritize unfinished work and resume it later', async () => {
    
  244.     function Bar(props) {
    
  245.       Scheduler.log('Bar');
    
  246.       return <div>{props.children}</div>;
    
  247.     }
    
  248. 
    
  249.     function Middle(props) {
    
  250.       Scheduler.log('Middle');
    
  251.       return <span>{props.children}</span>;
    
  252.     }
    
  253. 
    
  254.     function Foo(props) {
    
  255.       Scheduler.log('Foo');
    
  256.       return (
    
  257.         <div>
    
  258.           <Bar>{props.text}</Bar>
    
  259.           <LegacyHiddenDiv mode="hidden">
    
  260.             <Middle>{props.text}</Middle>
    
  261.           </LegacyHiddenDiv>
    
  262.           <Bar>{props.text}</Bar>
    
  263.           <LegacyHiddenDiv mode="hidden">
    
  264.             <Middle>Footer</Middle>
    
  265.           </LegacyHiddenDiv>
    
  266.         </div>
    
  267.       );
    
  268.     }
    
  269. 
    
  270.     // Init
    
  271.     ReactNoop.render(<Foo text="foo" />);
    
  272.     await waitForAll(['Foo', 'Bar', 'Bar', 'Middle', 'Middle']);
    
  273. 
    
  274.     // Render part of the work. This should be enough to flush everything except
    
  275.     // the middle which has lower priority.
    
  276.     ReactNoop.render(<Foo text="bar" />);
    
  277.     await waitFor(['Foo', 'Bar', 'Bar']);
    
  278.     // Flush only the remaining work
    
  279.     await waitForAll(['Middle', 'Middle']);
    
  280.   });
    
  281. 
    
  282.   // @gate www
    
  283.   it('can deprioritize a tree from without dropping work', async () => {
    
  284.     function Bar(props) {
    
  285.       Scheduler.log('Bar');
    
  286.       return <div>{props.children}</div>;
    
  287.     }
    
  288. 
    
  289.     function Middle(props) {
    
  290.       Scheduler.log('Middle');
    
  291.       return <span>{props.children}</span>;
    
  292.     }
    
  293. 
    
  294.     function Foo(props) {
    
  295.       Scheduler.log('Foo');
    
  296.       return (
    
  297.         <div>
    
  298.           <Bar>{props.text}</Bar>
    
  299.           <LegacyHiddenDiv mode="hidden">
    
  300.             <Middle>{props.text}</Middle>
    
  301.           </LegacyHiddenDiv>
    
  302.           <Bar>{props.text}</Bar>
    
  303.           <LegacyHiddenDiv mode="hidden">
    
  304.             <Middle>Footer</Middle>
    
  305.           </LegacyHiddenDiv>
    
  306.         </div>
    
  307.       );
    
  308.     }
    
  309. 
    
  310.     // Init
    
  311.     ReactNoop.flushSync(() => {
    
  312.       ReactNoop.render(<Foo text="foo" />);
    
  313.     });
    
  314.     assertLog(['Foo', 'Bar', 'Bar']);
    
  315.     await waitForAll(['Middle', 'Middle']);
    
  316. 
    
  317.     // Render the high priority work (everything except the hidden trees).
    
  318.     ReactNoop.flushSync(() => {
    
  319.       ReactNoop.render(<Foo text="foo" />);
    
  320.     });
    
  321.     assertLog(['Foo', 'Bar', 'Bar']);
    
  322. 
    
  323.     // The hidden content was deprioritized from high to low priority. A low
    
  324.     // priority callback should have been scheduled. Flush it now.
    
  325.     await waitForAll(['Middle', 'Middle']);
    
  326.   });
    
  327. 
    
  328.   xit('can resume work in a subtree even when a parent bails out', async () => {
    
  329.     function Bar(props) {
    
  330.       Scheduler.log('Bar');
    
  331.       return <div>{props.children}</div>;
    
  332.     }
    
  333. 
    
  334.     function Tester() {
    
  335.       // This component is just here to ensure that the bail out is
    
  336.       // in fact in effect in the expected place for this test.
    
  337.       Scheduler.log('Tester');
    
  338.       return <div />;
    
  339.     }
    
  340. 
    
  341.     function Middle(props) {
    
  342.       Scheduler.log('Middle');
    
  343.       return <span>{props.children}</span>;
    
  344.     }
    
  345. 
    
  346.     const middleContent = (
    
  347.       <aaa>
    
  348.         <Tester />
    
  349.         <bbb hidden={true}>
    
  350.           <ccc>
    
  351.             <Middle>Hi</Middle>
    
  352.           </ccc>
    
  353.         </bbb>
    
  354.       </aaa>
    
  355.     );
    
  356. 
    
  357.     function Foo(props) {
    
  358.       Scheduler.log('Foo');
    
  359.       return (
    
  360.         <div>
    
  361.           <Bar>{props.text}</Bar>
    
  362.           {middleContent}
    
  363.           <Bar>{props.text}</Bar>
    
  364.         </div>
    
  365.       );
    
  366.     }
    
  367. 
    
  368.     // Init
    
  369.     ReactNoop.render(<Foo text="foo" />);
    
  370.     ReactNoop.flushDeferredPri(52);
    
  371. 
    
  372.     assertLog(['Foo', 'Bar', 'Tester', 'Bar']);
    
  373. 
    
  374.     // We're now rendering an update that will bail out on updating middle.
    
  375.     ReactNoop.render(<Foo text="bar" />);
    
  376.     ReactNoop.flushDeferredPri(45 + 5);
    
  377. 
    
  378.     assertLog(['Foo', 'Bar', 'Bar']);
    
  379. 
    
  380.     // Flush the rest to make sure that the bailout didn't block this work.
    
  381.     await waitForAll(['Middle']);
    
  382.   });
    
  383. 
    
  384.   xit('can resume work in a bailed subtree within one pass', async () => {
    
  385.     function Bar(props) {
    
  386.       Scheduler.log('Bar');
    
  387.       return <div>{props.children}</div>;
    
  388.     }
    
  389. 
    
  390.     class Tester extends React.Component {
    
  391.       shouldComponentUpdate() {
    
  392.         return false;
    
  393.       }
    
  394.       render() {
    
  395.         // This component is just here to ensure that the bail out is
    
  396.         // in fact in effect in the expected place for this test.
    
  397.         Scheduler.log('Tester');
    
  398.         return <div />;
    
  399.       }
    
  400.     }
    
  401. 
    
  402.     function Middle(props) {
    
  403.       Scheduler.log('Middle');
    
  404.       return <span>{props.children}</span>;
    
  405.     }
    
  406. 
    
  407.     // Should content not just bail out on current, not workInProgress?
    
  408. 
    
  409.     class Content extends React.Component {
    
  410.       shouldComponentUpdate() {
    
  411.         return false;
    
  412.       }
    
  413.       render() {
    
  414.         return [
    
  415.           <Tester key="a" unused={this.props.unused} />,
    
  416.           <bbb key="b" hidden={true}>
    
  417.             <ccc>
    
  418.               <Middle>Hi</Middle>
    
  419.             </ccc>
    
  420.           </bbb>,
    
  421.         ];
    
  422.       }
    
  423.     }
    
  424. 
    
  425.     function Foo(props) {
    
  426.       Scheduler.log('Foo');
    
  427.       return (
    
  428.         <div hidden={props.text === 'bar'}>
    
  429.           <Bar>{props.text}</Bar>
    
  430.           <Content unused={props.text} />
    
  431.           <Bar>{props.text}</Bar>
    
  432.         </div>
    
  433.       );
    
  434.     }
    
  435. 
    
  436.     // Init
    
  437.     ReactNoop.render(<Foo text="foo" />);
    
  438.     ReactNoop.flushDeferredPri(52 + 5);
    
  439. 
    
  440.     assertLog(['Foo', 'Bar', 'Tester', 'Bar']);
    
  441. 
    
  442.     // Make a quick update which will create a low pri tree on top of the
    
  443.     // already low pri tree.
    
  444.     ReactNoop.render(<Foo text="bar" />);
    
  445.     ReactNoop.flushDeferredPri(15);
    
  446. 
    
  447.     assertLog(['Foo']);
    
  448. 
    
  449.     // At this point, middle will bail out but it has not yet fully rendered.
    
  450.     // Since that is the same priority as its parent tree. This should render
    
  451.     // as a single batch. Therefore, it is correct that Middle should be in the
    
  452.     // middle. If it occurs after the two "Bar" components then it was flushed
    
  453.     // after them which is not correct.
    
  454.     await waitForAll(['Bar', 'Middle', 'Bar']);
    
  455. 
    
  456.     // Let us try this again without fully finishing the first time. This will
    
  457.     // create a hanging subtree that is reconciling at the normal priority.
    
  458.     ReactNoop.render(<Foo text="foo" />);
    
  459.     ReactNoop.flushDeferredPri(40);
    
  460. 
    
  461.     assertLog(['Foo', 'Bar']);
    
  462. 
    
  463.     // This update will create a tree that aborts that work and down-prioritizes
    
  464.     // it. If the priority levels aren't down-prioritized correctly this may
    
  465.     // abort rendering of the down-prioritized content.
    
  466.     ReactNoop.render(<Foo text="bar" />);
    
  467.     await waitForAll(['Foo', 'Bar', 'Bar']);
    
  468.   });
    
  469. 
    
  470.   xit('can resume mounting a class component', async () => {
    
  471.     let foo;
    
  472.     class Parent extends React.Component {
    
  473.       shouldComponentUpdate() {
    
  474.         return false;
    
  475.       }
    
  476.       render() {
    
  477.         return <Foo prop={this.props.prop} />;
    
  478.       }
    
  479.     }
    
  480. 
    
  481.     class Foo extends React.Component {
    
  482.       constructor(props) {
    
  483.         super(props);
    
  484.         // Test based on a www bug where props was null on resume
    
  485.         Scheduler.log('Foo constructor: ' + props.prop);
    
  486.       }
    
  487.       render() {
    
  488.         foo = this;
    
  489.         Scheduler.log('Foo');
    
  490.         return <Bar />;
    
  491.       }
    
  492.     }
    
  493. 
    
  494.     function Bar() {
    
  495.       Scheduler.log('Bar');
    
  496.       return <div />;
    
  497.     }
    
  498. 
    
  499.     ReactNoop.render(<Parent prop="foo" />);
    
  500.     ReactNoop.flushDeferredPri(20);
    
  501.     assertLog(['Foo constructor: foo', 'Foo']);
    
  502. 
    
  503.     foo.setState({value: 'bar'});
    
  504. 
    
  505.     await waitForAll(['Foo', 'Bar']);
    
  506.   });
    
  507. 
    
  508.   xit('reuses the same instance when resuming a class instance', async () => {
    
  509.     let foo;
    
  510.     class Parent extends React.Component {
    
  511.       shouldComponentUpdate() {
    
  512.         return false;
    
  513.       }
    
  514.       render() {
    
  515.         return <Foo prop={this.props.prop} />;
    
  516.       }
    
  517.     }
    
  518. 
    
  519.     let constructorCount = 0;
    
  520.     class Foo extends React.Component {
    
  521.       constructor(props) {
    
  522.         super(props);
    
  523.         // Test based on a www bug where props was null on resume
    
  524.         Scheduler.log('constructor: ' + props.prop);
    
  525.         constructorCount++;
    
  526.       }
    
  527.       UNSAFE_componentWillMount() {
    
  528.         Scheduler.log('componentWillMount: ' + this.props.prop);
    
  529.       }
    
  530.       UNSAFE_componentWillReceiveProps() {
    
  531.         Scheduler.log('componentWillReceiveProps: ' + this.props.prop);
    
  532.       }
    
  533.       componentDidMount() {
    
  534.         Scheduler.log('componentDidMount: ' + this.props.prop);
    
  535.       }
    
  536.       UNSAFE_componentWillUpdate() {
    
  537.         Scheduler.log('componentWillUpdate: ' + this.props.prop);
    
  538.       }
    
  539.       componentDidUpdate() {
    
  540.         Scheduler.log('componentDidUpdate: ' + this.props.prop);
    
  541.       }
    
  542.       render() {
    
  543.         foo = this;
    
  544.         Scheduler.log('render: ' + this.props.prop);
    
  545.         return <Bar />;
    
  546.       }
    
  547.     }
    
  548. 
    
  549.     function Bar() {
    
  550.       Scheduler.log('Foo did complete');
    
  551.       return <div />;
    
  552.     }
    
  553. 
    
  554.     ReactNoop.render(<Parent prop="foo" />);
    
  555.     ReactNoop.flushDeferredPri(25);
    
  556.     assertLog([
    
  557.       'constructor: foo',
    
  558.       'componentWillMount: foo',
    
  559.       'render: foo',
    
  560.       'Foo did complete',
    
  561.     ]);
    
  562. 
    
  563.     foo.setState({value: 'bar'});
    
  564. 
    
  565.     await waitForAll([]);
    
  566.     expect(constructorCount).toEqual(1);
    
  567.     assertLog([
    
  568.       'componentWillMount: foo',
    
  569.       'render: foo',
    
  570.       'Foo did complete',
    
  571.       'componentDidMount: foo',
    
  572.     ]);
    
  573.   });
    
  574. 
    
  575.   xit('can reuse work done after being preempted', async () => {
    
  576.     function Bar(props) {
    
  577.       Scheduler.log('Bar');
    
  578.       return <div>{props.children}</div>;
    
  579.     }
    
  580. 
    
  581.     function Middle(props) {
    
  582.       Scheduler.log('Middle');
    
  583.       return <span>{props.children}</span>;
    
  584.     }
    
  585. 
    
  586.     const middleContent = (
    
  587.       <div>
    
  588.         <Middle>Hello</Middle>
    
  589.         <Bar>-</Bar>
    
  590.         <Middle>World</Middle>
    
  591.       </div>
    
  592.     );
    
  593. 
    
  594.     const step0 = (
    
  595.       <div>
    
  596.         <Middle>Hi</Middle>
    
  597.         <Bar>{'Foo'}</Bar>
    
  598.         <Middle>There</Middle>
    
  599.       </div>
    
  600.     );
    
  601. 
    
  602.     function Foo(props) {
    
  603.       Scheduler.log('Foo');
    
  604.       return (
    
  605.         <div>
    
  606.           <Bar>{props.text2}</Bar>
    
  607.           <div hidden={true}>{props.step === 0 ? step0 : middleContent}</div>
    
  608.         </div>
    
  609.       );
    
  610.     }
    
  611. 
    
  612.     // Init
    
  613.     ReactNoop.render(<Foo text="foo" text2="foo" step={0} />);
    
  614.     ReactNoop.flushDeferredPri(55 + 25 + 5 + 5);
    
  615. 
    
  616.     // We only finish the higher priority work. So the low pri content
    
  617.     // has not yet finished mounting.
    
  618.     assertLog(['Foo', 'Bar', 'Middle', 'Bar']);
    
  619. 
    
  620.     // Interrupt the rendering with a quick update. This should not touch the
    
  621.     // middle content.
    
  622.     ReactNoop.render(<Foo text="foo" text2="bar" step={0} />);
    
  623.     await waitForAll([]);
    
  624. 
    
  625.     // We've now rendered the entire tree but we didn't have to redo the work
    
  626.     // done by the first Middle and Bar already.
    
  627.     assertLog(['Foo', 'Bar', 'Middle']);
    
  628. 
    
  629.     // Make a quick update which will schedule low priority work to
    
  630.     // update the middle content.
    
  631.     ReactNoop.render(<Foo text="bar" text2="bar" step={1} />);
    
  632.     ReactNoop.flushDeferredPri(30 + 25 + 5);
    
  633. 
    
  634.     assertLog(['Foo', 'Bar']);
    
  635. 
    
  636.     // The middle content is now pending rendering...
    
  637.     ReactNoop.flushDeferredPri(30 + 5);
    
  638.     assertLog(['Middle', 'Bar']);
    
  639. 
    
  640.     // but we'll interrupt it to render some higher priority work.
    
  641.     // The middle content will bailout so it remains untouched.
    
  642.     ReactNoop.render(<Foo text="foo" text2="bar" step={1} />);
    
  643.     ReactNoop.flushDeferredPri(30);
    
  644. 
    
  645.     assertLog(['Foo', 'Bar']);
    
  646. 
    
  647.     // Since we did nothing to the middle subtree during the interruption,
    
  648.     // we should be able to reuse the reconciliation work that we already did
    
  649.     // without restarting.
    
  650.     await waitForAll(['Middle']);
    
  651.   });
    
  652. 
    
  653.   xit('can reuse work that began but did not complete, after being preempted', async () => {
    
  654.     let child;
    
  655.     let sibling;
    
  656. 
    
  657.     function GreatGrandchild() {
    
  658.       Scheduler.log('GreatGrandchild');
    
  659.       return <div />;
    
  660.     }
    
  661. 
    
  662.     function Grandchild() {
    
  663.       Scheduler.log('Grandchild');
    
  664.       return <GreatGrandchild />;
    
  665.     }
    
  666. 
    
  667.     class Child extends React.Component {
    
  668.       state = {step: 0};
    
  669.       render() {
    
  670.         child = this;
    
  671.         Scheduler.log('Child');
    
  672.         return <Grandchild />;
    
  673.       }
    
  674.     }
    
  675. 
    
  676.     class Sibling extends React.Component {
    
  677.       render() {
    
  678.         Scheduler.log('Sibling');
    
  679.         sibling = this;
    
  680.         return <div />;
    
  681.       }
    
  682.     }
    
  683. 
    
  684.     function Parent() {
    
  685.       Scheduler.log('Parent');
    
  686.       return [
    
  687.         // The extra div is necessary because when Parent bails out during the
    
  688.         // high priority update, its progressedPriority is set to high.
    
  689.         // So its direct children cannot be reused when we resume at
    
  690.         // low priority. I think this would be fixed by changing
    
  691.         // pendingWorkPriority and progressedPriority to be the priority of
    
  692.         // the children only, not including the fiber itself.
    
  693.         <div key="a">
    
  694.           <Child />
    
  695.         </div>,
    
  696.         <Sibling key="b" />,
    
  697.       ];
    
  698.     }
    
  699. 
    
  700.     ReactNoop.render(<Parent />);
    
  701.     await waitForAll([]);
    
  702. 
    
  703.     // Begin working on a low priority update to Child, but stop before
    
  704.     // GreatGrandchild. Child and Grandchild begin but don't complete.
    
  705.     child.setState({step: 1});
    
  706.     ReactNoop.flushDeferredPri(30);
    
  707.     assertLog(['Child', 'Grandchild']);
    
  708. 
    
  709.     // Interrupt the current low pri work with a high pri update elsewhere in
    
  710.     // the tree.
    
  711. 
    
  712.     ReactNoop.flushSync(() => {
    
  713.       sibling.setState({});
    
  714.     });
    
  715.     assertLog(['Sibling']);
    
  716. 
    
  717.     // Continue the low pri work. The work on Child and GrandChild was memoized
    
  718.     // so they should not be worked on again.
    
  719. 
    
  720.     await waitForAll([
    
  721.       // No Child
    
  722.       // No Grandchild
    
  723.       'GreatGrandchild',
    
  724.     ]);
    
  725.   });
    
  726. 
    
  727.   xit('can reuse work if shouldComponentUpdate is false, after being preempted', async () => {
    
  728.     function Bar(props) {
    
  729.       Scheduler.log('Bar');
    
  730.       return <div>{props.children}</div>;
    
  731.     }
    
  732. 
    
  733.     class Middle extends React.Component {
    
  734.       shouldComponentUpdate(nextProps) {
    
  735.         return this.props.children !== nextProps.children;
    
  736.       }
    
  737.       render() {
    
  738.         Scheduler.log('Middle');
    
  739.         return <span>{this.props.children}</span>;
    
  740.       }
    
  741.     }
    
  742. 
    
  743.     class Content extends React.Component {
    
  744.       shouldComponentUpdate(nextProps) {
    
  745.         return this.props.step !== nextProps.step;
    
  746.       }
    
  747.       render() {
    
  748.         Scheduler.log('Content');
    
  749.         return (
    
  750.           <div>
    
  751.             <Middle>{this.props.step === 0 ? 'Hi' : 'Hello'}</Middle>
    
  752.             <Bar>{this.props.step === 0 ? this.props.text : '-'}</Bar>
    
  753.             <Middle>{this.props.step === 0 ? 'There' : 'World'}</Middle>
    
  754.           </div>
    
  755.         );
    
  756.       }
    
  757.     }
    
  758. 
    
  759.     function Foo(props) {
    
  760.       Scheduler.log('Foo');
    
  761.       return (
    
  762.         <div>
    
  763.           <Bar>{props.text}</Bar>
    
  764.           <div hidden={true}>
    
  765.             <Content step={props.step} text={props.text} />
    
  766.           </div>
    
  767.         </div>
    
  768.       );
    
  769.     }
    
  770. 
    
  771.     // Init
    
  772.     ReactNoop.render(<Foo text="foo" step={0} />);
    
  773.     await waitForAll(['Foo', 'Bar', 'Content', 'Middle', 'Bar', 'Middle']);
    
  774. 
    
  775.     // Make a quick update which will schedule low priority work to
    
  776.     // update the middle content.
    
  777.     ReactNoop.render(<Foo text="bar" step={1} />);
    
  778.     ReactNoop.flushDeferredPri(30 + 5);
    
  779. 
    
  780.     assertLog(['Foo', 'Bar']);
    
  781. 
    
  782.     // The middle content is now pending rendering...
    
  783.     ReactNoop.flushDeferredPri(30 + 25 + 5);
    
  784.     assertLog(['Content', 'Middle', 'Bar']); // One more Middle left.
    
  785. 
    
  786.     // but we'll interrupt it to render some higher priority work.
    
  787.     // The middle content will bailout so it remains untouched.
    
  788.     ReactNoop.render(<Foo text="foo" step={1} />);
    
  789.     ReactNoop.flushDeferredPri(30);
    
  790. 
    
  791.     assertLog(['Foo', 'Bar']);
    
  792. 
    
  793.     // Since we did nothing to the middle subtree during the interruption,
    
  794.     // we should be able to reuse the reconciliation work that we already did
    
  795.     // without restarting.
    
  796.     await waitForAll(['Middle']);
    
  797.   });
    
  798. 
    
  799.   it('memoizes work even if shouldComponentUpdate returns false', async () => {
    
  800.     class Foo extends React.Component {
    
  801.       shouldComponentUpdate(nextProps) {
    
  802.         // this.props is the memoized props. So this should return true for
    
  803.         // every update except the first one.
    
  804.         const shouldUpdate = this.props.step !== 1;
    
  805.         Scheduler.log('shouldComponentUpdate: ' + shouldUpdate);
    
  806.         return shouldUpdate;
    
  807.       }
    
  808.       render() {
    
  809.         Scheduler.log('render');
    
  810.         return <div />;
    
  811.       }
    
  812.     }
    
  813. 
    
  814.     ReactNoop.render(<Foo step={1} />);
    
  815.     await waitForAll(['render']);
    
  816. 
    
  817.     ReactNoop.render(<Foo step={2} />);
    
  818.     await waitForAll(['shouldComponentUpdate: false']);
    
  819. 
    
  820.     ReactNoop.render(<Foo step={3} />);
    
  821.     await waitForAll([
    
  822.       // If the memoized props were not updated during last bail out, sCU will
    
  823.       // keep returning false.
    
  824.       'shouldComponentUpdate: true',
    
  825.       'render',
    
  826.     ]);
    
  827.   });
    
  828. 
    
  829.   it('can update in the middle of a tree using setState', async () => {
    
  830.     let instance;
    
  831.     class Bar extends React.Component {
    
  832.       constructor() {
    
  833.         super();
    
  834.         this.state = {a: 'a'};
    
  835.         instance = this;
    
  836.       }
    
  837.       render() {
    
  838.         return <div>{this.props.children}</div>;
    
  839.       }
    
  840.     }
    
  841. 
    
  842.     function Foo() {
    
  843.       return (
    
  844.         <div>
    
  845.           <Bar />
    
  846.         </div>
    
  847.       );
    
  848.     }
    
  849. 
    
  850.     ReactNoop.render(<Foo />);
    
  851.     await waitForAll([]);
    
  852.     expect(instance.state).toEqual({a: 'a'});
    
  853.     instance.setState({b: 'b'});
    
  854.     await waitForAll([]);
    
  855.     expect(instance.state).toEqual({a: 'a', b: 'b'});
    
  856.   });
    
  857. 
    
  858.   it('can queue multiple state updates', async () => {
    
  859.     let instance;
    
  860.     class Bar extends React.Component {
    
  861.       constructor() {
    
  862.         super();
    
  863.         this.state = {a: 'a'};
    
  864.         instance = this;
    
  865.       }
    
  866.       render() {
    
  867.         return <div>{this.props.children}</div>;
    
  868.       }
    
  869.     }
    
  870. 
    
  871.     function Foo() {
    
  872.       return (
    
  873.         <div>
    
  874.           <Bar />
    
  875.         </div>
    
  876.       );
    
  877.     }
    
  878. 
    
  879.     ReactNoop.render(<Foo />);
    
  880.     await waitForAll([]);
    
  881.     // Call setState multiple times before flushing
    
  882.     instance.setState({b: 'b'});
    
  883.     instance.setState({c: 'c'});
    
  884.     instance.setState({d: 'd'});
    
  885.     await waitForAll([]);
    
  886.     expect(instance.state).toEqual({a: 'a', b: 'b', c: 'c', d: 'd'});
    
  887.   });
    
  888. 
    
  889.   it('can use updater form of setState', async () => {
    
  890.     let instance;
    
  891.     class Bar extends React.Component {
    
  892.       constructor() {
    
  893.         super();
    
  894.         this.state = {num: 1};
    
  895.         instance = this;
    
  896.       }
    
  897.       render() {
    
  898.         return <div>{this.props.children}</div>;
    
  899.       }
    
  900.     }
    
  901. 
    
  902.     function Foo({multiplier}) {
    
  903.       return (
    
  904.         <div>
    
  905.           <Bar multiplier={multiplier} />
    
  906.         </div>
    
  907.       );
    
  908.     }
    
  909. 
    
  910.     function updater(state, props) {
    
  911.       return {num: state.num * props.multiplier};
    
  912.     }
    
  913. 
    
  914.     ReactNoop.render(<Foo multiplier={2} />);
    
  915.     await waitForAll([]);
    
  916.     expect(instance.state.num).toEqual(1);
    
  917.     instance.setState(updater);
    
  918.     await waitForAll([]);
    
  919.     expect(instance.state.num).toEqual(2);
    
  920. 
    
  921.     instance.setState(updater);
    
  922.     ReactNoop.render(<Foo multiplier={3} />);
    
  923.     await waitForAll([]);
    
  924.     expect(instance.state.num).toEqual(6);
    
  925.   });
    
  926. 
    
  927.   it('can call setState inside update callback', async () => {
    
  928.     let instance;
    
  929.     class Bar extends React.Component {
    
  930.       constructor() {
    
  931.         super();
    
  932.         this.state = {num: 1};
    
  933.         instance = this;
    
  934.       }
    
  935.       render() {
    
  936.         return <div>{this.props.children}</div>;
    
  937.       }
    
  938.     }
    
  939. 
    
  940.     function Foo({multiplier}) {
    
  941.       return (
    
  942.         <div>
    
  943.           <Bar multiplier={multiplier} />
    
  944.         </div>
    
  945.       );
    
  946.     }
    
  947. 
    
  948.     function updater(state, props) {
    
  949.       return {num: state.num * props.multiplier};
    
  950.     }
    
  951. 
    
  952.     function callback() {
    
  953.       this.setState({called: true});
    
  954.     }
    
  955. 
    
  956.     ReactNoop.render(<Foo multiplier={2} />);
    
  957.     await waitForAll([]);
    
  958.     instance.setState(updater);
    
  959.     instance.setState(updater, callback);
    
  960.     await waitForAll([]);
    
  961.     expect(instance.state.num).toEqual(4);
    
  962.     expect(instance.state.called).toEqual(true);
    
  963.   });
    
  964. 
    
  965.   it('can replaceState', async () => {
    
  966.     let instance;
    
  967.     class Bar extends React.Component {
    
  968.       state = {a: 'a'};
    
  969.       render() {
    
  970.         instance = this;
    
  971.         return <div>{this.props.children}</div>;
    
  972.       }
    
  973.     }
    
  974. 
    
  975.     function Foo() {
    
  976.       return (
    
  977.         <div>
    
  978.           <Bar />
    
  979.         </div>
    
  980.       );
    
  981.     }
    
  982. 
    
  983.     ReactNoop.render(<Foo />);
    
  984.     await waitForAll([]);
    
  985.     instance.setState({b: 'b'});
    
  986.     instance.setState({c: 'c'});
    
  987.     instance.updater.enqueueReplaceState(instance, {d: 'd'});
    
  988.     await waitForAll([]);
    
  989.     expect(instance.state).toEqual({d: 'd'});
    
  990.   });
    
  991. 
    
  992.   it('can forceUpdate', async () => {
    
  993.     function Baz() {
    
  994.       Scheduler.log('Baz');
    
  995.       return <div />;
    
  996.     }
    
  997. 
    
  998.     let instance;
    
  999.     class Bar extends React.Component {
    
  1000.       constructor() {
    
  1001.         super();
    
  1002.         instance = this;
    
  1003.       }
    
  1004.       shouldComponentUpdate() {
    
  1005.         return false;
    
  1006.       }
    
  1007.       render() {
    
  1008.         Scheduler.log('Bar');
    
  1009.         return <Baz />;
    
  1010.       }
    
  1011.     }
    
  1012. 
    
  1013.     function Foo() {
    
  1014.       Scheduler.log('Foo');
    
  1015.       return (
    
  1016.         <div>
    
  1017.           <Bar />
    
  1018.         </div>
    
  1019.       );
    
  1020.     }
    
  1021. 
    
  1022.     ReactNoop.render(<Foo />);
    
  1023.     await waitForAll(['Foo', 'Bar', 'Baz']);
    
  1024.     instance.forceUpdate();
    
  1025.     await waitForAll(['Bar', 'Baz']);
    
  1026.   });
    
  1027. 
    
  1028.   it('should clear forceUpdate after update is flushed', async () => {
    
  1029.     let a = 0;
    
  1030. 
    
  1031.     class Foo extends React.PureComponent {
    
  1032.       render() {
    
  1033.         const msg = `A: ${a}, B: ${this.props.b}`;
    
  1034.         Scheduler.log(msg);
    
  1035.         return msg;
    
  1036.       }
    
  1037.     }
    
  1038. 
    
  1039.     const foo = React.createRef(null);
    
  1040.     ReactNoop.render(<Foo ref={foo} b={0} />);
    
  1041.     await waitForAll(['A: 0, B: 0']);
    
  1042. 
    
  1043.     a = 1;
    
  1044.     foo.current.forceUpdate();
    
  1045.     await waitForAll(['A: 1, B: 0']);
    
  1046. 
    
  1047.     ReactNoop.render(<Foo ref={foo} b={0} />);
    
  1048.     await waitForAll([]);
    
  1049.   });
    
  1050. 
    
  1051.   xit('can call sCU while resuming a partly mounted component', () => {
    
  1052.     const instances = new Set();
    
  1053. 
    
  1054.     class Bar extends React.Component {
    
  1055.       state = {y: 'A'};
    
  1056.       constructor() {
    
  1057.         super();
    
  1058.         instances.add(this);
    
  1059.       }
    
  1060.       shouldComponentUpdate(newProps, newState) {
    
  1061.         return this.props.x !== newProps.x || this.state.y !== newState.y;
    
  1062.       }
    
  1063.       render() {
    
  1064.         Scheduler.log('Bar:' + this.props.x);
    
  1065.         return <span prop={String(this.props.x === this.state.y)} />;
    
  1066.       }
    
  1067.     }
    
  1068. 
    
  1069.     function Foo(props) {
    
  1070.       Scheduler.log('Foo');
    
  1071.       return [
    
  1072.         <Bar key="a" x="A" />,
    
  1073.         <Bar key="b" x={props.step === 0 ? 'B' : 'B2'} />,
    
  1074.         <Bar key="c" x="C" />,
    
  1075.         <Bar key="d" x="D" />,
    
  1076.       ];
    
  1077.     }
    
  1078. 
    
  1079.     ReactNoop.render(<Foo step={0} />);
    
  1080.     ReactNoop.flushDeferredPri(40);
    
  1081.     assertLog(['Foo', 'Bar:A', 'Bar:B', 'Bar:C']);
    
  1082. 
    
  1083.     expect(instances.size).toBe(3);
    
  1084. 
    
  1085.     ReactNoop.render(<Foo step={1} />);
    
  1086.     ReactNoop.flushDeferredPri(50);
    
  1087.     // A was memoized and reused. B was memoized but couldn't be reused because
    
  1088.     // props differences. C was memoized and reused. D never even started so it
    
  1089.     // needed a new instance.
    
  1090.     assertLog(['Foo', 'Bar:B2', 'Bar:D']);
    
  1091. 
    
  1092.     // We expect each rerender to correspond to a new instance.
    
  1093.     expect(instances.size).toBe(4);
    
  1094.   });
    
  1095. 
    
  1096.   xit('gets new props when setting state on a partly updated component', async () => {
    
  1097.     const instances = [];
    
  1098. 
    
  1099.     class Bar extends React.Component {
    
  1100.       state = {y: 'A'};
    
  1101.       constructor() {
    
  1102.         super();
    
  1103.         instances.push(this);
    
  1104.       }
    
  1105.       performAction() {
    
  1106.         this.setState({
    
  1107.           y: 'B',
    
  1108.         });
    
  1109.       }
    
  1110.       render() {
    
  1111.         Scheduler.log('Bar:' + this.props.x + '-' + this.props.step);
    
  1112.         return <span prop={String(this.props.x === this.state.y)} />;
    
  1113.       }
    
  1114.     }
    
  1115. 
    
  1116.     function Baz() {
    
  1117.       // This component is used as a sibling to Foo so that we can fully
    
  1118.       // complete Foo, without committing.
    
  1119.       Scheduler.log('Baz');
    
  1120.       return <div />;
    
  1121.     }
    
  1122. 
    
  1123.     function Foo(props) {
    
  1124.       Scheduler.log('Foo');
    
  1125.       return [
    
  1126.         <Bar key="a" x="A" step={props.step} />,
    
  1127.         <Bar key="b" x="B" step={props.step} />,
    
  1128.       ];
    
  1129.     }
    
  1130. 
    
  1131.     ReactNoop.render(
    
  1132.       <div>
    
  1133.         <Foo step={0} />
    
  1134.         <Baz />
    
  1135.         <Baz />
    
  1136.       </div>,
    
  1137.     );
    
  1138.     await waitForAll([]);
    
  1139. 
    
  1140.     // Flush part way through with new props, fully completing the first Bar.
    
  1141.     // However, it doesn't commit yet.
    
  1142.     ReactNoop.render(
    
  1143.       <div>
    
  1144.         <Foo step={1} />
    
  1145.         <Baz />
    
  1146.         <Baz />
    
  1147.       </div>,
    
  1148.     );
    
  1149.     ReactNoop.flushDeferredPri(45);
    
  1150.     assertLog(['Foo', 'Bar:A-1', 'Bar:B-1', 'Baz']);
    
  1151. 
    
  1152.     // Make an update to the same Bar.
    
  1153.     instances[0].performAction();
    
  1154. 
    
  1155.     await waitForAll(['Bar:A-1', 'Baz']);
    
  1156.   });
    
  1157. 
    
  1158.   xit('calls componentWillMount twice if the initial render is aborted', async () => {
    
  1159.     class LifeCycle extends React.Component {
    
  1160.       state = {x: this.props.x};
    
  1161.       UNSAFE_componentWillReceiveProps(nextProps) {
    
  1162.         Scheduler.log(
    
  1163.           'componentWillReceiveProps:' + this.state.x + '-' + nextProps.x,
    
  1164.         );
    
  1165.         this.setState({x: nextProps.x});
    
  1166.       }
    
  1167.       UNSAFE_componentWillMount() {
    
  1168.         Scheduler.log(
    
  1169.           'componentWillMount:' + this.state.x + '-' + this.props.x,
    
  1170.         );
    
  1171.       }
    
  1172.       componentDidMount() {
    
  1173.         Scheduler.log('componentDidMount:' + this.state.x + '-' + this.props.x);
    
  1174.       }
    
  1175.       render() {
    
  1176.         return <span />;
    
  1177.       }
    
  1178.     }
    
  1179. 
    
  1180.     function Trail() {
    
  1181.       Scheduler.log('Trail');
    
  1182.       return null;
    
  1183.     }
    
  1184. 
    
  1185.     function App(props) {
    
  1186.       Scheduler.log('App');
    
  1187.       return (
    
  1188.         <div>
    
  1189.           <LifeCycle x={props.x} />
    
  1190.           <Trail />
    
  1191.         </div>
    
  1192.       );
    
  1193.     }
    
  1194. 
    
  1195.     ReactNoop.render(<App x={0} />);
    
  1196.     ReactNoop.flushDeferredPri(30);
    
  1197. 
    
  1198.     assertLog(['App', 'componentWillMount:0-0']);
    
  1199. 
    
  1200.     ReactNoop.render(<App x={1} />);
    
  1201.     await waitForAll([
    
  1202.       'App',
    
  1203.       'componentWillReceiveProps:0-1',
    
  1204.       'componentWillMount:1-1',
    
  1205.       'Trail',
    
  1206.       'componentDidMount:1-1',
    
  1207.     ]);
    
  1208.   });
    
  1209. 
    
  1210.   xit('uses state set in componentWillMount even if initial render was aborted', async () => {
    
  1211.     class LifeCycle extends React.Component {
    
  1212.       constructor(props) {
    
  1213.         super(props);
    
  1214.         this.state = {x: this.props.x + '(ctor)'};
    
  1215.       }
    
  1216.       UNSAFE_componentWillMount() {
    
  1217.         Scheduler.log('componentWillMount:' + this.state.x);
    
  1218.         this.setState({x: this.props.x + '(willMount)'});
    
  1219.       }
    
  1220.       componentDidMount() {
    
  1221.         Scheduler.log('componentDidMount:' + this.state.x);
    
  1222.       }
    
  1223.       render() {
    
  1224.         Scheduler.log('render:' + this.state.x);
    
  1225.         return <span />;
    
  1226.       }
    
  1227.     }
    
  1228. 
    
  1229.     function App(props) {
    
  1230.       Scheduler.log('App');
    
  1231.       return <LifeCycle x={props.x} />;
    
  1232.     }
    
  1233. 
    
  1234.     ReactNoop.render(<App x={0} />);
    
  1235.     ReactNoop.flushDeferredPri(20);
    
  1236. 
    
  1237.     assertLog(['App', 'componentWillMount:0(ctor)', 'render:0(willMount)']);
    
  1238. 
    
  1239.     ReactNoop.render(<App x={1} />);
    
  1240.     await waitForAll([
    
  1241.       'App',
    
  1242.       'componentWillMount:0(willMount)',
    
  1243.       'render:1(willMount)',
    
  1244.       'componentDidMount:1(willMount)',
    
  1245.     ]);
    
  1246.   });
    
  1247. 
    
  1248.   xit('calls componentWill* twice if an update render is aborted', async () => {
    
  1249.     class LifeCycle extends React.Component {
    
  1250.       UNSAFE_componentWillMount() {
    
  1251.         Scheduler.log('componentWillMount:' + this.props.x);
    
  1252.       }
    
  1253.       componentDidMount() {
    
  1254.         Scheduler.log('componentDidMount:' + this.props.x);
    
  1255.       }
    
  1256.       UNSAFE_componentWillReceiveProps(nextProps) {
    
  1257.         Scheduler.log(
    
  1258.           'componentWillReceiveProps:' + this.props.x + '-' + nextProps.x,
    
  1259.         );
    
  1260.       }
    
  1261.       shouldComponentUpdate(nextProps) {
    
  1262.         Scheduler.log(
    
  1263.           'shouldComponentUpdate:' + this.props.x + '-' + nextProps.x,
    
  1264.         );
    
  1265.         return true;
    
  1266.       }
    
  1267.       UNSAFE_componentWillUpdate(nextProps) {
    
  1268.         Scheduler.log(
    
  1269.           'componentWillUpdate:' + this.props.x + '-' + nextProps.x,
    
  1270.         );
    
  1271.       }
    
  1272.       componentDidUpdate(prevProps) {
    
  1273.         Scheduler.log('componentDidUpdate:' + this.props.x + '-' + prevProps.x);
    
  1274.       }
    
  1275.       render() {
    
  1276.         Scheduler.log('render:' + this.props.x);
    
  1277.         return <span />;
    
  1278.       }
    
  1279.     }
    
  1280. 
    
  1281.     function Sibling() {
    
  1282.       // The sibling is used to confirm that we've completed the first child,
    
  1283.       // but not yet flushed.
    
  1284.       Scheduler.log('Sibling');
    
  1285.       return <span />;
    
  1286.     }
    
  1287. 
    
  1288.     function App(props) {
    
  1289.       Scheduler.log('App');
    
  1290. 
    
  1291.       return [<LifeCycle key="a" x={props.x} />, <Sibling key="b" />];
    
  1292.     }
    
  1293. 
    
  1294.     ReactNoop.render(<App x={0} />);
    
  1295.     await waitForAll([
    
  1296.       'App',
    
  1297.       'componentWillMount:0',
    
  1298.       'render:0',
    
  1299.       'Sibling',
    
  1300.       'componentDidMount:0',
    
  1301.     ]);
    
  1302. 
    
  1303.     ReactNoop.render(<App x={1} />);
    
  1304.     ReactNoop.flushDeferredPri(30);
    
  1305. 
    
  1306.     assertLog([
    
  1307.       'App',
    
  1308.       'componentWillReceiveProps:0-1',
    
  1309.       'shouldComponentUpdate:0-1',
    
  1310.       'componentWillUpdate:0-1',
    
  1311.       'render:1',
    
  1312.       'Sibling',
    
  1313.       // no componentDidUpdate
    
  1314.     ]);
    
  1315. 
    
  1316.     ReactNoop.render(<App x={2} />);
    
  1317.     await waitForAll([
    
  1318.       'App',
    
  1319.       'componentWillReceiveProps:1-2',
    
  1320.       'shouldComponentUpdate:1-2',
    
  1321.       'componentWillUpdate:1-2',
    
  1322.       'render:2',
    
  1323.       'Sibling',
    
  1324.       // When componentDidUpdate finally gets called, it covers both updates.
    
  1325.       'componentDidUpdate:2-0',
    
  1326.     ]);
    
  1327.   });
    
  1328. 
    
  1329.   it('calls getDerivedStateFromProps even for state-only updates', async () => {
    
  1330.     let instance;
    
  1331. 
    
  1332.     class LifeCycle extends React.Component {
    
  1333.       state = {};
    
  1334.       static getDerivedStateFromProps(props, prevState) {
    
  1335.         Scheduler.log('getDerivedStateFromProps');
    
  1336.         return {foo: 'foo'};
    
  1337.       }
    
  1338.       changeState() {
    
  1339.         this.setState({foo: 'bar'});
    
  1340.       }
    
  1341.       componentDidUpdate() {
    
  1342.         Scheduler.log('componentDidUpdate');
    
  1343.       }
    
  1344.       render() {
    
  1345.         Scheduler.log('render');
    
  1346.         instance = this;
    
  1347.         return null;
    
  1348.       }
    
  1349.     }
    
  1350. 
    
  1351.     ReactNoop.render(<LifeCycle />);
    
  1352.     await waitForAll(['getDerivedStateFromProps', 'render']);
    
  1353.     expect(instance.state).toEqual({foo: 'foo'});
    
  1354. 
    
  1355.     instance.changeState();
    
  1356.     await waitForAll([
    
  1357.       'getDerivedStateFromProps',
    
  1358.       'render',
    
  1359.       'componentDidUpdate',
    
  1360.     ]);
    
  1361.     expect(instance.state).toEqual({foo: 'foo'});
    
  1362.   });
    
  1363. 
    
  1364.   it('does not call getDerivedStateFromProps if neither state nor props have changed', async () => {
    
  1365.     class Parent extends React.Component {
    
  1366.       state = {parentRenders: 0};
    
  1367.       static getDerivedStateFromProps(props, prevState) {
    
  1368.         Scheduler.log('getDerivedStateFromProps');
    
  1369.         return prevState.parentRenders + 1;
    
  1370.       }
    
  1371.       render() {
    
  1372.         Scheduler.log('Parent');
    
  1373.         return <Child parentRenders={this.state.parentRenders} ref={child} />;
    
  1374.       }
    
  1375.     }
    
  1376. 
    
  1377.     class Child extends React.Component {
    
  1378.       render() {
    
  1379.         Scheduler.log('Child');
    
  1380.         return this.props.parentRenders;
    
  1381.       }
    
  1382.     }
    
  1383. 
    
  1384.     const child = React.createRef(null);
    
  1385.     ReactNoop.render(<Parent />);
    
  1386.     await waitForAll(['getDerivedStateFromProps', 'Parent', 'Child']);
    
  1387. 
    
  1388.     // Schedule an update on the child. The parent should not re-render.
    
  1389.     child.current.setState({});
    
  1390.     await waitForAll(['Child']);
    
  1391.   });
    
  1392. 
    
  1393.   xit('does not call componentWillReceiveProps for state-only updates', async () => {
    
  1394.     const instances = [];
    
  1395. 
    
  1396.     class LifeCycle extends React.Component {
    
  1397.       state = {x: 0};
    
  1398.       tick() {
    
  1399.         this.setState({
    
  1400.           x: this.state.x + 1,
    
  1401.         });
    
  1402.       }
    
  1403.       UNSAFE_componentWillMount() {
    
  1404.         instances.push(this);
    
  1405.         Scheduler.log('componentWillMount:' + this.state.x);
    
  1406.       }
    
  1407.       componentDidMount() {
    
  1408.         Scheduler.log('componentDidMount:' + this.state.x);
    
  1409.       }
    
  1410.       UNSAFE_componentWillReceiveProps(nextProps) {
    
  1411.         Scheduler.log('componentWillReceiveProps');
    
  1412.       }
    
  1413.       shouldComponentUpdate(nextProps, nextState) {
    
  1414.         Scheduler.log(
    
  1415.           'shouldComponentUpdate:' + this.state.x + '-' + nextState.x,
    
  1416.         );
    
  1417.         return true;
    
  1418.       }
    
  1419.       UNSAFE_componentWillUpdate(nextProps, nextState) {
    
  1420.         Scheduler.log(
    
  1421.           'componentWillUpdate:' + this.state.x + '-' + nextState.x,
    
  1422.         );
    
  1423.       }
    
  1424.       componentDidUpdate(prevProps, prevState) {
    
  1425.         Scheduler.log('componentDidUpdate:' + this.state.x + '-' + prevState.x);
    
  1426.       }
    
  1427.       render() {
    
  1428.         Scheduler.log('render:' + this.state.x);
    
  1429.         return <span />;
    
  1430.       }
    
  1431.     }
    
  1432. 
    
  1433.     // This wrap is a bit contrived because we can't pause a completed root and
    
  1434.     // there is currently an issue where a component can't reuse its render
    
  1435.     // output unless it fully completed.
    
  1436.     class Wrap extends React.Component {
    
  1437.       state = {y: 0};
    
  1438.       UNSAFE_componentWillMount() {
    
  1439.         instances.push(this);
    
  1440.       }
    
  1441.       tick() {
    
  1442.         this.setState({
    
  1443.           y: this.state.y + 1,
    
  1444.         });
    
  1445.       }
    
  1446.       render() {
    
  1447.         Scheduler.log('Wrap');
    
  1448.         return <LifeCycle y={this.state.y} />;
    
  1449.       }
    
  1450.     }
    
  1451. 
    
  1452.     function Sibling() {
    
  1453.       // The sibling is used to confirm that we've completed the first child,
    
  1454.       // but not yet flushed.
    
  1455.       Scheduler.log('Sibling');
    
  1456.       return <span />;
    
  1457.     }
    
  1458. 
    
  1459.     function App(props) {
    
  1460.       Scheduler.log('App');
    
  1461.       return [<Wrap key="a" />, <Sibling key="b" />];
    
  1462.     }
    
  1463. 
    
  1464.     ReactNoop.render(<App y={0} />);
    
  1465.     await waitForAll([
    
  1466.       'App',
    
  1467.       'Wrap',
    
  1468.       'componentWillMount:0',
    
  1469.       'render:0',
    
  1470.       'Sibling',
    
  1471.       'componentDidMount:0',
    
  1472.     ]);
    
  1473. 
    
  1474.     // LifeCycle
    
  1475.     instances[1].tick();
    
  1476. 
    
  1477.     ReactNoop.flushDeferredPri(25);
    
  1478. 
    
  1479.     assertLog([
    
  1480.       // no componentWillReceiveProps
    
  1481.       'shouldComponentUpdate:0-1',
    
  1482.       'componentWillUpdate:0-1',
    
  1483.       'render:1',
    
  1484.       // no componentDidUpdate
    
  1485.     ]);
    
  1486. 
    
  1487.     // LifeCycle
    
  1488.     instances[1].tick();
    
  1489. 
    
  1490.     await waitForAll([
    
  1491.       // no componentWillReceiveProps
    
  1492.       'shouldComponentUpdate:1-2',
    
  1493.       'componentWillUpdate:1-2',
    
  1494.       'render:2',
    
  1495.       // When componentDidUpdate finally gets called, it covers both updates.
    
  1496.       'componentDidUpdate:2-0',
    
  1497.     ]);
    
  1498. 
    
  1499.     // Next we will update props of LifeCycle by updating its parent.
    
  1500. 
    
  1501.     instances[0].tick();
    
  1502. 
    
  1503.     ReactNoop.flushDeferredPri(30);
    
  1504. 
    
  1505.     assertLog([
    
  1506.       'Wrap',
    
  1507.       'componentWillReceiveProps',
    
  1508.       'shouldComponentUpdate:2-2',
    
  1509.       'componentWillUpdate:2-2',
    
  1510.       'render:2',
    
  1511.       // no componentDidUpdate
    
  1512.     ]);
    
  1513. 
    
  1514.     // Next we will update LifeCycle directly but not with new props.
    
  1515.     instances[1].tick();
    
  1516. 
    
  1517.     await waitForAll([
    
  1518.       // This should not trigger another componentWillReceiveProps because
    
  1519.       // we never got new props.
    
  1520.       'shouldComponentUpdate:2-3',
    
  1521.       'componentWillUpdate:2-3',
    
  1522.       'render:3',
    
  1523.       'componentDidUpdate:3-2',
    
  1524.     ]);
    
  1525. 
    
  1526.     // TODO: Test that we get the expected values for the same scenario with
    
  1527.     // incomplete parents.
    
  1528.   });
    
  1529. 
    
  1530.   xit('skips will/DidUpdate when bailing unless an update was already in progress', async () => {
    
  1531.     class LifeCycle extends React.Component {
    
  1532.       UNSAFE_componentWillMount() {
    
  1533.         Scheduler.log('componentWillMount');
    
  1534.       }
    
  1535.       componentDidMount() {
    
  1536.         Scheduler.log('componentDidMount');
    
  1537.       }
    
  1538.       UNSAFE_componentWillReceiveProps(nextProps) {
    
  1539.         Scheduler.log('componentWillReceiveProps');
    
  1540.       }
    
  1541.       shouldComponentUpdate(nextProps) {
    
  1542.         Scheduler.log('shouldComponentUpdate');
    
  1543.         // Bail
    
  1544.         return this.props.x !== nextProps.x;
    
  1545.       }
    
  1546.       UNSAFE_componentWillUpdate(nextProps) {
    
  1547.         Scheduler.log('componentWillUpdate');
    
  1548.       }
    
  1549.       componentDidUpdate(prevProps) {
    
  1550.         Scheduler.log('componentDidUpdate');
    
  1551.       }
    
  1552.       render() {
    
  1553.         Scheduler.log('render');
    
  1554.         return <span />;
    
  1555.       }
    
  1556.     }
    
  1557. 
    
  1558.     function Sibling() {
    
  1559.       Scheduler.log('render sibling');
    
  1560.       return <span />;
    
  1561.     }
    
  1562. 
    
  1563.     function App(props) {
    
  1564.       return [<LifeCycle key="a" x={props.x} />, <Sibling key="b" />];
    
  1565.     }
    
  1566. 
    
  1567.     ReactNoop.render(<App x={0} />);
    
  1568.     await waitForAll([
    
  1569.       'componentWillMount',
    
  1570.       'render',
    
  1571.       'render sibling',
    
  1572.       'componentDidMount',
    
  1573.     ]);
    
  1574. 
    
  1575.     // Update to same props
    
  1576.     ReactNoop.render(<App x={0} />);
    
  1577.     await waitForAll([
    
  1578.       'componentWillReceiveProps',
    
  1579.       'shouldComponentUpdate',
    
  1580.       // no componentWillUpdate
    
  1581.       // no render
    
  1582.       'render sibling',
    
  1583.       // no componentDidUpdate
    
  1584.     ]);
    
  1585. 
    
  1586.     // Begin updating to new props...
    
  1587.     ReactNoop.render(<App x={1} />);
    
  1588.     ReactNoop.flushDeferredPri(30);
    
  1589. 
    
  1590.     assertLog([
    
  1591.       'componentWillReceiveProps',
    
  1592.       'shouldComponentUpdate',
    
  1593.       'componentWillUpdate',
    
  1594.       'render',
    
  1595.       'render sibling',
    
  1596.       // no componentDidUpdate yet
    
  1597.     ]);
    
  1598. 
    
  1599.     // ...but we'll interrupt it to rerender the same props.
    
  1600.     ReactNoop.render(<App x={1} />);
    
  1601.     await waitForAll([]);
    
  1602. 
    
  1603.     // We can bail out this time, but we must call componentDidUpdate.
    
  1604.     assertLog([
    
  1605.       'componentWillReceiveProps',
    
  1606.       'shouldComponentUpdate',
    
  1607.       // no componentWillUpdate
    
  1608.       // no render
    
  1609.       'render sibling',
    
  1610.       'componentDidUpdate',
    
  1611.     ]);
    
  1612.   });
    
  1613. 
    
  1614.   it('can nest batchedUpdates', async () => {
    
  1615.     let instance;
    
  1616. 
    
  1617.     class Foo extends React.Component {
    
  1618.       state = {n: 0};
    
  1619.       render() {
    
  1620.         instance = this;
    
  1621.         return <div />;
    
  1622.       }
    
  1623.     }
    
  1624. 
    
  1625.     ReactNoop.render(<Foo />);
    
  1626.     await waitForAll([]);
    
  1627. 
    
  1628.     ReactNoop.flushSync(() => {
    
  1629.       ReactNoop.batchedUpdates(() => {
    
  1630.         instance.setState({n: 1}, () => Scheduler.log('setState 1'));
    
  1631.         instance.setState({n: 2}, () => Scheduler.log('setState 2'));
    
  1632.         ReactNoop.batchedUpdates(() => {
    
  1633.           instance.setState({n: 3}, () => Scheduler.log('setState 3'));
    
  1634.           instance.setState({n: 4}, () => Scheduler.log('setState 4'));
    
  1635.           Scheduler.log('end inner batchedUpdates');
    
  1636.         });
    
  1637.         Scheduler.log('end outer batchedUpdates');
    
  1638.       });
    
  1639.     });
    
  1640. 
    
  1641.     // ReactNoop.flush() not needed because updates are synchronous
    
  1642. 
    
  1643.     assertLog([
    
  1644.       'end inner batchedUpdates',
    
  1645.       'end outer batchedUpdates',
    
  1646.       'setState 1',
    
  1647.       'setState 2',
    
  1648.       'setState 3',
    
  1649.       'setState 4',
    
  1650.     ]);
    
  1651.     expect(instance.state.n).toEqual(4);
    
  1652.   });
    
  1653. 
    
  1654.   it('can handle if setState callback throws', async () => {
    
  1655.     let instance;
    
  1656. 
    
  1657.     class Foo extends React.Component {
    
  1658.       state = {n: 0};
    
  1659.       render() {
    
  1660.         instance = this;
    
  1661.         return <div />;
    
  1662.       }
    
  1663.     }
    
  1664. 
    
  1665.     ReactNoop.render(<Foo />);
    
  1666.     await waitForAll([]);
    
  1667. 
    
  1668.     function updater({n}) {
    
  1669.       return {n: n + 1};
    
  1670.     }
    
  1671. 
    
  1672.     instance.setState(updater, () => Scheduler.log('first callback'));
    
  1673.     instance.setState(updater, () => {
    
  1674.       Scheduler.log('second callback');
    
  1675.       throw new Error('callback error');
    
  1676.     });
    
  1677.     instance.setState(updater, () => Scheduler.log('third callback'));
    
  1678. 
    
  1679.     await waitForThrow('callback error');
    
  1680. 
    
  1681.     // The third callback isn't called because the second one throws
    
  1682.     assertLog(['first callback', 'second callback']);
    
  1683.     expect(instance.state.n).toEqual(3);
    
  1684.   });
    
  1685. 
    
  1686.   // @gate !disableLegacyContext
    
  1687.   it('merges and masks context', async () => {
    
  1688.     class Intl extends React.Component {
    
  1689.       static childContextTypes = {
    
  1690.         locale: PropTypes.string,
    
  1691.       };
    
  1692.       getChildContext() {
    
  1693.         return {
    
  1694.           locale: this.props.locale,
    
  1695.         };
    
  1696.       }
    
  1697.       render() {
    
  1698.         Scheduler.log('Intl ' + JSON.stringify(this.context));
    
  1699.         return this.props.children;
    
  1700.       }
    
  1701.     }
    
  1702. 
    
  1703.     class Router extends React.Component {
    
  1704.       static childContextTypes = {
    
  1705.         route: PropTypes.string,
    
  1706.       };
    
  1707.       getChildContext() {
    
  1708.         return {
    
  1709.           route: this.props.route,
    
  1710.         };
    
  1711.       }
    
  1712.       render() {
    
  1713.         Scheduler.log('Router ' + JSON.stringify(this.context));
    
  1714.         return this.props.children;
    
  1715.       }
    
  1716.     }
    
  1717. 
    
  1718.     class ShowLocale extends React.Component {
    
  1719.       static contextTypes = {
    
  1720.         locale: PropTypes.string,
    
  1721.       };
    
  1722.       render() {
    
  1723.         Scheduler.log('ShowLocale ' + JSON.stringify(this.context));
    
  1724.         return this.context.locale;
    
  1725.       }
    
  1726.     }
    
  1727. 
    
  1728.     class ShowRoute extends React.Component {
    
  1729.       static contextTypes = {
    
  1730.         route: PropTypes.string,
    
  1731.       };
    
  1732.       render() {
    
  1733.         Scheduler.log('ShowRoute ' + JSON.stringify(this.context));
    
  1734.         return this.context.route;
    
  1735.       }
    
  1736.     }
    
  1737. 
    
  1738.     function ShowBoth(props, context) {
    
  1739.       Scheduler.log('ShowBoth ' + JSON.stringify(context));
    
  1740.       return `${context.route} in ${context.locale}`;
    
  1741.     }
    
  1742.     ShowBoth.contextTypes = {
    
  1743.       locale: PropTypes.string,
    
  1744.       route: PropTypes.string,
    
  1745.     };
    
  1746. 
    
  1747.     class ShowNeither extends React.Component {
    
  1748.       render() {
    
  1749.         Scheduler.log('ShowNeither ' + JSON.stringify(this.context));
    
  1750.         return null;
    
  1751.       }
    
  1752.     }
    
  1753. 
    
  1754.     class Indirection extends React.Component {
    
  1755.       render() {
    
  1756.         Scheduler.log('Indirection ' + JSON.stringify(this.context));
    
  1757.         return [
    
  1758.           <ShowLocale key="a" />,
    
  1759.           <ShowRoute key="b" />,
    
  1760.           <ShowNeither key="c" />,
    
  1761.           <Intl key="d" locale="ru">
    
  1762.             <ShowBoth />
    
  1763.           </Intl>,
    
  1764.           <ShowBoth key="e" />,
    
  1765.         ];
    
  1766.       }
    
  1767.     }
    
  1768. 
    
  1769.     ReactNoop.render(
    
  1770.       <Intl locale="fr">
    
  1771.         <ShowLocale />
    
  1772.         <div>
    
  1773.           <ShowBoth />
    
  1774.         </div>
    
  1775.       </Intl>,
    
  1776.     );
    
  1777.     await waitForAll([
    
  1778.       'Intl {}',
    
  1779.       'ShowLocale {"locale":"fr"}',
    
  1780.       'ShowBoth {"locale":"fr"}',
    
  1781.     ]);
    
  1782. 
    
  1783.     ReactNoop.render(
    
  1784.       <Intl locale="de">
    
  1785.         <ShowLocale />
    
  1786.         <div>
    
  1787.           <ShowBoth />
    
  1788.         </div>
    
  1789.       </Intl>,
    
  1790.     );
    
  1791.     await waitForAll([
    
  1792.       'Intl {}',
    
  1793.       'ShowLocale {"locale":"de"}',
    
  1794.       'ShowBoth {"locale":"de"}',
    
  1795.     ]);
    
  1796.     React.startTransition(() => {
    
  1797.       ReactNoop.render(
    
  1798.         <Intl locale="sv">
    
  1799.           <ShowLocale />
    
  1800.           <div>
    
  1801.             <ShowBoth />
    
  1802.           </div>
    
  1803.         </Intl>,
    
  1804.       );
    
  1805.     });
    
  1806.     await waitFor(['Intl {}']);
    
  1807. 
    
  1808.     ReactNoop.render(
    
  1809.       <Intl locale="en">
    
  1810.         <ShowLocale />
    
  1811.         <Router route="/about">
    
  1812.           <Indirection />
    
  1813.         </Router>
    
  1814.         <ShowBoth />
    
  1815.       </Intl>,
    
  1816.     );
    
  1817.     await waitForAll([
    
  1818.       'ShowLocale {"locale":"sv"}',
    
  1819.       'ShowBoth {"locale":"sv"}',
    
  1820.       'Intl {}',
    
  1821.       'ShowLocale {"locale":"en"}',
    
  1822.       'Router {}',
    
  1823.       'Indirection {}',
    
  1824.       'ShowLocale {"locale":"en"}',
    
  1825.       'ShowRoute {"route":"/about"}',
    
  1826.       'ShowNeither {}',
    
  1827.       'Intl {}',
    
  1828.       'ShowBoth {"locale":"ru","route":"/about"}',
    
  1829.       'ShowBoth {"locale":"en","route":"/about"}',
    
  1830.       'ShowBoth {"locale":"en"}',
    
  1831.     ]);
    
  1832.   });
    
  1833. 
    
  1834.   // @gate !disableLegacyContext
    
  1835.   it('does not leak own context into context provider', async () => {
    
  1836.     if (gate(flags => flags.disableLegacyContext)) {
    
  1837.       throw new Error('This test infinite loops when context is disabled.');
    
  1838.     }
    
  1839.     class Recurse extends React.Component {
    
  1840.       static contextTypes = {
    
  1841.         n: PropTypes.number,
    
  1842.       };
    
  1843.       static childContextTypes = {
    
  1844.         n: PropTypes.number,
    
  1845.       };
    
  1846.       getChildContext() {
    
  1847.         return {n: (this.context.n || 3) - 1};
    
  1848.       }
    
  1849.       render() {
    
  1850.         Scheduler.log('Recurse ' + JSON.stringify(this.context));
    
  1851.         if (this.context.n === 0) {
    
  1852.           return null;
    
  1853.         }
    
  1854.         return <Recurse />;
    
  1855.       }
    
  1856.     }
    
  1857. 
    
  1858.     ReactNoop.render(<Recurse />);
    
  1859.     await waitForAll([
    
  1860.       'Recurse {}',
    
  1861.       'Recurse {"n":2}',
    
  1862.       'Recurse {"n":1}',
    
  1863.       'Recurse {"n":0}',
    
  1864.     ]);
    
  1865.   });
    
  1866. 
    
  1867.   // @gate !disableModulePatternComponents
    
  1868.   // @gate !disableLegacyContext
    
  1869.   it('does not leak own context into context provider (factory components)', async () => {
    
  1870.     function Recurse(props, context) {
    
  1871.       return {
    
  1872.         getChildContext() {
    
  1873.           return {n: (context.n || 3) - 1};
    
  1874.         },
    
  1875.         render() {
    
  1876.           Scheduler.log('Recurse ' + JSON.stringify(context));
    
  1877.           if (context.n === 0) {
    
  1878.             return null;
    
  1879.           }
    
  1880.           return <Recurse />;
    
  1881.         },
    
  1882.       };
    
  1883.     }
    
  1884.     Recurse.contextTypes = {
    
  1885.       n: PropTypes.number,
    
  1886.     };
    
  1887.     Recurse.childContextTypes = {
    
  1888.       n: PropTypes.number,
    
  1889.     };
    
  1890. 
    
  1891.     ReactNoop.render(<Recurse />);
    
  1892.     await expect(
    
  1893.       async () =>
    
  1894.         await waitForAll([
    
  1895.           'Recurse {}',
    
  1896.           'Recurse {"n":2}',
    
  1897.           'Recurse {"n":1}',
    
  1898.           'Recurse {"n":0}',
    
  1899.         ]),
    
  1900.     ).toErrorDev([
    
  1901.       'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
    
  1902.         'Change Recurse to a class that extends React.Component instead. ' +
    
  1903.         "If you can't use a class try assigning the prototype on the function as a workaround. " +
    
  1904.         '`Recurse.prototype = React.Component.prototype`. ' +
    
  1905.         "Don't use an arrow function since it cannot be called with `new` by React.",
    
  1906.     ]);
    
  1907.   });
    
  1908. 
    
  1909.   // @gate www
    
  1910.   // @gate !disableLegacyContext
    
  1911.   it('provides context when reusing work', async () => {
    
  1912.     class Intl extends React.Component {
    
  1913.       static childContextTypes = {
    
  1914.         locale: PropTypes.string,
    
  1915.       };
    
  1916.       getChildContext() {
    
  1917.         return {
    
  1918.           locale: this.props.locale,
    
  1919.         };
    
  1920.       }
    
  1921.       render() {
    
  1922.         Scheduler.log('Intl ' + JSON.stringify(this.context));
    
  1923.         return this.props.children;
    
  1924.       }
    
  1925.     }
    
  1926. 
    
  1927.     class ShowLocale extends React.Component {
    
  1928.       static contextTypes = {
    
  1929.         locale: PropTypes.string,
    
  1930.       };
    
  1931.       render() {
    
  1932.         Scheduler.log('ShowLocale ' + JSON.stringify(this.context));
    
  1933.         return this.context.locale;
    
  1934.       }
    
  1935.     }
    
  1936. 
    
  1937.     React.startTransition(() => {
    
  1938.       ReactNoop.render(
    
  1939.         <Intl locale="fr">
    
  1940.           <ShowLocale />
    
  1941.           <LegacyHiddenDiv mode="hidden">
    
  1942.             <ShowLocale />
    
  1943.             <Intl locale="ru">
    
  1944.               <ShowLocale />
    
  1945.             </Intl>
    
  1946.           </LegacyHiddenDiv>
    
  1947.           <ShowLocale />
    
  1948.         </Intl>,
    
  1949.       );
    
  1950.     });
    
  1951. 
    
  1952.     await waitFor([
    
  1953.       'Intl {}',
    
  1954.       'ShowLocale {"locale":"fr"}',
    
  1955.       'ShowLocale {"locale":"fr"}',
    
  1956.     ]);
    
  1957. 
    
  1958.     await waitForAll([
    
  1959.       'ShowLocale {"locale":"fr"}',
    
  1960.       'Intl {}',
    
  1961.       'ShowLocale {"locale":"ru"}',
    
  1962.     ]);
    
  1963.   });
    
  1964. 
    
  1965.   // @gate !disableLegacyContext
    
  1966.   it('reads context when setState is below the provider', async () => {
    
  1967.     let statefulInst;
    
  1968. 
    
  1969.     class Intl extends React.Component {
    
  1970.       static childContextTypes = {
    
  1971.         locale: PropTypes.string,
    
  1972.       };
    
  1973.       getChildContext() {
    
  1974.         const childContext = {
    
  1975.           locale: this.props.locale,
    
  1976.         };
    
  1977.         Scheduler.log('Intl:provide ' + JSON.stringify(childContext));
    
  1978.         return childContext;
    
  1979.       }
    
  1980.       render() {
    
  1981.         Scheduler.log('Intl:read ' + JSON.stringify(this.context));
    
  1982.         return this.props.children;
    
  1983.       }
    
  1984.     }
    
  1985. 
    
  1986.     class ShowLocaleClass extends React.Component {
    
  1987.       static contextTypes = {
    
  1988.         locale: PropTypes.string,
    
  1989.       };
    
  1990.       render() {
    
  1991.         Scheduler.log('ShowLocaleClass:read ' + JSON.stringify(this.context));
    
  1992.         return this.context.locale;
    
  1993.       }
    
  1994.     }
    
  1995. 
    
  1996.     function ShowLocaleFn(props, context) {
    
  1997.       Scheduler.log('ShowLocaleFn:read ' + JSON.stringify(context));
    
  1998.       return context.locale;
    
  1999.     }
    
  2000.     ShowLocaleFn.contextTypes = {
    
  2001.       locale: PropTypes.string,
    
  2002.     };
    
  2003. 
    
  2004.     class Stateful extends React.Component {
    
  2005.       state = {x: 0};
    
  2006.       render() {
    
  2007.         statefulInst = this;
    
  2008.         return this.props.children;
    
  2009.       }
    
  2010.     }
    
  2011. 
    
  2012.     function IndirectionFn(props, context) {
    
  2013.       Scheduler.log('IndirectionFn ' + JSON.stringify(context));
    
  2014.       return props.children;
    
  2015.     }
    
  2016. 
    
  2017.     class IndirectionClass extends React.Component {
    
  2018.       render() {
    
  2019.         Scheduler.log('IndirectionClass ' + JSON.stringify(this.context));
    
  2020.         return this.props.children;
    
  2021.       }
    
  2022.     }
    
  2023. 
    
  2024.     ReactNoop.render(
    
  2025.       <Intl locale="fr">
    
  2026.         <IndirectionFn>
    
  2027.           <IndirectionClass>
    
  2028.             <Stateful>
    
  2029.               <ShowLocaleClass />
    
  2030.               <ShowLocaleFn />
    
  2031.             </Stateful>
    
  2032.           </IndirectionClass>
    
  2033.         </IndirectionFn>
    
  2034.       </Intl>,
    
  2035.     );
    
  2036.     await waitForAll([
    
  2037.       'Intl:read {}',
    
  2038.       'Intl:provide {"locale":"fr"}',
    
  2039.       'IndirectionFn {}',
    
  2040.       'IndirectionClass {}',
    
  2041.       'ShowLocaleClass:read {"locale":"fr"}',
    
  2042.       'ShowLocaleFn:read {"locale":"fr"}',
    
  2043.     ]);
    
  2044. 
    
  2045.     statefulInst.setState({x: 1});
    
  2046.     await waitForAll([]);
    
  2047.     // All work has been memoized because setState()
    
  2048.     // happened below the context and could not have affected it.
    
  2049.     assertLog([]);
    
  2050.   });
    
  2051. 
    
  2052.   // @gate !disableLegacyContext
    
  2053.   it('reads context when setState is above the provider', async () => {
    
  2054.     let statefulInst;
    
  2055. 
    
  2056.     class Intl extends React.Component {
    
  2057.       static childContextTypes = {
    
  2058.         locale: PropTypes.string,
    
  2059.       };
    
  2060.       getChildContext() {
    
  2061.         const childContext = {
    
  2062.           locale: this.props.locale,
    
  2063.         };
    
  2064.         Scheduler.log('Intl:provide ' + JSON.stringify(childContext));
    
  2065.         return childContext;
    
  2066.       }
    
  2067.       render() {
    
  2068.         Scheduler.log('Intl:read ' + JSON.stringify(this.context));
    
  2069.         return this.props.children;
    
  2070.       }
    
  2071.     }
    
  2072. 
    
  2073.     class ShowLocaleClass extends React.Component {
    
  2074.       static contextTypes = {
    
  2075.         locale: PropTypes.string,
    
  2076.       };
    
  2077.       render() {
    
  2078.         Scheduler.log('ShowLocaleClass:read ' + JSON.stringify(this.context));
    
  2079.         return this.context.locale;
    
  2080.       }
    
  2081.     }
    
  2082. 
    
  2083.     function ShowLocaleFn(props, context) {
    
  2084.       Scheduler.log('ShowLocaleFn:read ' + JSON.stringify(context));
    
  2085.       return context.locale;
    
  2086.     }
    
  2087.     ShowLocaleFn.contextTypes = {
    
  2088.       locale: PropTypes.string,
    
  2089.     };
    
  2090. 
    
  2091.     function IndirectionFn(props, context) {
    
  2092.       Scheduler.log('IndirectionFn ' + JSON.stringify(context));
    
  2093.       return props.children;
    
  2094.     }
    
  2095. 
    
  2096.     class IndirectionClass extends React.Component {
    
  2097.       render() {
    
  2098.         Scheduler.log('IndirectionClass ' + JSON.stringify(this.context));
    
  2099.         return this.props.children;
    
  2100.       }
    
  2101.     }
    
  2102. 
    
  2103.     class Stateful extends React.Component {
    
  2104.       state = {locale: 'fr'};
    
  2105.       render() {
    
  2106.         statefulInst = this;
    
  2107.         return <Intl locale={this.state.locale}>{this.props.children}</Intl>;
    
  2108.       }
    
  2109.     }
    
  2110. 
    
  2111.     ReactNoop.render(
    
  2112.       <Stateful>
    
  2113.         <IndirectionFn>
    
  2114.           <IndirectionClass>
    
  2115.             <ShowLocaleClass />
    
  2116.             <ShowLocaleFn />
    
  2117.           </IndirectionClass>
    
  2118.         </IndirectionFn>
    
  2119.       </Stateful>,
    
  2120.     );
    
  2121.     await waitForAll([
    
  2122.       'Intl:read {}',
    
  2123.       'Intl:provide {"locale":"fr"}',
    
  2124.       'IndirectionFn {}',
    
  2125.       'IndirectionClass {}',
    
  2126.       'ShowLocaleClass:read {"locale":"fr"}',
    
  2127.       'ShowLocaleFn:read {"locale":"fr"}',
    
  2128.     ]);
    
  2129. 
    
  2130.     statefulInst.setState({locale: 'gr'});
    
  2131.     await waitForAll([
    
  2132.       // Intl is below setState() so it might have been
    
  2133.       // affected by it. Therefore we re-render and recompute
    
  2134.       // its child context.
    
  2135.       'Intl:read {}',
    
  2136.       'Intl:provide {"locale":"gr"}',
    
  2137.       // TODO: it's unfortunate that we can't reuse work on
    
  2138.       // these components even though they don't depend on context.
    
  2139.       'IndirectionFn {}',
    
  2140.       'IndirectionClass {}',
    
  2141.       // These components depend on context:
    
  2142.       'ShowLocaleClass:read {"locale":"gr"}',
    
  2143.       'ShowLocaleFn:read {"locale":"gr"}',
    
  2144.     ]);
    
  2145.   });
    
  2146. 
    
  2147.   // @gate !disableLegacyContext || !__DEV__
    
  2148.   it('maintains the correct context when providers bail out due to low priority', async () => {
    
  2149.     class Root extends React.Component {
    
  2150.       render() {
    
  2151.         return <Middle {...this.props} />;
    
  2152.       }
    
  2153.     }
    
  2154. 
    
  2155.     let instance;
    
  2156. 
    
  2157.     class Middle extends React.Component {
    
  2158.       constructor(props, context) {
    
  2159.         super(props, context);
    
  2160.         instance = this;
    
  2161.       }
    
  2162.       shouldComponentUpdate() {
    
  2163.         // Return false so that our child will get a NoWork priority (and get bailed out)
    
  2164.         return false;
    
  2165.       }
    
  2166.       render() {
    
  2167.         return <Child />;
    
  2168.       }
    
  2169.     }
    
  2170. 
    
  2171.     // Child must be a context provider to trigger the bug
    
  2172.     class Child extends React.Component {
    
  2173.       static childContextTypes = {};
    
  2174.       getChildContext() {
    
  2175.         return {};
    
  2176.       }
    
  2177.       render() {
    
  2178.         return <div />;
    
  2179.       }
    
  2180.     }
    
  2181. 
    
  2182.     // Init
    
  2183.     ReactNoop.render(<Root />);
    
  2184.     await waitForAll([]);
    
  2185. 
    
  2186.     // Trigger an update in the middle of the tree
    
  2187.     instance.setState({});
    
  2188.     await waitForAll([]);
    
  2189.   });
    
  2190. 
    
  2191.   // @gate !disableLegacyContext || !__DEV__
    
  2192.   it('maintains the correct context when unwinding due to an error in render', async () => {
    
  2193.     class Root extends React.Component {
    
  2194.       componentDidCatch(error) {
    
  2195.         // If context is pushed/popped correctly,
    
  2196.         // This method will be used to handle the intentionally-thrown Error.
    
  2197.       }
    
  2198.       render() {
    
  2199.         return <ContextProvider depth={1} />;
    
  2200.       }
    
  2201.     }
    
  2202. 
    
  2203.     let instance;
    
  2204. 
    
  2205.     class ContextProvider extends React.Component {
    
  2206.       constructor(props, context) {
    
  2207.         super(props, context);
    
  2208.         this.state = {};
    
  2209.         if (props.depth === 1) {
    
  2210.           instance = this;
    
  2211.         }
    
  2212.       }
    
  2213.       static childContextTypes = {};
    
  2214.       getChildContext() {
    
  2215.         return {};
    
  2216.       }
    
  2217.       render() {
    
  2218.         if (this.state.throwError) {
    
  2219.           throw Error();
    
  2220.         }
    
  2221.         return this.props.depth < 4 ? (
    
  2222.           <ContextProvider depth={this.props.depth + 1} />
    
  2223.         ) : (
    
  2224.           <div />
    
  2225.         );
    
  2226.       }
    
  2227.     }
    
  2228. 
    
  2229.     // Init
    
  2230.     ReactNoop.render(<Root />);
    
  2231.     await waitForAll([]);
    
  2232. 
    
  2233.     // Trigger an update in the middle of the tree
    
  2234.     // This is necessary to reproduce the error as it currently exists.
    
  2235.     instance.setState({
    
  2236.       throwError: true,
    
  2237.     });
    
  2238.     await expect(async () => await waitForAll([])).toErrorDev(
    
  2239.       'Error boundaries should implement getDerivedStateFromError()',
    
  2240.     );
    
  2241.   });
    
  2242. 
    
  2243.   // @gate !disableLegacyContext || !__DEV__
    
  2244.   it('should not recreate masked context unless inputs have changed', async () => {
    
  2245.     let scuCounter = 0;
    
  2246. 
    
  2247.     class MyComponent extends React.Component {
    
  2248.       static contextTypes = {};
    
  2249.       componentDidMount(prevProps, prevState) {
    
  2250.         Scheduler.log('componentDidMount');
    
  2251.         this.setState({setStateInCDU: true});
    
  2252.       }
    
  2253.       componentDidUpdate(prevProps, prevState) {
    
  2254.         Scheduler.log('componentDidUpdate');
    
  2255.         if (this.state.setStateInCDU) {
    
  2256.           this.setState({setStateInCDU: false});
    
  2257.         }
    
  2258.       }
    
  2259.       UNSAFE_componentWillReceiveProps(nextProps) {
    
  2260.         Scheduler.log('componentWillReceiveProps');
    
  2261.         this.setState({setStateInCDU: true});
    
  2262.       }
    
  2263.       render() {
    
  2264.         Scheduler.log('render');
    
  2265.         return null;
    
  2266.       }
    
  2267.       shouldComponentUpdate(nextProps, nextState) {
    
  2268.         Scheduler.log('shouldComponentUpdate');
    
  2269.         return scuCounter++ < 5; // Don't let test hang
    
  2270.       }
    
  2271.     }
    
  2272. 
    
  2273.     ReactNoop.render(<MyComponent />);
    
  2274.     await waitForAll([
    
  2275.       'render',
    
  2276.       'componentDidMount',
    
  2277.       'shouldComponentUpdate',
    
  2278.       'render',
    
  2279.       'componentDidUpdate',
    
  2280.       'shouldComponentUpdate',
    
  2281.       'render',
    
  2282.       'componentDidUpdate',
    
  2283.     ]);
    
  2284.   });
    
  2285. 
    
  2286.   xit('should reuse memoized work if pointers are updated before calling lifecycles', async () => {
    
  2287.     const cduNextProps = [];
    
  2288.     const cduPrevProps = [];
    
  2289.     const scuNextProps = [];
    
  2290.     const scuPrevProps = [];
    
  2291.     let renderCounter = 0;
    
  2292. 
    
  2293.     function SecondChild(props) {
    
  2294.       return <span>{props.children}</span>;
    
  2295.     }
    
  2296. 
    
  2297.     class FirstChild extends React.Component {
    
  2298.       componentDidUpdate(prevProps, prevState) {
    
  2299.         cduNextProps.push(this.props);
    
  2300.         cduPrevProps.push(prevProps);
    
  2301.       }
    
  2302.       shouldComponentUpdate(nextProps, nextState) {
    
  2303.         scuNextProps.push(nextProps);
    
  2304.         scuPrevProps.push(this.props);
    
  2305.         return this.props.children !== nextProps.children;
    
  2306.       }
    
  2307.       render() {
    
  2308.         renderCounter++;
    
  2309.         return <span>{this.props.children}</span>;
    
  2310.       }
    
  2311.     }
    
  2312. 
    
  2313.     class Middle extends React.Component {
    
  2314.       render() {
    
  2315.         return (
    
  2316.           <div>
    
  2317.             <FirstChild>{this.props.children}</FirstChild>
    
  2318.             <SecondChild>{this.props.children}</SecondChild>
    
  2319.           </div>
    
  2320.         );
    
  2321.       }
    
  2322.     }
    
  2323. 
    
  2324.     function Root(props) {
    
  2325.       return (
    
  2326.         <div hidden={true}>
    
  2327.           <Middle {...props} />
    
  2328.         </div>
    
  2329.       );
    
  2330.     }
    
  2331. 
    
  2332.     // Initial render of the entire tree.
    
  2333.     // Renders: Root, Middle, FirstChild, SecondChild
    
  2334.     ReactNoop.render(<Root>A</Root>);
    
  2335.     await waitForAll([]);
    
  2336. 
    
  2337.     expect(renderCounter).toBe(1);
    
  2338. 
    
  2339.     // Schedule low priority work to update children.
    
  2340.     // Give it enough time to partially render.
    
  2341.     // Renders: Root, Middle, FirstChild
    
  2342.     ReactNoop.render(<Root>B</Root>);
    
  2343.     ReactNoop.flushDeferredPri(20 + 30 + 5);
    
  2344. 
    
  2345.     // At this point our FirstChild component has rendered a second time,
    
  2346.     // But since the render is not completed cDU should not be called yet.
    
  2347.     expect(renderCounter).toBe(2);
    
  2348.     expect(scuPrevProps).toEqual([{children: 'A'}]);
    
  2349.     expect(scuNextProps).toEqual([{children: 'B'}]);
    
  2350.     expect(cduPrevProps).toEqual([]);
    
  2351.     expect(cduNextProps).toEqual([]);
    
  2352. 
    
  2353.     // Next interrupt the partial render with higher priority work.
    
  2354.     // The in-progress child content will bailout.
    
  2355.     // Renders: Root, Middle, FirstChild, SecondChild
    
  2356.     ReactNoop.render(<Root>B</Root>);
    
  2357.     await waitForAll([]);
    
  2358. 
    
  2359.     // At this point the higher priority render has completed.
    
  2360.     // Since FirstChild props didn't change, sCU returned false.
    
  2361.     // The previous memoized copy should be used.
    
  2362.     expect(renderCounter).toBe(2);
    
  2363.     expect(scuPrevProps).toEqual([{children: 'A'}, {children: 'B'}]);
    
  2364.     expect(scuNextProps).toEqual([{children: 'B'}, {children: 'B'}]);
    
  2365.     expect(cduPrevProps).toEqual([{children: 'A'}]);
    
  2366.     expect(cduNextProps).toEqual([{children: 'B'}]);
    
  2367.   });
    
  2368. 
    
  2369.   // @gate !disableLegacyContext
    
  2370.   it('updates descendants with new context values', async () => {
    
  2371.     let instance;
    
  2372. 
    
  2373.     class TopContextProvider extends React.Component {
    
  2374.       static childContextTypes = {
    
  2375.         count: PropTypes.number,
    
  2376.       };
    
  2377.       constructor() {
    
  2378.         super();
    
  2379.         this.state = {count: 0};
    
  2380.         instance = this;
    
  2381.       }
    
  2382.       getChildContext = () => ({
    
  2383.         count: this.state.count,
    
  2384.       });
    
  2385.       render = () => this.props.children;
    
  2386.       updateCount = () =>
    
  2387.         this.setState(state => ({
    
  2388.           count: state.count + 1,
    
  2389.         }));
    
  2390.     }
    
  2391. 
    
  2392.     class Middle extends React.Component {
    
  2393.       render = () => this.props.children;
    
  2394.     }
    
  2395. 
    
  2396.     class Child extends React.Component {
    
  2397.       static contextTypes = {
    
  2398.         count: PropTypes.number,
    
  2399.       };
    
  2400.       render = () => {
    
  2401.         Scheduler.log(`count:${this.context.count}`);
    
  2402.         return null;
    
  2403.       };
    
  2404.     }
    
  2405. 
    
  2406.     ReactNoop.render(
    
  2407.       <TopContextProvider>
    
  2408.         <Middle>
    
  2409.           <Child />
    
  2410.         </Middle>
    
  2411.       </TopContextProvider>,
    
  2412.     );
    
  2413. 
    
  2414.     await waitForAll(['count:0']);
    
  2415.     instance.updateCount();
    
  2416.     await waitForAll(['count:1']);
    
  2417.   });
    
  2418. 
    
  2419.   // @gate !disableLegacyContext
    
  2420.   it('updates descendants with multiple context-providing ancestors with new context values', async () => {
    
  2421.     let instance;
    
  2422. 
    
  2423.     class TopContextProvider extends React.Component {
    
  2424.       static childContextTypes = {
    
  2425.         count: PropTypes.number,
    
  2426.       };
    
  2427.       constructor() {
    
  2428.         super();
    
  2429.         this.state = {count: 0};
    
  2430.         instance = this;
    
  2431.       }
    
  2432.       getChildContext = () => ({
    
  2433.         count: this.state.count,
    
  2434.       });
    
  2435.       render = () => this.props.children;
    
  2436.       updateCount = () =>
    
  2437.         this.setState(state => ({
    
  2438.           count: state.count + 1,
    
  2439.         }));
    
  2440.     }
    
  2441. 
    
  2442.     class MiddleContextProvider extends React.Component {
    
  2443.       static childContextTypes = {
    
  2444.         name: PropTypes.string,
    
  2445.       };
    
  2446.       getChildContext = () => ({
    
  2447.         name: 'brian',
    
  2448.       });
    
  2449.       render = () => this.props.children;
    
  2450.     }
    
  2451. 
    
  2452.     class Child extends React.Component {
    
  2453.       static contextTypes = {
    
  2454.         count: PropTypes.number,
    
  2455.       };
    
  2456.       render = () => {
    
  2457.         Scheduler.log(`count:${this.context.count}`);
    
  2458.         return null;
    
  2459.       };
    
  2460.     }
    
  2461. 
    
  2462.     ReactNoop.render(
    
  2463.       <TopContextProvider>
    
  2464.         <MiddleContextProvider>
    
  2465.           <Child />
    
  2466.         </MiddleContextProvider>
    
  2467.       </TopContextProvider>,
    
  2468.     );
    
  2469. 
    
  2470.     await waitForAll(['count:0']);
    
  2471.     instance.updateCount();
    
  2472.     await waitForAll(['count:1']);
    
  2473.   });
    
  2474. 
    
  2475.   // @gate !disableLegacyContext
    
  2476.   it('should not update descendants with new context values if shouldComponentUpdate returns false', async () => {
    
  2477.     let instance;
    
  2478. 
    
  2479.     class TopContextProvider extends React.Component {
    
  2480.       static childContextTypes = {
    
  2481.         count: PropTypes.number,
    
  2482.       };
    
  2483.       constructor() {
    
  2484.         super();
    
  2485.         this.state = {count: 0};
    
  2486.         instance = this;
    
  2487.       }
    
  2488.       getChildContext = () => ({
    
  2489.         count: this.state.count,
    
  2490.       });
    
  2491.       render = () => this.props.children;
    
  2492.       updateCount = () =>
    
  2493.         this.setState(state => ({
    
  2494.           count: state.count + 1,
    
  2495.         }));
    
  2496.     }
    
  2497. 
    
  2498.     class MiddleScu extends React.Component {
    
  2499.       shouldComponentUpdate() {
    
  2500.         return false;
    
  2501.       }
    
  2502.       render = () => this.props.children;
    
  2503.     }
    
  2504. 
    
  2505.     class MiddleContextProvider extends React.Component {
    
  2506.       static childContextTypes = {
    
  2507.         name: PropTypes.string,
    
  2508.       };
    
  2509.       getChildContext = () => ({
    
  2510.         name: 'brian',
    
  2511.       });
    
  2512.       render = () => this.props.children;
    
  2513.     }
    
  2514. 
    
  2515.     class Child extends React.Component {
    
  2516.       static contextTypes = {
    
  2517.         count: PropTypes.number,
    
  2518.       };
    
  2519.       render = () => {
    
  2520.         Scheduler.log(`count:${this.context.count}`);
    
  2521.         return null;
    
  2522.       };
    
  2523.     }
    
  2524. 
    
  2525.     ReactNoop.render(
    
  2526.       <TopContextProvider>
    
  2527.         <MiddleScu>
    
  2528.           <MiddleContextProvider>
    
  2529.             <Child />
    
  2530.           </MiddleContextProvider>
    
  2531.         </MiddleScu>
    
  2532.       </TopContextProvider>,
    
  2533.     );
    
  2534. 
    
  2535.     await waitForAll(['count:0']);
    
  2536.     instance.updateCount();
    
  2537.     await waitForAll([]);
    
  2538.   });
    
  2539. 
    
  2540.   // @gate !disableLegacyContext
    
  2541.   it('should update descendants with new context values if setState() is called in the middle of the tree', async () => {
    
  2542.     let middleInstance;
    
  2543.     let topInstance;
    
  2544. 
    
  2545.     class TopContextProvider extends React.Component {
    
  2546.       static childContextTypes = {
    
  2547.         count: PropTypes.number,
    
  2548.       };
    
  2549.       constructor() {
    
  2550.         super();
    
  2551.         this.state = {count: 0};
    
  2552.         topInstance = this;
    
  2553.       }
    
  2554.       getChildContext = () => ({
    
  2555.         count: this.state.count,
    
  2556.       });
    
  2557.       render = () => this.props.children;
    
  2558.       updateCount = () =>
    
  2559.         this.setState(state => ({
    
  2560.           count: state.count + 1,
    
  2561.         }));
    
  2562.     }
    
  2563. 
    
  2564.     class MiddleScu extends React.Component {
    
  2565.       shouldComponentUpdate() {
    
  2566.         return false;
    
  2567.       }
    
  2568.       render = () => this.props.children;
    
  2569.     }
    
  2570. 
    
  2571.     class MiddleContextProvider extends React.Component {
    
  2572.       static childContextTypes = {
    
  2573.         name: PropTypes.string,
    
  2574.       };
    
  2575.       constructor() {
    
  2576.         super();
    
  2577.         this.state = {name: 'brian'};
    
  2578.         middleInstance = this;
    
  2579.       }
    
  2580.       getChildContext = () => ({
    
  2581.         name: this.state.name,
    
  2582.       });
    
  2583.       updateName = name => {
    
  2584.         this.setState({name});
    
  2585.       };
    
  2586.       render = () => this.props.children;
    
  2587.     }
    
  2588. 
    
  2589.     class Child extends React.Component {
    
  2590.       static contextTypes = {
    
  2591.         count: PropTypes.number,
    
  2592.         name: PropTypes.string,
    
  2593.       };
    
  2594.       render = () => {
    
  2595.         Scheduler.log(`count:${this.context.count}, name:${this.context.name}`);
    
  2596.         return null;
    
  2597.       };
    
  2598.     }
    
  2599. 
    
  2600.     ReactNoop.render(
    
  2601.       <TopContextProvider>
    
  2602.         <MiddleScu>
    
  2603.           <MiddleContextProvider>
    
  2604.             <Child />
    
  2605.           </MiddleContextProvider>
    
  2606.         </MiddleScu>
    
  2607.       </TopContextProvider>,
    
  2608.     );
    
  2609. 
    
  2610.     await waitForAll(['count:0, name:brian']);
    
  2611.     topInstance.updateCount();
    
  2612.     await waitForAll([]);
    
  2613.     middleInstance.updateName('not brian');
    
  2614.     await waitForAll(['count:1, name:not brian']);
    
  2615.   });
    
  2616. 
    
  2617.   it('does not interrupt for update at same priority', async () => {
    
  2618.     function Parent(props) {
    
  2619.       Scheduler.log('Parent: ' + props.step);
    
  2620.       return <Child step={props.step} />;
    
  2621.     }
    
  2622. 
    
  2623.     function Child(props) {
    
  2624.       Scheduler.log('Child: ' + props.step);
    
  2625.       return null;
    
  2626.     }
    
  2627. 
    
  2628.     React.startTransition(() => {
    
  2629.       ReactNoop.render(<Parent step={1} />);
    
  2630.     });
    
  2631.     await waitFor(['Parent: 1']);
    
  2632. 
    
  2633.     // Interrupt at same priority
    
  2634.     ReactNoop.render(<Parent step={2} />);
    
  2635. 
    
  2636.     await waitForAll(['Child: 1', 'Parent: 2', 'Child: 2']);
    
  2637.   });
    
  2638. 
    
  2639.   it('does not interrupt for update at lower priority', async () => {
    
  2640.     function Parent(props) {
    
  2641.       Scheduler.log('Parent: ' + props.step);
    
  2642.       return <Child step={props.step} />;
    
  2643.     }
    
  2644. 
    
  2645.     function Child(props) {
    
  2646.       Scheduler.log('Child: ' + props.step);
    
  2647.       return null;
    
  2648.     }
    
  2649. 
    
  2650.     React.startTransition(() => {
    
  2651.       ReactNoop.render(<Parent step={1} />);
    
  2652.     });
    
  2653.     await waitFor(['Parent: 1']);
    
  2654. 
    
  2655.     // Interrupt at lower priority
    
  2656.     ReactNoop.expire(2000);
    
  2657.     ReactNoop.render(<Parent step={2} />);
    
  2658. 
    
  2659.     await waitForAll(['Child: 1', 'Parent: 2', 'Child: 2']);
    
  2660.   });
    
  2661. 
    
  2662.   it('does interrupt for update at higher priority', async () => {
    
  2663.     function Parent(props) {
    
  2664.       Scheduler.log('Parent: ' + props.step);
    
  2665.       return <Child step={props.step} />;
    
  2666.     }
    
  2667. 
    
  2668.     function Child(props) {
    
  2669.       Scheduler.log('Child: ' + props.step);
    
  2670.       return null;
    
  2671.     }
    
  2672. 
    
  2673.     React.startTransition(() => {
    
  2674.       ReactNoop.render(<Parent step={1} />);
    
  2675.     });
    
  2676.     await waitFor(['Parent: 1']);
    
  2677. 
    
  2678.     // Interrupt at higher priority
    
  2679.     ReactNoop.flushSync(() => ReactNoop.render(<Parent step={2} />));
    
  2680.     assertLog(['Parent: 2', 'Child: 2']);
    
  2681. 
    
  2682.     await waitForAll([]);
    
  2683.   });
    
  2684. 
    
  2685.   // We sometimes use Maps with Fibers as keys.
    
  2686.   // @gate !disableLegacyContext || !__DEV__
    
  2687.   it('does not break with a bad Map polyfill', async () => {
    
  2688.     const realMapSet = Map.prototype.set;
    
  2689. 
    
  2690.     async function triggerCodePathThatUsesFibersAsMapKeys() {
    
  2691.       function Thing() {
    
  2692.         throw new Error('No.');
    
  2693.       }
    
  2694.       // This class uses legacy context, which triggers warnings,
    
  2695.       // the procedures for which use a Map to store fibers.
    
  2696.       class Boundary extends React.Component {
    
  2697.         state = {didError: false};
    
  2698.         componentDidCatch() {
    
  2699.           this.setState({didError: true});
    
  2700.         }
    
  2701.         static contextTypes = {
    
  2702.           color: () => null,
    
  2703.         };
    
  2704.         render() {
    
  2705.           return this.state.didError ? null : <Thing />;
    
  2706.         }
    
  2707.       }
    
  2708.       ReactNoop.render(
    
  2709.         <React.StrictMode>
    
  2710.           <Boundary />
    
  2711.         </React.StrictMode>,
    
  2712.       );
    
  2713.       await expect(async () => {
    
  2714.         await waitForAll([]);
    
  2715.       }).toErrorDev([
    
  2716.         'Legacy context API has been detected within a strict-mode tree',
    
  2717.       ]);
    
  2718.     }
    
  2719. 
    
  2720.     // First, verify that this code path normally receives Fibers as keys,
    
  2721.     // and that they're not extensible.
    
  2722.     jest.resetModules();
    
  2723.     let receivedNonExtensibleObjects;
    
  2724.     // eslint-disable-next-line no-extend-native
    
  2725.     Map.prototype.set = function (key) {
    
  2726.       if (typeof key === 'object' && key !== null) {
    
  2727.         if (!Object.isExtensible(key)) {
    
  2728.           receivedNonExtensibleObjects = true;
    
  2729.         }
    
  2730.       }
    
  2731.       return realMapSet.apply(this, arguments);
    
  2732.     };
    
  2733.     React = require('react');
    
  2734.     ReactNoop = require('react-noop-renderer');
    
  2735.     Scheduler = require('scheduler');
    
  2736.     let InternalTestUtils = require('internal-test-utils');
    
  2737.     waitForAll = InternalTestUtils.waitForAll;
    
  2738.     waitFor = InternalTestUtils.waitFor;
    
  2739.     waitForThrow = InternalTestUtils.waitForThrow;
    
  2740.     assertLog = InternalTestUtils.assertLog;
    
  2741. 
    
  2742.     try {
    
  2743.       receivedNonExtensibleObjects = false;
    
  2744.       await triggerCodePathThatUsesFibersAsMapKeys();
    
  2745.     } finally {
    
  2746.       // eslint-disable-next-line no-extend-native
    
  2747.       Map.prototype.set = realMapSet;
    
  2748.     }
    
  2749.     // If this fails, find another code path in Fiber
    
  2750.     // that passes Fibers as keys to Maps.
    
  2751.     // Note that we only expect them to be non-extensible
    
  2752.     // in development.
    
  2753.     expect(receivedNonExtensibleObjects).toBe(__DEV__);
    
  2754. 
    
  2755.     // Next, verify that a Map polyfill that "writes" to keys
    
  2756.     // doesn't cause a failure.
    
  2757.     jest.resetModules();
    
  2758.     // eslint-disable-next-line no-extend-native
    
  2759.     Map.prototype.set = function (key, value) {
    
  2760.       if (typeof key === 'object' && key !== null) {
    
  2761.         // A polyfill could do something like this.
    
  2762.         // It would throw if an object is not extensible.
    
  2763.         key.__internalValueSlot = value;
    
  2764.       }
    
  2765.       return realMapSet.apply(this, arguments);
    
  2766.     };
    
  2767.     React = require('react');
    
  2768.     ReactNoop = require('react-noop-renderer');
    
  2769.     Scheduler = require('scheduler');
    
  2770.     InternalTestUtils = require('internal-test-utils');
    
  2771.     waitForAll = InternalTestUtils.waitForAll;
    
  2772.     waitFor = InternalTestUtils.waitFor;
    
  2773.     waitForThrow = InternalTestUtils.waitForThrow;
    
  2774.     assertLog = InternalTestUtils.assertLog;
    
  2775. 
    
  2776.     try {
    
  2777.       await triggerCodePathThatUsesFibersAsMapKeys();
    
  2778.     } finally {
    
  2779.       // eslint-disable-next-line no-extend-native
    
  2780.       Map.prototype.set = realMapSet;
    
  2781.     }
    
  2782.     // If we got this far, our feature detection worked.
    
  2783.     // We knew that Map#set() throws for non-extensible objects,
    
  2784.     // so we didn't set them as non-extensible for that reason.
    
  2785.   });
    
  2786. });