1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. let React;
    
  13. let ReactDOM;
    
  14. let ReactDOMClient;
    
  15. let ReactTestUtils;
    
  16. let act;
    
  17. let Scheduler;
    
  18. let waitForAll;
    
  19. let waitFor;
    
  20. let assertLog;
    
  21. 
    
  22. describe('ReactUpdates', () => {
    
  23.   beforeEach(() => {
    
  24.     jest.resetModules();
    
  25.     React = require('react');
    
  26.     ReactDOM = require('react-dom');
    
  27.     ReactDOMClient = require('react-dom/client');
    
  28.     ReactTestUtils = require('react-dom/test-utils');
    
  29.     act = require('internal-test-utils').act;
    
  30.     Scheduler = require('scheduler');
    
  31. 
    
  32.     const InternalTestUtils = require('internal-test-utils');
    
  33.     waitForAll = InternalTestUtils.waitForAll;
    
  34.     waitFor = InternalTestUtils.waitFor;
    
  35.     assertLog = InternalTestUtils.assertLog;
    
  36.   });
    
  37. 
    
  38.   // Note: This is based on a similar component we use in www. We can delete
    
  39.   // once the extra div wrapper is no longer necessary.
    
  40.   function LegacyHiddenDiv({children, mode}) {
    
  41.     return (
    
  42.       <div hidden={mode === 'hidden'}>
    
  43.         <React.unstable_LegacyHidden
    
  44.           mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
    
  45.           {children}
    
  46.         </React.unstable_LegacyHidden>
    
  47.       </div>
    
  48.     );
    
  49.   }
    
  50. 
    
  51.   it('should batch state when updating state twice', () => {
    
  52.     let updateCount = 0;
    
  53. 
    
  54.     class Component extends React.Component {
    
  55.       state = {x: 0};
    
  56. 
    
  57.       componentDidUpdate() {
    
  58.         updateCount++;
    
  59.       }
    
  60. 
    
  61.       render() {
    
  62.         return <div>{this.state.x}</div>;
    
  63.       }
    
  64.     }
    
  65. 
    
  66.     const instance = ReactTestUtils.renderIntoDocument(<Component />);
    
  67.     expect(instance.state.x).toBe(0);
    
  68. 
    
  69.     ReactDOM.unstable_batchedUpdates(function () {
    
  70.       instance.setState({x: 1});
    
  71.       instance.setState({x: 2});
    
  72.       expect(instance.state.x).toBe(0);
    
  73.       expect(updateCount).toBe(0);
    
  74.     });
    
  75. 
    
  76.     expect(instance.state.x).toBe(2);
    
  77.     expect(updateCount).toBe(1);
    
  78.   });
    
  79. 
    
  80.   it('should batch state when updating two different state keys', () => {
    
  81.     let updateCount = 0;
    
  82. 
    
  83.     class Component extends React.Component {
    
  84.       state = {x: 0, y: 0};
    
  85. 
    
  86.       componentDidUpdate() {
    
  87.         updateCount++;
    
  88.       }
    
  89. 
    
  90.       render() {
    
  91.         return (
    
  92.           <div>
    
  93.             ({this.state.x}, {this.state.y})
    
  94.           </div>
    
  95.         );
    
  96.       }
    
  97.     }
    
  98. 
    
  99.     const instance = ReactTestUtils.renderIntoDocument(<Component />);
    
  100.     expect(instance.state.x).toBe(0);
    
  101.     expect(instance.state.y).toBe(0);
    
  102. 
    
  103.     ReactDOM.unstable_batchedUpdates(function () {
    
  104.       instance.setState({x: 1});
    
  105.       instance.setState({y: 2});
    
  106.       expect(instance.state.x).toBe(0);
    
  107.       expect(instance.state.y).toBe(0);
    
  108.       expect(updateCount).toBe(0);
    
  109.     });
    
  110. 
    
  111.     expect(instance.state.x).toBe(1);
    
  112.     expect(instance.state.y).toBe(2);
    
  113.     expect(updateCount).toBe(1);
    
  114.   });
    
  115. 
    
  116.   it('should batch state and props together', () => {
    
  117.     let updateCount = 0;
    
  118. 
    
  119.     class Component extends React.Component {
    
  120.       state = {y: 0};
    
  121. 
    
  122.       componentDidUpdate() {
    
  123.         updateCount++;
    
  124.       }
    
  125. 
    
  126.       render() {
    
  127.         return (
    
  128.           <div>
    
  129.             ({this.props.x}, {this.state.y})
    
  130.           </div>
    
  131.         );
    
  132.       }
    
  133.     }
    
  134. 
    
  135.     const container = document.createElement('div');
    
  136.     const instance = ReactDOM.render(<Component x={0} />, container);
    
  137.     expect(instance.props.x).toBe(0);
    
  138.     expect(instance.state.y).toBe(0);
    
  139. 
    
  140.     ReactDOM.unstable_batchedUpdates(function () {
    
  141.       ReactDOM.render(<Component x={1} />, container);
    
  142.       instance.setState({y: 2});
    
  143.       expect(instance.props.x).toBe(0);
    
  144.       expect(instance.state.y).toBe(0);
    
  145.       expect(updateCount).toBe(0);
    
  146.     });
    
  147. 
    
  148.     expect(instance.props.x).toBe(1);
    
  149.     expect(instance.state.y).toBe(2);
    
  150.     expect(updateCount).toBe(1);
    
  151.   });
    
  152. 
    
  153.   it('should batch parent/child state updates together', () => {
    
  154.     let parentUpdateCount = 0;
    
  155. 
    
  156.     class Parent extends React.Component {
    
  157.       state = {x: 0};
    
  158.       childRef = React.createRef();
    
  159. 
    
  160.       componentDidUpdate() {
    
  161.         parentUpdateCount++;
    
  162.       }
    
  163. 
    
  164.       render() {
    
  165.         return (
    
  166.           <div>
    
  167.             <Child ref={this.childRef} x={this.state.x} />
    
  168.           </div>
    
  169.         );
    
  170.       }
    
  171.     }
    
  172. 
    
  173.     let childUpdateCount = 0;
    
  174. 
    
  175.     class Child extends React.Component {
    
  176.       state = {y: 0};
    
  177. 
    
  178.       componentDidUpdate() {
    
  179.         childUpdateCount++;
    
  180.       }
    
  181. 
    
  182.       render() {
    
  183.         return <div>{this.props.x + this.state.y}</div>;
    
  184.       }
    
  185.     }
    
  186. 
    
  187.     const instance = ReactTestUtils.renderIntoDocument(<Parent />);
    
  188.     const child = instance.childRef.current;
    
  189.     expect(instance.state.x).toBe(0);
    
  190.     expect(child.state.y).toBe(0);
    
  191. 
    
  192.     ReactDOM.unstable_batchedUpdates(function () {
    
  193.       instance.setState({x: 1});
    
  194.       child.setState({y: 2});
    
  195.       expect(instance.state.x).toBe(0);
    
  196.       expect(child.state.y).toBe(0);
    
  197.       expect(parentUpdateCount).toBe(0);
    
  198.       expect(childUpdateCount).toBe(0);
    
  199.     });
    
  200. 
    
  201.     expect(instance.state.x).toBe(1);
    
  202.     expect(child.state.y).toBe(2);
    
  203.     expect(parentUpdateCount).toBe(1);
    
  204.     expect(childUpdateCount).toBe(1);
    
  205.   });
    
  206. 
    
  207.   it('should batch child/parent state updates together', () => {
    
  208.     let parentUpdateCount = 0;
    
  209. 
    
  210.     class Parent extends React.Component {
    
  211.       state = {x: 0};
    
  212.       childRef = React.createRef();
    
  213. 
    
  214.       componentDidUpdate() {
    
  215.         parentUpdateCount++;
    
  216.       }
    
  217. 
    
  218.       render() {
    
  219.         return (
    
  220.           <div>
    
  221.             <Child ref={this.childRef} x={this.state.x} />
    
  222.           </div>
    
  223.         );
    
  224.       }
    
  225.     }
    
  226. 
    
  227.     let childUpdateCount = 0;
    
  228. 
    
  229.     class Child extends React.Component {
    
  230.       state = {y: 0};
    
  231. 
    
  232.       componentDidUpdate() {
    
  233.         childUpdateCount++;
    
  234.       }
    
  235. 
    
  236.       render() {
    
  237.         return <div>{this.props.x + this.state.y}</div>;
    
  238.       }
    
  239.     }
    
  240. 
    
  241.     const instance = ReactTestUtils.renderIntoDocument(<Parent />);
    
  242.     const child = instance.childRef.current;
    
  243.     expect(instance.state.x).toBe(0);
    
  244.     expect(child.state.y).toBe(0);
    
  245. 
    
  246.     ReactDOM.unstable_batchedUpdates(function () {
    
  247.       child.setState({y: 2});
    
  248.       instance.setState({x: 1});
    
  249.       expect(instance.state.x).toBe(0);
    
  250.       expect(child.state.y).toBe(0);
    
  251.       expect(parentUpdateCount).toBe(0);
    
  252.       expect(childUpdateCount).toBe(0);
    
  253.     });
    
  254. 
    
  255.     expect(instance.state.x).toBe(1);
    
  256.     expect(child.state.y).toBe(2);
    
  257.     expect(parentUpdateCount).toBe(1);
    
  258. 
    
  259.     // Batching reduces the number of updates here to 1.
    
  260.     expect(childUpdateCount).toBe(1);
    
  261.   });
    
  262. 
    
  263.   it('should support chained state updates', () => {
    
  264.     let updateCount = 0;
    
  265. 
    
  266.     class Component extends React.Component {
    
  267.       state = {x: 0};
    
  268. 
    
  269.       componentDidUpdate() {
    
  270.         updateCount++;
    
  271.       }
    
  272. 
    
  273.       render() {
    
  274.         return <div>{this.state.x}</div>;
    
  275.       }
    
  276.     }
    
  277. 
    
  278.     const instance = ReactTestUtils.renderIntoDocument(<Component />);
    
  279.     expect(instance.state.x).toBe(0);
    
  280. 
    
  281.     let innerCallbackRun = false;
    
  282.     ReactDOM.unstable_batchedUpdates(function () {
    
  283.       instance.setState({x: 1}, function () {
    
  284.         instance.setState({x: 2}, function () {
    
  285.           expect(this).toBe(instance);
    
  286.           innerCallbackRun = true;
    
  287.           expect(instance.state.x).toBe(2);
    
  288.           expect(updateCount).toBe(2);
    
  289.         });
    
  290.         expect(instance.state.x).toBe(1);
    
  291.         expect(updateCount).toBe(1);
    
  292.       });
    
  293.       expect(instance.state.x).toBe(0);
    
  294.       expect(updateCount).toBe(0);
    
  295.     });
    
  296. 
    
  297.     expect(innerCallbackRun).toBeTruthy();
    
  298.     expect(instance.state.x).toBe(2);
    
  299.     expect(updateCount).toBe(2);
    
  300.   });
    
  301. 
    
  302.   it('should batch forceUpdate together', () => {
    
  303.     let shouldUpdateCount = 0;
    
  304.     let updateCount = 0;
    
  305. 
    
  306.     class Component extends React.Component {
    
  307.       state = {x: 0};
    
  308. 
    
  309.       shouldComponentUpdate() {
    
  310.         shouldUpdateCount++;
    
  311.       }
    
  312. 
    
  313.       componentDidUpdate() {
    
  314.         updateCount++;
    
  315.       }
    
  316. 
    
  317.       render() {
    
  318.         return <div>{this.state.x}</div>;
    
  319.       }
    
  320.     }
    
  321. 
    
  322.     const instance = ReactTestUtils.renderIntoDocument(<Component />);
    
  323.     expect(instance.state.x).toBe(0);
    
  324. 
    
  325.     let callbacksRun = 0;
    
  326.     ReactDOM.unstable_batchedUpdates(function () {
    
  327.       instance.setState({x: 1}, function () {
    
  328.         callbacksRun++;
    
  329.       });
    
  330.       instance.forceUpdate(function () {
    
  331.         callbacksRun++;
    
  332.       });
    
  333.       expect(instance.state.x).toBe(0);
    
  334.       expect(updateCount).toBe(0);
    
  335.     });
    
  336. 
    
  337.     expect(callbacksRun).toBe(2);
    
  338.     // shouldComponentUpdate shouldn't be called since we're forcing
    
  339.     expect(shouldUpdateCount).toBe(0);
    
  340.     expect(instance.state.x).toBe(1);
    
  341.     expect(updateCount).toBe(1);
    
  342.   });
    
  343. 
    
  344.   it('should update children even if parent blocks updates', () => {
    
  345.     let parentRenderCount = 0;
    
  346.     let childRenderCount = 0;
    
  347. 
    
  348.     class Parent extends React.Component {
    
  349.       childRef = React.createRef();
    
  350. 
    
  351.       shouldComponentUpdate() {
    
  352.         return false;
    
  353.       }
    
  354. 
    
  355.       render() {
    
  356.         parentRenderCount++;
    
  357.         return <Child ref={this.childRef} />;
    
  358.       }
    
  359.     }
    
  360. 
    
  361.     class Child extends React.Component {
    
  362.       render() {
    
  363.         childRenderCount++;
    
  364.         return <div />;
    
  365.       }
    
  366.     }
    
  367. 
    
  368.     expect(parentRenderCount).toBe(0);
    
  369.     expect(childRenderCount).toBe(0);
    
  370. 
    
  371.     let instance = <Parent />;
    
  372.     instance = ReactTestUtils.renderIntoDocument(instance);
    
  373. 
    
  374.     expect(parentRenderCount).toBe(1);
    
  375.     expect(childRenderCount).toBe(1);
    
  376. 
    
  377.     ReactDOM.unstable_batchedUpdates(function () {
    
  378.       instance.setState({x: 1});
    
  379.     });
    
  380. 
    
  381.     expect(parentRenderCount).toBe(1);
    
  382.     expect(childRenderCount).toBe(1);
    
  383. 
    
  384.     ReactDOM.unstable_batchedUpdates(function () {
    
  385.       instance.childRef.current.setState({x: 1});
    
  386.     });
    
  387. 
    
  388.     expect(parentRenderCount).toBe(1);
    
  389.     expect(childRenderCount).toBe(2);
    
  390.   });
    
  391. 
    
  392.   it('should not reconcile children passed via props', () => {
    
  393.     let numMiddleRenders = 0;
    
  394.     let numBottomRenders = 0;
    
  395. 
    
  396.     class Top extends React.Component {
    
  397.       render() {
    
  398.         return (
    
  399.           <Middle>
    
  400.             <Bottom />
    
  401.           </Middle>
    
  402.         );
    
  403.       }
    
  404.     }
    
  405. 
    
  406.     class Middle extends React.Component {
    
  407.       componentDidMount() {
    
  408.         this.forceUpdate();
    
  409.       }
    
  410. 
    
  411.       render() {
    
  412.         numMiddleRenders++;
    
  413.         return React.Children.only(this.props.children);
    
  414.       }
    
  415.     }
    
  416. 
    
  417.     class Bottom extends React.Component {
    
  418.       render() {
    
  419.         numBottomRenders++;
    
  420.         return null;
    
  421.       }
    
  422.     }
    
  423. 
    
  424.     ReactTestUtils.renderIntoDocument(<Top />);
    
  425.     expect(numMiddleRenders).toBe(2);
    
  426.     expect(numBottomRenders).toBe(1);
    
  427.   });
    
  428. 
    
  429.   it('should flow updates correctly', () => {
    
  430.     let willUpdates = [];
    
  431.     let didUpdates = [];
    
  432. 
    
  433.     const UpdateLoggingMixin = {
    
  434.       UNSAFE_componentWillUpdate: function () {
    
  435.         willUpdates.push(this.constructor.displayName);
    
  436.       },
    
  437.       componentDidUpdate: function () {
    
  438.         didUpdates.push(this.constructor.displayName);
    
  439.       },
    
  440.     };
    
  441. 
    
  442.     class Box extends React.Component {
    
  443.       boxDivRef = React.createRef();
    
  444. 
    
  445.       render() {
    
  446.         return <div ref={this.boxDivRef}>{this.props.children}</div>;
    
  447.       }
    
  448.     }
    
  449.     Object.assign(Box.prototype, UpdateLoggingMixin);
    
  450. 
    
  451.     class Child extends React.Component {
    
  452.       spanRef = React.createRef();
    
  453. 
    
  454.       render() {
    
  455.         return <span ref={this.spanRef}>child</span>;
    
  456.       }
    
  457.     }
    
  458.     Object.assign(Child.prototype, UpdateLoggingMixin);
    
  459. 
    
  460.     class Switcher extends React.Component {
    
  461.       state = {tabKey: 'hello'};
    
  462.       boxRef = React.createRef();
    
  463.       switcherDivRef = React.createRef();
    
  464.       render() {
    
  465.         const child = this.props.children;
    
  466. 
    
  467.         return (
    
  468.           <Box ref={this.boxRef}>
    
  469.             <div
    
  470.               ref={this.switcherDivRef}
    
  471.               style={{
    
  472.                 display: this.state.tabKey === child.key ? '' : 'none',
    
  473.               }}>
    
  474.               {child}
    
  475.             </div>
    
  476.           </Box>
    
  477.         );
    
  478.       }
    
  479.     }
    
  480.     Object.assign(Switcher.prototype, UpdateLoggingMixin);
    
  481. 
    
  482.     class App extends React.Component {
    
  483.       switcherRef = React.createRef();
    
  484.       childRef = React.createRef();
    
  485. 
    
  486.       render() {
    
  487.         return (
    
  488.           <Switcher ref={this.switcherRef}>
    
  489.             <Child key="hello" ref={this.childRef} />
    
  490.           </Switcher>
    
  491.         );
    
  492.       }
    
  493.     }
    
  494.     Object.assign(App.prototype, UpdateLoggingMixin);
    
  495. 
    
  496.     let root = <App />;
    
  497.     root = ReactTestUtils.renderIntoDocument(root);
    
  498. 
    
  499.     function expectUpdates(desiredWillUpdates, desiredDidUpdates) {
    
  500.       let i;
    
  501.       for (i = 0; i < desiredWillUpdates; i++) {
    
  502.         expect(willUpdates).toContain(desiredWillUpdates[i]);
    
  503.       }
    
  504.       for (i = 0; i < desiredDidUpdates; i++) {
    
  505.         expect(didUpdates).toContain(desiredDidUpdates[i]);
    
  506.       }
    
  507.       willUpdates = [];
    
  508.       didUpdates = [];
    
  509.     }
    
  510. 
    
  511.     function triggerUpdate(c) {
    
  512.       c.setState({x: 1});
    
  513.     }
    
  514. 
    
  515.     function testUpdates(components, desiredWillUpdates, desiredDidUpdates) {
    
  516.       let i;
    
  517. 
    
  518.       ReactDOM.unstable_batchedUpdates(function () {
    
  519.         for (i = 0; i < components.length; i++) {
    
  520.           triggerUpdate(components[i]);
    
  521.         }
    
  522.       });
    
  523. 
    
  524.       expectUpdates(desiredWillUpdates, desiredDidUpdates);
    
  525. 
    
  526.       // Try them in reverse order
    
  527. 
    
  528.       ReactDOM.unstable_batchedUpdates(function () {
    
  529.         for (i = components.length - 1; i >= 0; i--) {
    
  530.           triggerUpdate(components[i]);
    
  531.         }
    
  532.       });
    
  533. 
    
  534.       expectUpdates(desiredWillUpdates, desiredDidUpdates);
    
  535.     }
    
  536.     testUpdates(
    
  537.       [root.switcherRef.current.boxRef.current, root.switcherRef.current],
    
  538.       // Owner-child relationships have inverse will and did
    
  539.       ['Switcher', 'Box'],
    
  540.       ['Box', 'Switcher'],
    
  541.     );
    
  542. 
    
  543.     testUpdates(
    
  544.       [root.childRef.current, root.switcherRef.current.boxRef.current],
    
  545.       // Not owner-child so reconcile independently
    
  546.       ['Box', 'Child'],
    
  547.       ['Box', 'Child'],
    
  548.     );
    
  549. 
    
  550.     testUpdates(
    
  551.       [root.childRef.current, root.switcherRef.current],
    
  552.       // Switcher owns Box and Child, Box does not own Child
    
  553.       ['Switcher', 'Box', 'Child'],
    
  554.       ['Box', 'Switcher', 'Child'],
    
  555.     );
    
  556.   });
    
  557. 
    
  558.   it('should queue mount-ready handlers across different roots', () => {
    
  559.     // We'll define two components A and B, then update both of them. When A's
    
  560.     // componentDidUpdate handlers is called, B's DOM should already have been
    
  561.     // updated.
    
  562. 
    
  563.     const bContainer = document.createElement('div');
    
  564. 
    
  565.     let b;
    
  566. 
    
  567.     let aUpdated = false;
    
  568. 
    
  569.     class A extends React.Component {
    
  570.       state = {x: 0};
    
  571. 
    
  572.       componentDidUpdate() {
    
  573.         expect(ReactDOM.findDOMNode(b).textContent).toBe('B1');
    
  574.         aUpdated = true;
    
  575.       }
    
  576. 
    
  577.       render() {
    
  578.         let portal = null;
    
  579.         // If we're using Fiber, we use Portals instead to achieve this.
    
  580.         portal = ReactDOM.createPortal(<B ref={n => (b = n)} />, bContainer);
    
  581.         return (
    
  582.           <div>
    
  583.             A{this.state.x}
    
  584.             {portal}
    
  585.           </div>
    
  586.         );
    
  587.       }
    
  588.     }
    
  589. 
    
  590.     class B extends React.Component {
    
  591.       state = {x: 0};
    
  592. 
    
  593.       render() {
    
  594.         return <div>B{this.state.x}</div>;
    
  595.       }
    
  596.     }
    
  597. 
    
  598.     const a = ReactTestUtils.renderIntoDocument(<A />);
    
  599.     ReactDOM.unstable_batchedUpdates(function () {
    
  600.       a.setState({x: 1});
    
  601.       b.setState({x: 1});
    
  602.     });
    
  603. 
    
  604.     expect(aUpdated).toBe(true);
    
  605.   });
    
  606. 
    
  607.   it('should flush updates in the correct order', () => {
    
  608.     const updates = [];
    
  609. 
    
  610.     class Outer extends React.Component {
    
  611.       state = {x: 0};
    
  612.       innerRef = React.createRef();
    
  613. 
    
  614.       render() {
    
  615.         updates.push('Outer-render-' + this.state.x);
    
  616.         return (
    
  617.           <div>
    
  618.             <Inner x={this.state.x} ref={this.innerRef} />
    
  619.           </div>
    
  620.         );
    
  621.       }
    
  622. 
    
  623.       componentDidUpdate() {
    
  624.         const x = this.state.x;
    
  625.         updates.push('Outer-didUpdate-' + x);
    
  626.         updates.push('Inner-setState-' + x);
    
  627.         this.innerRef.current.setState({x: x}, function () {
    
  628.           updates.push('Inner-callback-' + x);
    
  629.         });
    
  630.       }
    
  631.     }
    
  632. 
    
  633.     class Inner extends React.Component {
    
  634.       state = {x: 0};
    
  635. 
    
  636.       render() {
    
  637.         updates.push('Inner-render-' + this.props.x + '-' + this.state.x);
    
  638.         return <div />;
    
  639.       }
    
  640. 
    
  641.       componentDidUpdate() {
    
  642.         updates.push('Inner-didUpdate-' + this.props.x + '-' + this.state.x);
    
  643.       }
    
  644.     }
    
  645. 
    
  646.     const instance = ReactTestUtils.renderIntoDocument(<Outer />);
    
  647. 
    
  648.     updates.push('Outer-setState-1');
    
  649.     instance.setState({x: 1}, function () {
    
  650.       updates.push('Outer-callback-1');
    
  651.       updates.push('Outer-setState-2');
    
  652.       instance.setState({x: 2}, function () {
    
  653.         updates.push('Outer-callback-2');
    
  654.       });
    
  655.     });
    
  656. 
    
  657.     /* eslint-disable indent */
    
  658.     expect(updates).toEqual([
    
  659.       'Outer-render-0',
    
  660.       'Inner-render-0-0',
    
  661. 
    
  662.       'Outer-setState-1',
    
  663.       'Outer-render-1',
    
  664.       'Inner-render-1-0',
    
  665.       'Inner-didUpdate-1-0',
    
  666.       'Outer-didUpdate-1',
    
  667.       // Happens in a batch, so don't re-render yet
    
  668.       'Inner-setState-1',
    
  669.       'Outer-callback-1',
    
  670. 
    
  671.       // Happens in a batch
    
  672.       'Outer-setState-2',
    
  673. 
    
  674.       // Flush batched updates all at once
    
  675.       'Outer-render-2',
    
  676.       'Inner-render-2-1',
    
  677.       'Inner-didUpdate-2-1',
    
  678.       'Inner-callback-1',
    
  679.       'Outer-didUpdate-2',
    
  680.       'Inner-setState-2',
    
  681.       'Outer-callback-2',
    
  682.       'Inner-render-2-2',
    
  683.       'Inner-didUpdate-2-2',
    
  684.       'Inner-callback-2',
    
  685.     ]);
    
  686.     /* eslint-enable indent */
    
  687.   });
    
  688. 
    
  689.   it('should flush updates in the correct order across roots', () => {
    
  690.     const instances = [];
    
  691.     const updates = [];
    
  692. 
    
  693.     class MockComponent extends React.Component {
    
  694.       render() {
    
  695.         updates.push(this.props.depth);
    
  696.         return <div />;
    
  697.       }
    
  698. 
    
  699.       componentDidMount() {
    
  700.         instances.push(this);
    
  701.         if (this.props.depth < this.props.count) {
    
  702.           ReactDOM.render(
    
  703.             <MockComponent
    
  704.               depth={this.props.depth + 1}
    
  705.               count={this.props.count}
    
  706.             />,
    
  707.             ReactDOM.findDOMNode(this),
    
  708.           );
    
  709.         }
    
  710.       }
    
  711.     }
    
  712. 
    
  713.     ReactTestUtils.renderIntoDocument(<MockComponent depth={0} count={2} />);
    
  714. 
    
  715.     expect(updates).toEqual([0, 1, 2]);
    
  716. 
    
  717.     ReactDOM.unstable_batchedUpdates(function () {
    
  718.       // Simulate update on each component from top to bottom.
    
  719.       instances.forEach(function (instance) {
    
  720.         instance.forceUpdate();
    
  721.       });
    
  722.     });
    
  723. 
    
  724.     expect(updates).toEqual([0, 1, 2, 0, 1, 2]);
    
  725.   });
    
  726. 
    
  727.   it('should queue nested updates', () => {
    
  728.     // See https://github.com/facebook/react/issues/1147
    
  729. 
    
  730.     class X extends React.Component {
    
  731.       state = {s: 0};
    
  732. 
    
  733.       render() {
    
  734.         if (this.state.s === 0) {
    
  735.           return (
    
  736.             <div>
    
  737.               <span>0</span>
    
  738.             </div>
    
  739.           );
    
  740.         } else {
    
  741.           return <div>1</div>;
    
  742.         }
    
  743.       }
    
  744. 
    
  745.       go = () => {
    
  746.         this.setState({s: 1});
    
  747.         this.setState({s: 0});
    
  748.         this.setState({s: 1});
    
  749.       };
    
  750.     }
    
  751. 
    
  752.     class Y extends React.Component {
    
  753.       render() {
    
  754.         return (
    
  755.           <div>
    
  756.             <Z />
    
  757.           </div>
    
  758.         );
    
  759.       }
    
  760.     }
    
  761. 
    
  762.     class Z extends React.Component {
    
  763.       render() {
    
  764.         return <div />;
    
  765.       }
    
  766. 
    
  767.       UNSAFE_componentWillUpdate() {
    
  768.         x.go();
    
  769.       }
    
  770.     }
    
  771. 
    
  772.     const x = ReactTestUtils.renderIntoDocument(<X />);
    
  773.     const y = ReactTestUtils.renderIntoDocument(<Y />);
    
  774.     expect(ReactDOM.findDOMNode(x).textContent).toBe('0');
    
  775. 
    
  776.     y.forceUpdate();
    
  777.     expect(ReactDOM.findDOMNode(x).textContent).toBe('1');
    
  778.   });
    
  779. 
    
  780.   it('should queue updates from during mount', () => {
    
  781.     // See https://github.com/facebook/react/issues/1353
    
  782.     let a;
    
  783. 
    
  784.     class A extends React.Component {
    
  785.       state = {x: 0};
    
  786. 
    
  787.       UNSAFE_componentWillMount() {
    
  788.         a = this;
    
  789.       }
    
  790. 
    
  791.       render() {
    
  792.         return <div>A{this.state.x}</div>;
    
  793.       }
    
  794.     }
    
  795. 
    
  796.     class B extends React.Component {
    
  797.       UNSAFE_componentWillMount() {
    
  798.         a.setState({x: 1});
    
  799.       }
    
  800. 
    
  801.       render() {
    
  802.         return <div />;
    
  803.       }
    
  804.     }
    
  805. 
    
  806.     ReactDOM.unstable_batchedUpdates(function () {
    
  807.       ReactTestUtils.renderIntoDocument(
    
  808.         <div>
    
  809.           <A />
    
  810.           <B />
    
  811.         </div>,
    
  812.       );
    
  813.     });
    
  814. 
    
  815.     expect(a.state.x).toBe(1);
    
  816.     expect(ReactDOM.findDOMNode(a).textContent).toBe('A1');
    
  817.   });
    
  818. 
    
  819.   it('calls componentWillReceiveProps setState callback properly', () => {
    
  820.     let callbackCount = 0;
    
  821. 
    
  822.     class A extends React.Component {
    
  823.       state = {x: this.props.x};
    
  824. 
    
  825.       UNSAFE_componentWillReceiveProps(nextProps) {
    
  826.         const newX = nextProps.x;
    
  827.         this.setState({x: newX}, function () {
    
  828.           // State should have updated by the time this callback gets called
    
  829.           expect(this.state.x).toBe(newX);
    
  830.           callbackCount++;
    
  831.         });
    
  832.       }
    
  833. 
    
  834.       render() {
    
  835.         return <div>{this.state.x}</div>;
    
  836.       }
    
  837.     }
    
  838. 
    
  839.     const container = document.createElement('div');
    
  840.     ReactDOM.render(<A x={1} />, container);
    
  841.     ReactDOM.render(<A x={2} />, container);
    
  842.     expect(callbackCount).toBe(1);
    
  843.   });
    
  844. 
    
  845.   it('does not call render after a component as been deleted', () => {
    
  846.     let renderCount = 0;
    
  847.     let componentB = null;
    
  848. 
    
  849.     class B extends React.Component {
    
  850.       state = {updates: 0};
    
  851. 
    
  852.       componentDidMount() {
    
  853.         componentB = this;
    
  854.       }
    
  855. 
    
  856.       render() {
    
  857.         renderCount++;
    
  858.         return <div />;
    
  859.       }
    
  860.     }
    
  861. 
    
  862.     class A extends React.Component {
    
  863.       state = {showB: true};
    
  864. 
    
  865.       render() {
    
  866.         return this.state.showB ? <B /> : <div />;
    
  867.       }
    
  868.     }
    
  869. 
    
  870.     const component = ReactTestUtils.renderIntoDocument(<A />);
    
  871. 
    
  872.     ReactDOM.unstable_batchedUpdates(function () {
    
  873.       // B will have scheduled an update but the batching should ensure that its
    
  874.       // update never fires.
    
  875.       componentB.setState({updates: 1});
    
  876.       component.setState({showB: false});
    
  877.     });
    
  878. 
    
  879.     expect(renderCount).toBe(1);
    
  880.   });
    
  881. 
    
  882.   it('throws in setState if the update callback is not a function', () => {
    
  883.     function Foo() {
    
  884.       this.a = 1;
    
  885.       this.b = 2;
    
  886.     }
    
  887. 
    
  888.     class A extends React.Component {
    
  889.       state = {};
    
  890. 
    
  891.       render() {
    
  892.         return <div />;
    
  893.       }
    
  894.     }
    
  895. 
    
  896.     let component = ReactTestUtils.renderIntoDocument(<A />);
    
  897. 
    
  898.     expect(() => {
    
  899.       expect(() => component.setState({}, 'no')).toErrorDev(
    
  900.         'setState(...): Expected the last optional `callback` argument to be ' +
    
  901.           'a function. Instead received: no.',
    
  902.       );
    
  903.     }).toThrowError(
    
  904.       'Invalid argument passed as callback. Expected a function. Instead ' +
    
  905.         'received: no',
    
  906.     );
    
  907.     component = ReactTestUtils.renderIntoDocument(<A />);
    
  908.     expect(() => {
    
  909.       expect(() => component.setState({}, {foo: 'bar'})).toErrorDev(
    
  910.         'setState(...): Expected the last optional `callback` argument to be ' +
    
  911.           'a function. Instead received: [object Object].',
    
  912.       );
    
  913.     }).toThrowError(
    
  914.       'Invalid argument passed as callback. Expected a function. Instead ' +
    
  915.         'received: [object Object]',
    
  916.     );
    
  917.     // Make sure the warning is deduplicated and doesn't fire again
    
  918.     component = ReactTestUtils.renderIntoDocument(<A />);
    
  919.     expect(() => component.setState({}, new Foo())).toThrowError(
    
  920.       'Invalid argument passed as callback. Expected a function. Instead ' +
    
  921.         'received: [object Object]',
    
  922.     );
    
  923.   });
    
  924. 
    
  925.   it('throws in forceUpdate if the update callback is not a function', () => {
    
  926.     function Foo() {
    
  927.       this.a = 1;
    
  928.       this.b = 2;
    
  929.     }
    
  930. 
    
  931.     class A extends React.Component {
    
  932.       state = {};
    
  933. 
    
  934.       render() {
    
  935.         return <div />;
    
  936.       }
    
  937.     }
    
  938. 
    
  939.     let component = ReactTestUtils.renderIntoDocument(<A />);
    
  940. 
    
  941.     expect(() => {
    
  942.       expect(() => component.forceUpdate('no')).toErrorDev(
    
  943.         'forceUpdate(...): Expected the last optional `callback` argument to be ' +
    
  944.           'a function. Instead received: no.',
    
  945.       );
    
  946.     }).toThrowError(
    
  947.       'Invalid argument passed as callback. Expected a function. Instead ' +
    
  948.         'received: no',
    
  949.     );
    
  950.     component = ReactTestUtils.renderIntoDocument(<A />);
    
  951.     expect(() => {
    
  952.       expect(() => component.forceUpdate({foo: 'bar'})).toErrorDev(
    
  953.         'forceUpdate(...): Expected the last optional `callback` argument to be ' +
    
  954.           'a function. Instead received: [object Object].',
    
  955.       );
    
  956.     }).toThrowError(
    
  957.       'Invalid argument passed as callback. Expected a function. Instead ' +
    
  958.         'received: [object Object]',
    
  959.     );
    
  960.     // Make sure the warning is deduplicated and doesn't fire again
    
  961.     component = ReactTestUtils.renderIntoDocument(<A />);
    
  962.     expect(() => component.forceUpdate(new Foo())).toThrowError(
    
  963.       'Invalid argument passed as callback. Expected a function. Instead ' +
    
  964.         'received: [object Object]',
    
  965.     );
    
  966.   });
    
  967. 
    
  968.   it('does not update one component twice in a batch (#2410)', () => {
    
  969.     class Parent extends React.Component {
    
  970.       childRef = React.createRef();
    
  971. 
    
  972.       getChild = () => {
    
  973.         return this.childRef.current;
    
  974.       };
    
  975. 
    
  976.       render() {
    
  977.         return <Child ref={this.childRef} />;
    
  978.       }
    
  979.     }
    
  980. 
    
  981.     let renderCount = 0;
    
  982.     let postRenderCount = 0;
    
  983.     let once = false;
    
  984. 
    
  985.     class Child extends React.Component {
    
  986.       state = {updated: false};
    
  987. 
    
  988.       UNSAFE_componentWillUpdate() {
    
  989.         if (!once) {
    
  990.           once = true;
    
  991.           this.setState({updated: true});
    
  992.         }
    
  993.       }
    
  994. 
    
  995.       componentDidMount() {
    
  996.         expect(renderCount).toBe(postRenderCount + 1);
    
  997.         postRenderCount++;
    
  998.       }
    
  999. 
    
  1000.       componentDidUpdate() {
    
  1001.         expect(renderCount).toBe(postRenderCount + 1);
    
  1002.         postRenderCount++;
    
  1003.       }
    
  1004. 
    
  1005.       render() {
    
  1006.         expect(renderCount).toBe(postRenderCount);
    
  1007.         renderCount++;
    
  1008.         return <div />;
    
  1009.       }
    
  1010.     }
    
  1011. 
    
  1012.     const parent = ReactTestUtils.renderIntoDocument(<Parent />);
    
  1013.     const child = parent.getChild();
    
  1014.     ReactDOM.unstable_batchedUpdates(function () {
    
  1015.       parent.forceUpdate();
    
  1016.       child.forceUpdate();
    
  1017.     });
    
  1018.   });
    
  1019. 
    
  1020.   it('does not update one component twice in a batch (#6371)', () => {
    
  1021.     let callbacks = [];
    
  1022.     function emitChange() {
    
  1023.       callbacks.forEach(c => c());
    
  1024.     }
    
  1025. 
    
  1026.     class App extends React.Component {
    
  1027.       constructor(props) {
    
  1028.         super(props);
    
  1029.         this.state = {showChild: true};
    
  1030.       }
    
  1031.       componentDidMount() {
    
  1032.         this.setState({showChild: false});
    
  1033.       }
    
  1034.       render() {
    
  1035.         return (
    
  1036.           <div>
    
  1037.             <ForceUpdatesOnChange />
    
  1038.             {this.state.showChild && <EmitsChangeOnUnmount />}
    
  1039.           </div>
    
  1040.         );
    
  1041.       }
    
  1042.     }
    
  1043. 
    
  1044.     class EmitsChangeOnUnmount extends React.Component {
    
  1045.       componentWillUnmount() {
    
  1046.         emitChange();
    
  1047.       }
    
  1048.       render() {
    
  1049.         return null;
    
  1050.       }
    
  1051.     }
    
  1052. 
    
  1053.     class ForceUpdatesOnChange extends React.Component {
    
  1054.       componentDidMount() {
    
  1055.         this.onChange = () => this.forceUpdate();
    
  1056.         this.onChange();
    
  1057.         callbacks.push(this.onChange);
    
  1058.       }
    
  1059.       componentWillUnmount() {
    
  1060.         callbacks = callbacks.filter(c => c !== this.onChange);
    
  1061.       }
    
  1062.       render() {
    
  1063.         return <div key={Math.random()} onClick={function () {}} />;
    
  1064.       }
    
  1065.     }
    
  1066. 
    
  1067.     ReactDOM.render(<App />, document.createElement('div'));
    
  1068.   });
    
  1069. 
    
  1070.   it('unstable_batchedUpdates should return value from a callback', () => {
    
  1071.     const result = ReactDOM.unstable_batchedUpdates(function () {
    
  1072.       return 42;
    
  1073.     });
    
  1074.     expect(result).toEqual(42);
    
  1075.   });
    
  1076. 
    
  1077.   it('unmounts and remounts a root in the same batch', () => {
    
  1078.     const container = document.createElement('div');
    
  1079.     ReactDOM.render(<span>a</span>, container);
    
  1080.     ReactDOM.unstable_batchedUpdates(function () {
    
  1081.       ReactDOM.unmountComponentAtNode(container);
    
  1082.       ReactDOM.render(<span>b</span>, container);
    
  1083.     });
    
  1084.     expect(container.textContent).toBe('b');
    
  1085.   });
    
  1086. 
    
  1087.   it('handles reentrant mounting in synchronous mode', () => {
    
  1088.     let mounts = 0;
    
  1089.     class Editor extends React.Component {
    
  1090.       render() {
    
  1091.         return <div>{this.props.text}</div>;
    
  1092.       }
    
  1093.       componentDidMount() {
    
  1094.         mounts++;
    
  1095.         // This should be called only once but we guard just in case.
    
  1096.         if (!this.props.rendered) {
    
  1097.           this.props.onChange({rendered: true});
    
  1098.         }
    
  1099.       }
    
  1100.     }
    
  1101. 
    
  1102.     const container = document.createElement('div');
    
  1103.     function render() {
    
  1104.       ReactDOM.render(
    
  1105.         <Editor
    
  1106.           onChange={newProps => {
    
  1107.             props = {...props, ...newProps};
    
  1108.             render();
    
  1109.           }}
    
  1110.           {...props}
    
  1111.         />,
    
  1112.         container,
    
  1113.       );
    
  1114.     }
    
  1115. 
    
  1116.     let props = {text: 'hello', rendered: false};
    
  1117.     render();
    
  1118.     props = {...props, text: 'goodbye'};
    
  1119.     render();
    
  1120.     expect(container.textContent).toBe('goodbye');
    
  1121.     expect(mounts).toBe(1);
    
  1122.   });
    
  1123. 
    
  1124.   it('mounts and unmounts are sync even in a batch', () => {
    
  1125.     const ops = [];
    
  1126.     const container = document.createElement('div');
    
  1127.     ReactDOM.unstable_batchedUpdates(() => {
    
  1128.       ReactDOM.render(<div>Hello</div>, container);
    
  1129.       ops.push(container.textContent);
    
  1130.       ReactDOM.unmountComponentAtNode(container);
    
  1131.       ops.push(container.textContent);
    
  1132.     });
    
  1133.     expect(ops).toEqual(['Hello', '']);
    
  1134.   });
    
  1135. 
    
  1136.   it(
    
  1137.     'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
    
  1138.       'should both flush in the immediately subsequent commit',
    
  1139.     () => {
    
  1140.       const ops = [];
    
  1141.       class Foo extends React.Component {
    
  1142.         state = {a: false, b: false};
    
  1143.         UNSAFE_componentWillUpdate(_, nextState) {
    
  1144.           if (!nextState.a) {
    
  1145.             this.setState({a: true});
    
  1146.           }
    
  1147.         }
    
  1148.         componentDidUpdate() {
    
  1149.           ops.push('Foo updated');
    
  1150.           if (!this.state.b) {
    
  1151.             this.setState({b: true});
    
  1152.           }
    
  1153.         }
    
  1154.         render() {
    
  1155.           ops.push(`a: ${this.state.a}, b: ${this.state.b}`);
    
  1156.           return null;
    
  1157.         }
    
  1158.       }
    
  1159. 
    
  1160.       const container = document.createElement('div');
    
  1161.       // Mount
    
  1162.       ReactDOM.render(<Foo />, container);
    
  1163.       // Root update
    
  1164.       ReactDOM.render(<Foo />, container);
    
  1165.       expect(ops).toEqual([
    
  1166.         // Mount
    
  1167.         'a: false, b: false',
    
  1168.         // Root update
    
  1169.         'a: false, b: false',
    
  1170.         'Foo updated',
    
  1171.         // Subsequent update (both a and b should have flushed)
    
  1172.         'a: true, b: true',
    
  1173.         'Foo updated',
    
  1174.         // There should not be any additional updates
    
  1175.       ]);
    
  1176.     },
    
  1177.   );
    
  1178. 
    
  1179.   it(
    
  1180.     'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
    
  1181.       '(on a sibling) should both flush in the immediately subsequent commit',
    
  1182.     () => {
    
  1183.       const ops = [];
    
  1184.       class Foo extends React.Component {
    
  1185.         state = {a: false};
    
  1186.         UNSAFE_componentWillUpdate(_, nextState) {
    
  1187.           if (!nextState.a) {
    
  1188.             this.setState({a: true});
    
  1189.           }
    
  1190.         }
    
  1191.         componentDidUpdate() {
    
  1192.           ops.push('Foo updated');
    
  1193.         }
    
  1194.         render() {
    
  1195.           ops.push(`a: ${this.state.a}`);
    
  1196.           return null;
    
  1197.         }
    
  1198.       }
    
  1199. 
    
  1200.       class Bar extends React.Component {
    
  1201.         state = {b: false};
    
  1202.         componentDidUpdate() {
    
  1203.           ops.push('Bar updated');
    
  1204.           if (!this.state.b) {
    
  1205.             this.setState({b: true});
    
  1206.           }
    
  1207.         }
    
  1208.         render() {
    
  1209.           ops.push(`b: ${this.state.b}`);
    
  1210.           return null;
    
  1211.         }
    
  1212.       }
    
  1213. 
    
  1214.       const container = document.createElement('div');
    
  1215.       // Mount
    
  1216.       ReactDOM.render(
    
  1217.         <div>
    
  1218.           <Foo />
    
  1219.           <Bar />
    
  1220.         </div>,
    
  1221.         container,
    
  1222.       );
    
  1223.       // Root update
    
  1224.       ReactDOM.render(
    
  1225.         <div>
    
  1226.           <Foo />
    
  1227.           <Bar />
    
  1228.         </div>,
    
  1229.         container,
    
  1230.       );
    
  1231.       expect(ops).toEqual([
    
  1232.         // Mount
    
  1233.         'a: false',
    
  1234.         'b: false',
    
  1235.         // Root update
    
  1236.         'a: false',
    
  1237.         'b: false',
    
  1238.         'Foo updated',
    
  1239.         'Bar updated',
    
  1240.         // Subsequent update (both a and b should have flushed)
    
  1241.         'a: true',
    
  1242.         'b: true',
    
  1243.         'Foo updated',
    
  1244.         'Bar updated',
    
  1245.         // There should not be any additional updates
    
  1246.       ]);
    
  1247.     },
    
  1248.   );
    
  1249. 
    
  1250.   it('uses correct base state for setState inside render phase', () => {
    
  1251.     const ops = [];
    
  1252. 
    
  1253.     class Foo extends React.Component {
    
  1254.       state = {step: 0};
    
  1255.       render() {
    
  1256.         const memoizedStep = this.state.step;
    
  1257.         this.setState(baseState => {
    
  1258.           const baseStep = baseState.step;
    
  1259.           ops.push(`base: ${baseStep}, memoized: ${memoizedStep}`);
    
  1260.           return baseStep === 0 ? {step: 1} : null;
    
  1261.         });
    
  1262.         return null;
    
  1263.       }
    
  1264.     }
    
  1265. 
    
  1266.     const container = document.createElement('div');
    
  1267.     expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
    
  1268.       'Cannot update during an existing state transition',
    
  1269.     );
    
  1270.     expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
    
  1271.   });
    
  1272. 
    
  1273.   it('does not re-render if state update is null', () => {
    
  1274.     const container = document.createElement('div');
    
  1275. 
    
  1276.     let instance;
    
  1277.     let ops = [];
    
  1278.     class Foo extends React.Component {
    
  1279.       render() {
    
  1280.         instance = this;
    
  1281.         ops.push('render');
    
  1282.         return <div />;
    
  1283.       }
    
  1284.     }
    
  1285.     ReactDOM.render(<Foo />, container);
    
  1286. 
    
  1287.     ops = [];
    
  1288.     instance.setState(() => null);
    
  1289.     expect(ops).toEqual([]);
    
  1290.   });
    
  1291. 
    
  1292.   // Will change once we switch to async by default
    
  1293.   it('synchronously renders hidden subtrees', () => {
    
  1294.     const container = document.createElement('div');
    
  1295.     let ops = [];
    
  1296. 
    
  1297.     function Baz() {
    
  1298.       ops.push('Baz');
    
  1299.       return null;
    
  1300.     }
    
  1301. 
    
  1302.     function Bar() {
    
  1303.       ops.push('Bar');
    
  1304.       return null;
    
  1305.     }
    
  1306. 
    
  1307.     function Foo() {
    
  1308.       ops.push('Foo');
    
  1309.       return (
    
  1310.         <div>
    
  1311.           <div hidden={true}>
    
  1312.             <Bar />
    
  1313.           </div>
    
  1314.           <Baz />
    
  1315.         </div>
    
  1316.       );
    
  1317.     }
    
  1318. 
    
  1319.     // Mount
    
  1320.     ReactDOM.render(<Foo />, container);
    
  1321.     expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
    
  1322.     ops = [];
    
  1323. 
    
  1324.     // Update
    
  1325.     ReactDOM.render(<Foo />, container);
    
  1326.     expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
    
  1327.   });
    
  1328. 
    
  1329.   // @gate www
    
  1330.   it('delays sync updates inside hidden subtrees in Concurrent Mode', async () => {
    
  1331.     const container = document.createElement('div');
    
  1332. 
    
  1333.     function Baz() {
    
  1334.       Scheduler.log('Baz');
    
  1335.       return <p>baz</p>;
    
  1336.     }
    
  1337. 
    
  1338.     let setCounter;
    
  1339.     function Bar() {
    
  1340.       const [counter, _setCounter] = React.useState(0);
    
  1341.       setCounter = _setCounter;
    
  1342.       Scheduler.log('Bar');
    
  1343.       return <p>bar {counter}</p>;
    
  1344.     }
    
  1345. 
    
  1346.     function Foo() {
    
  1347.       Scheduler.log('Foo');
    
  1348.       React.useEffect(() => {
    
  1349.         Scheduler.log('Foo#effect');
    
  1350.       });
    
  1351.       return (
    
  1352.         <div>
    
  1353.           <LegacyHiddenDiv mode="hidden">
    
  1354.             <Bar />
    
  1355.           </LegacyHiddenDiv>
    
  1356.           <Baz />
    
  1357.         </div>
    
  1358.       );
    
  1359.     }
    
  1360. 
    
  1361.     const root = ReactDOMClient.createRoot(container);
    
  1362.     let hiddenDiv;
    
  1363.     await act(async () => {
    
  1364.       root.render(<Foo />);
    
  1365.       await waitFor(['Foo', 'Baz', 'Foo#effect']);
    
  1366.       hiddenDiv = container.firstChild.firstChild;
    
  1367.       expect(hiddenDiv.hidden).toBe(true);
    
  1368.       expect(hiddenDiv.innerHTML).toBe('');
    
  1369.       // Run offscreen update
    
  1370.       await waitForAll(['Bar']);
    
  1371.       expect(hiddenDiv.hidden).toBe(true);
    
  1372.       expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
    
  1373.     });
    
  1374. 
    
  1375.     ReactDOM.flushSync(() => {
    
  1376.       setCounter(1);
    
  1377.     });
    
  1378.     // Should not flush yet
    
  1379.     expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
    
  1380. 
    
  1381.     // Run offscreen update
    
  1382.     await waitForAll(['Bar']);
    
  1383.     expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
    
  1384.   });
    
  1385. 
    
  1386.   it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
    
  1387.     class Foo extends React.Component {
    
  1388.       componentDidMount() {
    
  1389.         const limit = 1200;
    
  1390.         for (let i = 0; i < limit; i++) {
    
  1391.           if (i < limit - 1) {
    
  1392.             ReactDOM.render(<div />, document.createElement('div'));
    
  1393.           } else {
    
  1394.             ReactDOM.render(<div />, document.createElement('div'), () => {
    
  1395.               // The "nested update limit" error isn't thrown until setState
    
  1396.               this.setState({});
    
  1397.             });
    
  1398.           }
    
  1399.         }
    
  1400.       }
    
  1401.       render() {
    
  1402.         return null;
    
  1403.       }
    
  1404.     }
    
  1405. 
    
  1406.     const container = document.createElement('div');
    
  1407.     ReactDOM.render(<Foo />, container);
    
  1408.   });
    
  1409. 
    
  1410.   it('resets the update counter for unrelated updates', () => {
    
  1411.     const container = document.createElement('div');
    
  1412.     const ref = React.createRef();
    
  1413. 
    
  1414.     class EventuallyTerminating extends React.Component {
    
  1415.       state = {step: 0};
    
  1416.       componentDidMount() {
    
  1417.         this.setState({step: 1});
    
  1418.       }
    
  1419.       componentDidUpdate() {
    
  1420.         if (this.state.step < limit) {
    
  1421.           this.setState({step: this.state.step + 1});
    
  1422.         }
    
  1423.       }
    
  1424.       render() {
    
  1425.         return this.state.step;
    
  1426.       }
    
  1427.     }
    
  1428. 
    
  1429.     let limit = 55;
    
  1430.     expect(() => {
    
  1431.       ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
    
  1432.     }).toThrow('Maximum');
    
  1433. 
    
  1434.     // Verify that we don't go over the limit if these updates are unrelated.
    
  1435.     limit -= 10;
    
  1436.     ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
    
  1437.     expect(container.textContent).toBe(limit.toString());
    
  1438.     ref.current.setState({step: 0});
    
  1439.     expect(container.textContent).toBe(limit.toString());
    
  1440.     ref.current.setState({step: 0});
    
  1441.     expect(container.textContent).toBe(limit.toString());
    
  1442. 
    
  1443.     limit += 10;
    
  1444.     expect(() => {
    
  1445.       ref.current.setState({step: 0});
    
  1446.     }).toThrow('Maximum');
    
  1447.     expect(ref.current).toBe(null);
    
  1448.   });
    
  1449. 
    
  1450.   it('does not fall into an infinite update loop', () => {
    
  1451.     class NonTerminating extends React.Component {
    
  1452.       state = {step: 0};
    
  1453.       componentDidMount() {
    
  1454.         this.setState({step: 1});
    
  1455.       }
    
  1456.       UNSAFE_componentWillUpdate() {
    
  1457.         this.setState({step: 2});
    
  1458.       }
    
  1459.       render() {
    
  1460.         return (
    
  1461.           <div>
    
  1462.             Hello {this.props.name}
    
  1463.             {this.state.step}
    
  1464.           </div>
    
  1465.         );
    
  1466.       }
    
  1467.     }
    
  1468. 
    
  1469.     const container = document.createElement('div');
    
  1470.     expect(() => {
    
  1471.       ReactDOM.render(<NonTerminating />, container);
    
  1472.     }).toThrow('Maximum');
    
  1473.   });
    
  1474. 
    
  1475.   it('does not fall into an infinite update loop with useLayoutEffect', () => {
    
  1476.     function NonTerminating() {
    
  1477.       const [step, setStep] = React.useState(0);
    
  1478.       React.useLayoutEffect(() => {
    
  1479.         setStep(x => x + 1);
    
  1480.       });
    
  1481.       return step;
    
  1482.     }
    
  1483. 
    
  1484.     const container = document.createElement('div');
    
  1485.     expect(() => {
    
  1486.       ReactDOM.render(<NonTerminating />, container);
    
  1487.     }).toThrow('Maximum');
    
  1488.   });
    
  1489. 
    
  1490.   it('can recover after falling into an infinite update loop', () => {
    
  1491.     class NonTerminating extends React.Component {
    
  1492.       state = {step: 0};
    
  1493.       componentDidMount() {
    
  1494.         this.setState({step: 1});
    
  1495.       }
    
  1496.       componentDidUpdate() {
    
  1497.         this.setState({step: 2});
    
  1498.       }
    
  1499.       render() {
    
  1500.         return this.state.step;
    
  1501.       }
    
  1502.     }
    
  1503. 
    
  1504.     class Terminating extends React.Component {
    
  1505.       state = {step: 0};
    
  1506.       componentDidMount() {
    
  1507.         this.setState({step: 1});
    
  1508.       }
    
  1509.       render() {
    
  1510.         return this.state.step;
    
  1511.       }
    
  1512.     }
    
  1513. 
    
  1514.     const container = document.createElement('div');
    
  1515.     expect(() => {
    
  1516.       ReactDOM.render(<NonTerminating />, container);
    
  1517.     }).toThrow('Maximum');
    
  1518. 
    
  1519.     ReactDOM.render(<Terminating />, container);
    
  1520.     expect(container.textContent).toBe('1');
    
  1521. 
    
  1522.     expect(() => {
    
  1523.       ReactDOM.render(<NonTerminating />, container);
    
  1524.     }).toThrow('Maximum');
    
  1525. 
    
  1526.     ReactDOM.render(<Terminating />, container);
    
  1527.     expect(container.textContent).toBe('1');
    
  1528.   });
    
  1529. 
    
  1530.   it('does not fall into mutually recursive infinite update loop with same container', () => {
    
  1531.     // Note: this test would fail if there were two or more different roots.
    
  1532. 
    
  1533.     class A extends React.Component {
    
  1534.       componentDidMount() {
    
  1535.         ReactDOM.render(<B />, container);
    
  1536.       }
    
  1537.       render() {
    
  1538.         return null;
    
  1539.       }
    
  1540.     }
    
  1541. 
    
  1542.     class B extends React.Component {
    
  1543.       componentDidMount() {
    
  1544.         ReactDOM.render(<A />, container);
    
  1545.       }
    
  1546.       render() {
    
  1547.         return null;
    
  1548.       }
    
  1549.     }
    
  1550. 
    
  1551.     const container = document.createElement('div');
    
  1552.     expect(() => {
    
  1553.       ReactDOM.render(<A />, container);
    
  1554.     }).toThrow('Maximum');
    
  1555.   });
    
  1556. 
    
  1557.   it('does not fall into an infinite error loop', () => {
    
  1558.     function BadRender() {
    
  1559.       throw new Error('error');
    
  1560.     }
    
  1561. 
    
  1562.     class ErrorBoundary extends React.Component {
    
  1563.       componentDidCatch() {
    
  1564.         // Schedule a no-op state update to avoid triggering a DEV warning in the test.
    
  1565.         this.setState({});
    
  1566. 
    
  1567.         this.props.parent.remount();
    
  1568.       }
    
  1569.       render() {
    
  1570.         return <BadRender />;
    
  1571.       }
    
  1572.     }
    
  1573. 
    
  1574.     class NonTerminating extends React.Component {
    
  1575.       state = {step: 0};
    
  1576.       remount() {
    
  1577.         this.setState(state => ({step: state.step + 1}));
    
  1578.       }
    
  1579.       render() {
    
  1580.         return <ErrorBoundary key={this.state.step} parent={this} />;
    
  1581.       }
    
  1582.     }
    
  1583. 
    
  1584.     const container = document.createElement('div');
    
  1585.     expect(() => {
    
  1586.       ReactDOM.render(<NonTerminating />, container);
    
  1587.     }).toThrow('Maximum');
    
  1588.   });
    
  1589. 
    
  1590.   it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
    
  1591.     const subscribers = [];
    
  1592. 
    
  1593.     class Child extends React.Component {
    
  1594.       state = {value: 'initial'};
    
  1595.       componentDidMount() {
    
  1596.         subscribers.push(this);
    
  1597.       }
    
  1598.       render() {
    
  1599.         return null;
    
  1600.       }
    
  1601.     }
    
  1602. 
    
  1603.     class App extends React.Component {
    
  1604.       render() {
    
  1605.         const children = [];
    
  1606.         for (let i = 0; i < 1200; i++) {
    
  1607.           children.push(<Child key={i} />);
    
  1608.         }
    
  1609.         return children;
    
  1610.       }
    
  1611.     }
    
  1612. 
    
  1613.     const container = document.createElement('div');
    
  1614.     ReactDOM.render(<App />, container);
    
  1615. 
    
  1616.     ReactDOM.unstable_batchedUpdates(() => {
    
  1617.       subscribers.forEach(s => {
    
  1618.         s.setState({value: 'update'});
    
  1619.       });
    
  1620.     });
    
  1621.   });
    
  1622. 
    
  1623.   // TODO: Replace this branch with @gate pragmas
    
  1624.   if (__DEV__) {
    
  1625.     it('warns about a deferred infinite update loop with useEffect', async () => {
    
  1626.       function NonTerminating() {
    
  1627.         const [step, setStep] = React.useState(0);
    
  1628.         React.useEffect(() => {
    
  1629.           setStep(x => x + 1);
    
  1630.         });
    
  1631.         return step;
    
  1632.       }
    
  1633. 
    
  1634.       function App() {
    
  1635.         return <NonTerminating />;
    
  1636.       }
    
  1637. 
    
  1638.       let error = null;
    
  1639.       let stack = null;
    
  1640.       const originalConsoleError = console.error;
    
  1641.       console.error = (e, s) => {
    
  1642.         error = e;
    
  1643.         stack = s;
    
  1644.         Scheduler.log('stop');
    
  1645.       };
    
  1646.       try {
    
  1647.         const container = document.createElement('div');
    
  1648.         const root = ReactDOMClient.createRoot(container);
    
  1649.         root.render(<App />);
    
  1650.         await waitFor(['stop']);
    
  1651.       } finally {
    
  1652.         console.error = originalConsoleError;
    
  1653.       }
    
  1654. 
    
  1655.       expect(error).toContain('Maximum update depth exceeded');
    
  1656.       expect(stack).toContain('at NonTerminating');
    
  1657.     });
    
  1658. 
    
  1659.     it('can have nested updates if they do not cross the limit', async () => {
    
  1660.       let _setStep;
    
  1661.       const LIMIT = 50;
    
  1662. 
    
  1663.       function Terminating() {
    
  1664.         const [step, setStep] = React.useState(0);
    
  1665.         _setStep = setStep;
    
  1666.         React.useEffect(() => {
    
  1667.           if (step < LIMIT) {
    
  1668.             setStep(x => x + 1);
    
  1669.           }
    
  1670.         });
    
  1671.         Scheduler.log(step);
    
  1672.         return step;
    
  1673.       }
    
  1674. 
    
  1675.       const container = document.createElement('div');
    
  1676.       await act(() => {
    
  1677.         ReactDOM.render(<Terminating />, container);
    
  1678.       });
    
  1679.       expect(container.textContent).toBe('50');
    
  1680.       await act(() => {
    
  1681.         _setStep(0);
    
  1682.       });
    
  1683.       expect(container.textContent).toBe('50');
    
  1684.     });
    
  1685. 
    
  1686.     it('can have many updates inside useEffect without triggering a warning', async () => {
    
  1687.       function Terminating() {
    
  1688.         const [step, setStep] = React.useState(0);
    
  1689.         React.useEffect(() => {
    
  1690.           for (let i = 0; i < 1000; i++) {
    
  1691.             setStep(x => x + 1);
    
  1692.           }
    
  1693.           Scheduler.log('Done');
    
  1694.         }, []);
    
  1695.         return step;
    
  1696.       }
    
  1697. 
    
  1698.       const container = document.createElement('div');
    
  1699.       await act(() => {
    
  1700.         ReactDOM.render(<Terminating />, container);
    
  1701.       });
    
  1702. 
    
  1703.       assertLog(['Done']);
    
  1704.       expect(container.textContent).toBe('1000');
    
  1705.     });
    
  1706.   }
    
  1707. 
    
  1708.   it('prevents infinite update loop triggered by synchronous updates in useEffect', () => {
    
  1709.     // Ignore flushSync warning
    
  1710.     spyOnDev(console, 'error').mockImplementation(() => {});
    
  1711. 
    
  1712.     function NonTerminating() {
    
  1713.       const [step, setStep] = React.useState(0);
    
  1714.       React.useEffect(() => {
    
  1715.         // Other examples of synchronous updates in useEffect are imperative
    
  1716.         // event dispatches like `el.focus`, or `useSyncExternalStore`, which
    
  1717.         // may schedule a synchronous update upon subscribing if it detects
    
  1718.         // that the store has been mutated since the initial render.
    
  1719.         //
    
  1720.         // (Originally I wrote this test using `el.focus` but those errors
    
  1721.         // get dispatched in a JSDOM event and I don't know how to "catch" those
    
  1722.         // so that they don't fail the test.)
    
  1723.         ReactDOM.flushSync(() => {
    
  1724.           setStep(step + 1);
    
  1725.         });
    
  1726.       }, [step]);
    
  1727.       return step;
    
  1728.     }
    
  1729. 
    
  1730.     const container = document.createElement('div');
    
  1731.     const root = ReactDOMClient.createRoot(container);
    
  1732.     expect(() => {
    
  1733.       ReactDOM.flushSync(() => {
    
  1734.         root.render(<NonTerminating />);
    
  1735.       });
    
  1736.     }).toThrow('Maximum update depth exceeded');
    
  1737.   });
    
  1738. });