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 ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  14. let PropTypes;
    
  15. let React;
    
  16. let ReactNoop;
    
  17. let Scheduler;
    
  18. let act;
    
  19. let assertLog;
    
  20. let waitForAll;
    
  21. let waitFor;
    
  22. let waitForThrow;
    
  23. 
    
  24. describe('ReactIncrementalErrorHandling', () => {
    
  25.   beforeEach(() => {
    
  26.     jest.resetModules();
    
  27.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  28.     ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
    
  29.     PropTypes = require('prop-types');
    
  30.     React = require('react');
    
  31.     ReactNoop = require('react-noop-renderer');
    
  32.     Scheduler = require('scheduler');
    
  33.     act = require('internal-test-utils').act;
    
  34. 
    
  35.     const InternalTestUtils = require('internal-test-utils');
    
  36.     assertLog = InternalTestUtils.assertLog;
    
  37.     waitForAll = InternalTestUtils.waitForAll;
    
  38.     waitFor = InternalTestUtils.waitFor;
    
  39.     waitForThrow = InternalTestUtils.waitForThrow;
    
  40.   });
    
  41. 
    
  42.   afterEach(() => {
    
  43.     jest.restoreAllMocks();
    
  44.   });
    
  45. 
    
  46.   function normalizeCodeLocInfo(str) {
    
  47.     return (
    
  48.       str &&
    
  49.       str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
    
  50.         return '\n    in ' + name + ' (at **)';
    
  51.       })
    
  52.     );
    
  53.   }
    
  54. 
    
  55.   // Note: This is based on a similar component we use in www. We can delete
    
  56.   // once the extra div wrapper is no longer necessary.
    
  57.   function LegacyHiddenDiv({children, mode}) {
    
  58.     return (
    
  59.       <div hidden={mode === 'hidden'}>
    
  60.         <React.unstable_LegacyHidden
    
  61.           mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
    
  62.           {children}
    
  63.         </React.unstable_LegacyHidden>
    
  64.       </div>
    
  65.     );
    
  66.   }
    
  67. 
    
  68.   it('recovers from errors asynchronously', async () => {
    
  69.     class ErrorBoundary extends React.Component {
    
  70.       state = {error: null};
    
  71.       static getDerivedStateFromError(error) {
    
  72.         Scheduler.log('getDerivedStateFromError');
    
  73.         return {error};
    
  74.       }
    
  75.       render() {
    
  76.         if (this.state.error) {
    
  77.           Scheduler.log('ErrorBoundary (catch)');
    
  78.           return <ErrorMessage error={this.state.error} />;
    
  79.         }
    
  80.         Scheduler.log('ErrorBoundary (try)');
    
  81.         return this.props.children;
    
  82.       }
    
  83.     }
    
  84. 
    
  85.     function ErrorMessage({error}) {
    
  86.       Scheduler.log('ErrorMessage');
    
  87.       return <span prop={`Caught an error: ${error.message}`} />;
    
  88.     }
    
  89. 
    
  90.     function Indirection({children}) {
    
  91.       Scheduler.log('Indirection');
    
  92.       return children || null;
    
  93.     }
    
  94. 
    
  95.     function BadRender({unused}) {
    
  96.       Scheduler.log('throw');
    
  97.       throw new Error('oops!');
    
  98.     }
    
  99. 
    
  100.     React.startTransition(() => {
    
  101.       ReactNoop.render(
    
  102.         <>
    
  103.           <ErrorBoundary>
    
  104.             <Indirection>
    
  105.               <Indirection>
    
  106.                 <Indirection>
    
  107.                   <BadRender />
    
  108.                 </Indirection>
    
  109.               </Indirection>
    
  110.             </Indirection>
    
  111.           </ErrorBoundary>
    
  112.           <Indirection />
    
  113.           <Indirection />
    
  114.         </>,
    
  115.       );
    
  116.     });
    
  117. 
    
  118.     // Start rendering asynchronously
    
  119.     await waitFor([
    
  120.       'ErrorBoundary (try)',
    
  121.       'Indirection',
    
  122.       'Indirection',
    
  123.       'Indirection',
    
  124.       // An error is thrown. React keeps rendering asynchronously.
    
  125.       'throw',
    
  126. 
    
  127.       // Call getDerivedStateFromError and re-render the error boundary, this
    
  128.       // time rendering an error message.
    
  129.       'getDerivedStateFromError',
    
  130.       'ErrorBoundary (catch)',
    
  131.       'ErrorMessage',
    
  132.     ]);
    
  133.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  134. 
    
  135.     // The work loop unwound to the nearest error boundary. Continue rendering
    
  136.     // asynchronously.
    
  137.     await waitFor(['Indirection']);
    
  138. 
    
  139.     // Since the error was thrown during an async render, React won't commit the
    
  140.     // result yet. After render we render the last child, React will attempt to
    
  141.     // render again, synchronously, just in case that happens to fix the error
    
  142.     // (i.e. as in the case of a data race). Flush just one more unit of work to
    
  143.     // demonstrate that this render is synchronous.
    
  144.     expect(ReactNoop.flushNextYield()).toEqual([
    
  145.       'Indirection',
    
  146. 
    
  147.       'ErrorBoundary (try)',
    
  148.       'Indirection',
    
  149.       'Indirection',
    
  150.       'Indirection',
    
  151. 
    
  152.       // The error was thrown again. This time, React will actually commit
    
  153.       // the result.
    
  154.       'throw',
    
  155.       'getDerivedStateFromError',
    
  156.       'ErrorBoundary (catch)',
    
  157.       'ErrorMessage',
    
  158.       'Indirection',
    
  159.       'Indirection',
    
  160.     ]);
    
  161. 
    
  162.     expect(ReactNoop).toMatchRenderedOutput(
    
  163.       <span prop="Caught an error: oops!" />,
    
  164.     );
    
  165.   });
    
  166. 
    
  167.   it('recovers from errors asynchronously (legacy, no getDerivedStateFromError)', async () => {
    
  168.     class ErrorBoundary extends React.Component {
    
  169.       state = {error: null};
    
  170.       componentDidCatch(error) {
    
  171.         Scheduler.log('componentDidCatch');
    
  172.         this.setState({error});
    
  173.       }
    
  174.       render() {
    
  175.         if (this.state.error) {
    
  176.           Scheduler.log('ErrorBoundary (catch)');
    
  177.           return <ErrorMessage error={this.state.error} />;
    
  178.         }
    
  179.         Scheduler.log('ErrorBoundary (try)');
    
  180.         return this.props.children;
    
  181.       }
    
  182.     }
    
  183. 
    
  184.     function ErrorMessage({error}) {
    
  185.       Scheduler.log('ErrorMessage');
    
  186.       return <span prop={`Caught an error: ${error.message}`} />;
    
  187.     }
    
  188. 
    
  189.     function Indirection({children}) {
    
  190.       Scheduler.log('Indirection');
    
  191.       return children || null;
    
  192.     }
    
  193. 
    
  194.     function BadRender({unused}) {
    
  195.       Scheduler.log('throw');
    
  196.       throw new Error('oops!');
    
  197.     }
    
  198. 
    
  199.     React.startTransition(() => {
    
  200.       ReactNoop.render(
    
  201.         <>
    
  202.           <ErrorBoundary>
    
  203.             <Indirection>
    
  204.               <Indirection>
    
  205.                 <Indirection>
    
  206.                   <BadRender />
    
  207.                 </Indirection>
    
  208.               </Indirection>
    
  209.             </Indirection>
    
  210.           </ErrorBoundary>
    
  211.           <Indirection />
    
  212.           <Indirection />
    
  213.         </>,
    
  214.       );
    
  215.     });
    
  216. 
    
  217.     // Start rendering asynchronously
    
  218.     await waitFor([
    
  219.       'ErrorBoundary (try)',
    
  220.       'Indirection',
    
  221.       'Indirection',
    
  222.       'Indirection',
    
  223.       // An error is thrown. React keeps rendering asynchronously.
    
  224.       'throw',
    
  225.     ]);
    
  226. 
    
  227.     // Still rendering async...
    
  228.     await waitFor(['Indirection']);
    
  229. 
    
  230.     await waitFor([
    
  231.       'Indirection',
    
  232.       // Now that the tree is complete, and there's no remaining work, React
    
  233.       // reverts to legacy mode to retry one more time before handling the error.
    
  234. 
    
  235.       'ErrorBoundary (try)',
    
  236.       'Indirection',
    
  237.       'Indirection',
    
  238.       'Indirection',
    
  239. 
    
  240.       // The error was thrown again. Now we can handle it.
    
  241.       'throw',
    
  242.       'Indirection',
    
  243.       'Indirection',
    
  244.       'componentDidCatch',
    
  245.       'ErrorBoundary (catch)',
    
  246.       'ErrorMessage',
    
  247.     ]);
    
  248.     expect(ReactNoop).toMatchRenderedOutput(
    
  249.       <span prop="Caught an error: oops!" />,
    
  250.     );
    
  251.   });
    
  252. 
    
  253.   it("retries at a lower priority if there's additional pending work", async () => {
    
  254.     function App(props) {
    
  255.       if (props.isBroken) {
    
  256.         Scheduler.log('error');
    
  257.         throw new Error('Oops!');
    
  258.       }
    
  259.       Scheduler.log('success');
    
  260.       return <span prop="Everything is fine." />;
    
  261.     }
    
  262. 
    
  263.     function onCommit() {
    
  264.       Scheduler.log('commit');
    
  265.     }
    
  266. 
    
  267.     React.startTransition(() => {
    
  268.       ReactNoop.render(<App isBroken={true} />, onCommit);
    
  269.     });
    
  270.     await waitFor(['error']);
    
  271. 
    
  272.     React.startTransition(() => {
    
  273.       // This update is in a separate batch
    
  274.       ReactNoop.render(<App isBroken={false} />, onCommit);
    
  275.     });
    
  276. 
    
  277.     // React will try to recover by rendering all the pending updates in a
    
  278.     // single batch, synchronously. This time it succeeds.
    
  279.     //
    
  280.     // This tells Scheduler to render a single unit of work. Because the render
    
  281.     // to recover from the error is synchronous, this should be enough to
    
  282.     // finish the rest of the work.
    
  283.     Scheduler.unstable_flushNumberOfYields(1);
    
  284.     assertLog([
    
  285.       'success',
    
  286.       // Nothing commits until the second update completes.
    
  287.       'commit',
    
  288.       'commit',
    
  289.     ]);
    
  290.     expect(ReactNoop).toMatchRenderedOutput(
    
  291.       <span prop="Everything is fine." />,
    
  292.     );
    
  293.   });
    
  294. 
    
  295.   // @gate www
    
  296.   it('does not include offscreen work when retrying after an error', async () => {
    
  297.     function App(props) {
    
  298.       if (props.isBroken) {
    
  299.         Scheduler.log('error');
    
  300.         throw new Error('Oops!');
    
  301.       }
    
  302.       Scheduler.log('success');
    
  303.       return (
    
  304.         <>
    
  305.           Everything is fine
    
  306.           <LegacyHiddenDiv mode="hidden">
    
  307.             <div>Offscreen content</div>
    
  308.           </LegacyHiddenDiv>
    
  309.         </>
    
  310.       );
    
  311.     }
    
  312. 
    
  313.     function onCommit() {
    
  314.       Scheduler.log('commit');
    
  315.     }
    
  316. 
    
  317.     React.startTransition(() => {
    
  318.       ReactNoop.render(<App isBroken={true} />, onCommit);
    
  319.     });
    
  320.     await waitFor(['error']);
    
  321. 
    
  322.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  323. 
    
  324.     React.startTransition(() => {
    
  325.       // This update is in a separate batch
    
  326.       ReactNoop.render(<App isBroken={false} />, onCommit);
    
  327.     });
    
  328. 
    
  329.     // React will try to recover by rendering all the pending updates in a
    
  330.     // single batch, synchronously. This time it succeeds.
    
  331.     //
    
  332.     // This tells Scheduler to render a single unit of work. Because the render
    
  333.     // to recover from the error is synchronous, this should be enough to
    
  334.     // finish the rest of the work.
    
  335.     Scheduler.unstable_flushNumberOfYields(1);
    
  336.     assertLog([
    
  337.       'success',
    
  338.       // Nothing commits until the second update completes.
    
  339.       'commit',
    
  340.       'commit',
    
  341.     ]);
    
  342.     // This should not include the offscreen content
    
  343.     expect(ReactNoop).toMatchRenderedOutput(
    
  344.       <>
    
  345.         Everything is fine
    
  346.         <div hidden={true} />
    
  347.       </>,
    
  348.     );
    
  349. 
    
  350.     // The offscreen content finishes in a subsequent render
    
  351.     await waitForAll([]);
    
  352.     expect(ReactNoop).toMatchRenderedOutput(
    
  353.       <>
    
  354.         Everything is fine
    
  355.         <div hidden={true}>
    
  356.           <div>Offscreen content</div>
    
  357.         </div>
    
  358.       </>,
    
  359.     );
    
  360.   });
    
  361. 
    
  362.   it('retries one more time before handling error', async () => {
    
  363.     function BadRender({unused}) {
    
  364.       Scheduler.log('BadRender');
    
  365.       throw new Error('oops');
    
  366.     }
    
  367. 
    
  368.     function Sibling({unused}) {
    
  369.       Scheduler.log('Sibling');
    
  370.       return <span prop="Sibling" />;
    
  371.     }
    
  372. 
    
  373.     function Parent({unused}) {
    
  374.       Scheduler.log('Parent');
    
  375.       return (
    
  376.         <>
    
  377.           <BadRender />
    
  378.           <Sibling />
    
  379.         </>
    
  380.       );
    
  381.     }
    
  382. 
    
  383.     React.startTransition(() => {
    
  384.       ReactNoop.render(<Parent />, () => Scheduler.log('commit'));
    
  385.     });
    
  386. 
    
  387.     // Render the bad component asynchronously
    
  388.     await waitFor(['Parent', 'BadRender']);
    
  389. 
    
  390.     // The work loop unwound to the nearest error boundary. React will try
    
  391.     // to render one more time, synchronously. Flush just one unit of work to
    
  392.     // demonstrate that this render is synchronous.
    
  393.     expect(() => Scheduler.unstable_flushNumberOfYields(1)).toThrow('oops');
    
  394.     assertLog(['Parent', 'BadRender', 'commit']);
    
  395.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  396.   });
    
  397. 
    
  398.   it('retries one more time if an error occurs during a render that expires midway through the tree', async () => {
    
  399.     function Oops({unused}) {
    
  400.       Scheduler.log('Oops');
    
  401.       throw new Error('Oops');
    
  402.     }
    
  403. 
    
  404.     function Text({text}) {
    
  405.       Scheduler.log(text);
    
  406.       return text;
    
  407.     }
    
  408. 
    
  409.     function App({unused}) {
    
  410.       return (
    
  411.         <>
    
  412.           <Text text="A" />
    
  413.           <Text text="B" />
    
  414.           <Oops />
    
  415.           <Text text="C" />
    
  416.           <Text text="D" />
    
  417.         </>
    
  418.       );
    
  419.     }
    
  420. 
    
  421.     React.startTransition(() => {
    
  422.       ReactNoop.render(<App />);
    
  423.     });
    
  424. 
    
  425.     // Render part of the tree
    
  426.     await waitFor(['A', 'B']);
    
  427. 
    
  428.     // Expire the render midway through
    
  429.     Scheduler.unstable_advanceTime(10000);
    
  430. 
    
  431.     expect(() => {
    
  432.       Scheduler.unstable_flushExpired();
    
  433.       ReactNoop.flushSync();
    
  434.     }).toThrow('Oops');
    
  435. 
    
  436.     assertLog([
    
  437.       // The render expired, but we shouldn't throw out the partial work.
    
  438.       // Finish the current level.
    
  439.       'Oops',
    
  440. 
    
  441.       // Since the error occurred during a partially concurrent render, we should
    
  442.       // retry one more time, synchronously.
    
  443.       'A',
    
  444.       'B',
    
  445.       'Oops',
    
  446.     ]);
    
  447.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  448.   });
    
  449. 
    
  450.   it('calls componentDidCatch multiple times for multiple errors', async () => {
    
  451.     let id = 0;
    
  452.     class BadMount extends React.Component {
    
  453.       componentDidMount() {
    
  454.         throw new Error(`Error ${++id}`);
    
  455.       }
    
  456.       render() {
    
  457.         Scheduler.log('BadMount');
    
  458.         return null;
    
  459.       }
    
  460.     }
    
  461. 
    
  462.     class ErrorBoundary extends React.Component {
    
  463.       state = {errorCount: 0};
    
  464.       componentDidCatch(error) {
    
  465.         Scheduler.log(`componentDidCatch: ${error.message}`);
    
  466.         this.setState(state => ({errorCount: state.errorCount + 1}));
    
  467.       }
    
  468.       render() {
    
  469.         if (this.state.errorCount > 0) {
    
  470.           return <span prop={`Number of errors: ${this.state.errorCount}`} />;
    
  471.         }
    
  472.         Scheduler.log('ErrorBoundary');
    
  473.         return this.props.children;
    
  474.       }
    
  475.     }
    
  476. 
    
  477.     ReactNoop.render(
    
  478.       <ErrorBoundary>
    
  479.         <BadMount />
    
  480.         <BadMount />
    
  481.         <BadMount />
    
  482.       </ErrorBoundary>,
    
  483.     );
    
  484. 
    
  485.     await waitForAll([
    
  486.       'ErrorBoundary',
    
  487.       'BadMount',
    
  488.       'BadMount',
    
  489.       'BadMount',
    
  490.       'componentDidCatch: Error 1',
    
  491.       'componentDidCatch: Error 2',
    
  492.       'componentDidCatch: Error 3',
    
  493.     ]);
    
  494.     expect(ReactNoop).toMatchRenderedOutput(
    
  495.       <span prop="Number of errors: 3" />,
    
  496.     );
    
  497.   });
    
  498. 
    
  499.   it('catches render error in a boundary during full deferred mounting', async () => {
    
  500.     class ErrorBoundary extends React.Component {
    
  501.       state = {error: null};
    
  502.       componentDidCatch(error) {
    
  503.         this.setState({error});
    
  504.       }
    
  505.       render() {
    
  506.         if (this.state.error) {
    
  507.           return (
    
  508.             <span prop={`Caught an error: ${this.state.error.message}.`} />
    
  509.           );
    
  510.         }
    
  511.         return this.props.children;
    
  512.       }
    
  513.     }
    
  514. 
    
  515.     function BrokenRender(props) {
    
  516.       throw new Error('Hello');
    
  517.     }
    
  518. 
    
  519.     ReactNoop.render(
    
  520.       <ErrorBoundary>
    
  521.         <BrokenRender />
    
  522.       </ErrorBoundary>,
    
  523.     );
    
  524.     await waitForAll([]);
    
  525.     expect(ReactNoop).toMatchRenderedOutput(
    
  526.       <span prop="Caught an error: Hello." />,
    
  527.     );
    
  528.   });
    
  529. 
    
  530.   it('catches render error in a boundary during partial deferred mounting', async () => {
    
  531.     class ErrorBoundary extends React.Component {
    
  532.       state = {error: null};
    
  533.       componentDidCatch(error) {
    
  534.         Scheduler.log('ErrorBoundary componentDidCatch');
    
  535.         this.setState({error});
    
  536.       }
    
  537.       render() {
    
  538.         if (this.state.error) {
    
  539.           Scheduler.log('ErrorBoundary render error');
    
  540.           return (
    
  541.             <span prop={`Caught an error: ${this.state.error.message}.`} />
    
  542.           );
    
  543.         }
    
  544.         Scheduler.log('ErrorBoundary render success');
    
  545.         return this.props.children;
    
  546.       }
    
  547.     }
    
  548. 
    
  549.     function BrokenRender({unused}) {
    
  550.       Scheduler.log('BrokenRender');
    
  551.       throw new Error('Hello');
    
  552.     }
    
  553. 
    
  554.     React.startTransition(() => {
    
  555.       ReactNoop.render(
    
  556.         <ErrorBoundary>
    
  557.           <BrokenRender />
    
  558.         </ErrorBoundary>,
    
  559.       );
    
  560.     });
    
  561. 
    
  562.     await waitFor(['ErrorBoundary render success']);
    
  563.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  564. 
    
  565.     await waitForAll([
    
  566.       'BrokenRender',
    
  567.       // React retries one more time
    
  568.       'ErrorBoundary render success',
    
  569. 
    
  570.       // Errored again on retry. Now handle it.
    
  571.       'BrokenRender',
    
  572.       'ErrorBoundary componentDidCatch',
    
  573.       'ErrorBoundary render error',
    
  574.     ]);
    
  575.     expect(ReactNoop).toMatchRenderedOutput(
    
  576.       <span prop="Caught an error: Hello." />,
    
  577.     );
    
  578.   });
    
  579. 
    
  580.   it('catches render error in a boundary during synchronous mounting', () => {
    
  581.     class ErrorBoundary extends React.Component {
    
  582.       state = {error: null};
    
  583.       componentDidCatch(error) {
    
  584.         Scheduler.log('ErrorBoundary componentDidCatch');
    
  585.         this.setState({error});
    
  586.       }
    
  587.       render() {
    
  588.         if (this.state.error) {
    
  589.           Scheduler.log('ErrorBoundary render error');
    
  590.           return (
    
  591.             <span prop={`Caught an error: ${this.state.error.message}.`} />
    
  592.           );
    
  593.         }
    
  594.         Scheduler.log('ErrorBoundary render success');
    
  595.         return this.props.children;
    
  596.       }
    
  597.     }
    
  598. 
    
  599.     function BrokenRender({unused}) {
    
  600.       Scheduler.log('BrokenRender');
    
  601.       throw new Error('Hello');
    
  602.     }
    
  603. 
    
  604.     ReactNoop.flushSync(() => {
    
  605.       ReactNoop.render(
    
  606.         <ErrorBoundary>
    
  607.           <BrokenRender />
    
  608.         </ErrorBoundary>,
    
  609.       );
    
  610.     });
    
  611. 
    
  612.     assertLog([
    
  613.       'ErrorBoundary render success',
    
  614.       'BrokenRender',
    
  615. 
    
  616.       // React retries one more time
    
  617.       'ErrorBoundary render success',
    
  618.       'BrokenRender',
    
  619. 
    
  620.       // Errored again on retry. Now handle it.
    
  621.       'ErrorBoundary componentDidCatch',
    
  622.       'ErrorBoundary render error',
    
  623.     ]);
    
  624.     expect(ReactNoop).toMatchRenderedOutput(
    
  625.       <span prop="Caught an error: Hello." />,
    
  626.     );
    
  627.   });
    
  628. 
    
  629.   it('catches render error in a boundary during batched mounting', () => {
    
  630.     class ErrorBoundary extends React.Component {
    
  631.       state = {error: null};
    
  632.       componentDidCatch(error) {
    
  633.         Scheduler.log('ErrorBoundary componentDidCatch');
    
  634.         this.setState({error});
    
  635.       }
    
  636.       render() {
    
  637.         if (this.state.error) {
    
  638.           Scheduler.log('ErrorBoundary render error');
    
  639.           return (
    
  640.             <span prop={`Caught an error: ${this.state.error.message}.`} />
    
  641.           );
    
  642.         }
    
  643.         Scheduler.log('ErrorBoundary render success');
    
  644.         return this.props.children;
    
  645.       }
    
  646.     }
    
  647. 
    
  648.     function BrokenRender({unused}) {
    
  649.       Scheduler.log('BrokenRender');
    
  650.       throw new Error('Hello');
    
  651.     }
    
  652. 
    
  653.     ReactNoop.flushSync(() => {
    
  654.       ReactNoop.render(<ErrorBoundary>Before the storm.</ErrorBoundary>);
    
  655.       ReactNoop.render(
    
  656.         <ErrorBoundary>
    
  657.           <BrokenRender />
    
  658.         </ErrorBoundary>,
    
  659.       );
    
  660.     });
    
  661. 
    
  662.     assertLog([
    
  663.       'ErrorBoundary render success',
    
  664.       'BrokenRender',
    
  665. 
    
  666.       // React retries one more time
    
  667.       'ErrorBoundary render success',
    
  668.       'BrokenRender',
    
  669. 
    
  670.       // Errored again on retry. Now handle it.
    
  671.       'ErrorBoundary componentDidCatch',
    
  672.       'ErrorBoundary render error',
    
  673.     ]);
    
  674.     expect(ReactNoop).toMatchRenderedOutput(
    
  675.       <span prop="Caught an error: Hello." />,
    
  676.     );
    
  677.   });
    
  678. 
    
  679.   it('propagates an error from a noop error boundary during full deferred mounting', async () => {
    
  680.     class RethrowErrorBoundary extends React.Component {
    
  681.       componentDidCatch(error) {
    
  682.         Scheduler.log('RethrowErrorBoundary componentDidCatch');
    
  683.         throw error;
    
  684.       }
    
  685.       render() {
    
  686.         Scheduler.log('RethrowErrorBoundary render');
    
  687.         return this.props.children;
    
  688.       }
    
  689.     }
    
  690. 
    
  691.     function BrokenRender({unused}) {
    
  692.       Scheduler.log('BrokenRender');
    
  693.       throw new Error('Hello');
    
  694.     }
    
  695. 
    
  696.     ReactNoop.render(
    
  697.       <RethrowErrorBoundary>
    
  698.         <BrokenRender />
    
  699.       </RethrowErrorBoundary>,
    
  700.     );
    
  701. 
    
  702.     await waitForThrow('Hello');
    
  703.     assertLog([
    
  704.       'RethrowErrorBoundary render',
    
  705.       'BrokenRender',
    
  706. 
    
  707.       // React retries one more time
    
  708.       'RethrowErrorBoundary render',
    
  709.       'BrokenRender',
    
  710. 
    
  711.       // Errored again on retry. Now handle it.
    
  712.       'RethrowErrorBoundary componentDidCatch',
    
  713.     ]);
    
  714.     expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
    
  715.   });
    
  716. 
    
  717.   it('propagates an error from a noop error boundary during partial deferred mounting', async () => {
    
  718.     class RethrowErrorBoundary extends React.Component {
    
  719.       componentDidCatch(error) {
    
  720.         Scheduler.log('RethrowErrorBoundary componentDidCatch');
    
  721.         throw error;
    
  722.       }
    
  723.       render() {
    
  724.         Scheduler.log('RethrowErrorBoundary render');
    
  725.         return this.props.children;
    
  726.       }
    
  727.     }
    
  728. 
    
  729.     function BrokenRender({unused}) {
    
  730.       Scheduler.log('BrokenRender');
    
  731.       throw new Error('Hello');
    
  732.     }
    
  733. 
    
  734.     React.startTransition(() => {
    
  735.       ReactNoop.render(
    
  736.         <RethrowErrorBoundary>
    
  737.           <BrokenRender />
    
  738.         </RethrowErrorBoundary>,
    
  739.       );
    
  740.     });
    
  741. 
    
  742.     await waitFor(['RethrowErrorBoundary render']);
    
  743. 
    
  744.     await waitForThrow('Hello');
    
  745.     assertLog([
    
  746.       'BrokenRender',
    
  747. 
    
  748.       // React retries one more time
    
  749.       'RethrowErrorBoundary render',
    
  750.       'BrokenRender',
    
  751. 
    
  752.       // Errored again on retry. Now handle it.
    
  753.       'RethrowErrorBoundary componentDidCatch',
    
  754.     ]);
    
  755.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  756.   });
    
  757. 
    
  758.   it('propagates an error from a noop error boundary during synchronous mounting', () => {
    
  759.     class RethrowErrorBoundary extends React.Component {
    
  760.       componentDidCatch(error) {
    
  761.         Scheduler.log('RethrowErrorBoundary componentDidCatch');
    
  762.         throw error;
    
  763.       }
    
  764.       render() {
    
  765.         Scheduler.log('RethrowErrorBoundary render');
    
  766.         return this.props.children;
    
  767.       }
    
  768.     }
    
  769. 
    
  770.     function BrokenRender({unused}) {
    
  771.       Scheduler.log('BrokenRender');
    
  772.       throw new Error('Hello');
    
  773.     }
    
  774. 
    
  775.     expect(() => {
    
  776.       ReactNoop.flushSync(() => {
    
  777.         ReactNoop.render(
    
  778.           <RethrowErrorBoundary>
    
  779.             <BrokenRender />
    
  780.           </RethrowErrorBoundary>,
    
  781.         );
    
  782.       });
    
  783.     }).toThrow('Hello');
    
  784.     assertLog([
    
  785.       'RethrowErrorBoundary render',
    
  786.       'BrokenRender',
    
  787. 
    
  788.       // React retries one more time
    
  789.       'RethrowErrorBoundary render',
    
  790.       'BrokenRender',
    
  791. 
    
  792.       // Errored again on retry. Now handle it.
    
  793.       'RethrowErrorBoundary componentDidCatch',
    
  794.     ]);
    
  795.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  796.   });
    
  797. 
    
  798.   it('propagates an error from a noop error boundary during batched mounting', () => {
    
  799.     class RethrowErrorBoundary extends React.Component {
    
  800.       componentDidCatch(error) {
    
  801.         Scheduler.log('RethrowErrorBoundary componentDidCatch');
    
  802.         throw error;
    
  803.       }
    
  804.       render() {
    
  805.         Scheduler.log('RethrowErrorBoundary render');
    
  806.         return this.props.children;
    
  807.       }
    
  808.     }
    
  809. 
    
  810.     function BrokenRender({unused}) {
    
  811.       Scheduler.log('BrokenRender');
    
  812.       throw new Error('Hello');
    
  813.     }
    
  814. 
    
  815.     expect(() => {
    
  816.       ReactNoop.flushSync(() => {
    
  817.         ReactNoop.render(
    
  818.           <RethrowErrorBoundary>Before the storm.</RethrowErrorBoundary>,
    
  819.         );
    
  820.         ReactNoop.render(
    
  821.           <RethrowErrorBoundary>
    
  822.             <BrokenRender />
    
  823.           </RethrowErrorBoundary>,
    
  824.         );
    
  825.       });
    
  826.     }).toThrow('Hello');
    
  827.     assertLog([
    
  828.       'RethrowErrorBoundary render',
    
  829.       'BrokenRender',
    
  830. 
    
  831.       // React retries one more time
    
  832.       'RethrowErrorBoundary render',
    
  833.       'BrokenRender',
    
  834. 
    
  835.       // Errored again on retry. Now handle it.
    
  836.       'RethrowErrorBoundary componentDidCatch',
    
  837.     ]);
    
  838.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  839.   });
    
  840. 
    
  841.   it('applies batched updates regardless despite errors in scheduling', async () => {
    
  842.     ReactNoop.render(<span prop="a:1" />);
    
  843.     expect(() => {
    
  844.       ReactNoop.batchedUpdates(() => {
    
  845.         ReactNoop.render(<span prop="a:2" />);
    
  846.         ReactNoop.render(<span prop="a:3" />);
    
  847.         throw new Error('Hello');
    
  848.       });
    
  849.     }).toThrow('Hello');
    
  850.     await waitForAll([]);
    
  851.     expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
    
  852.   });
    
  853. 
    
  854.   it('applies nested batched updates despite errors in scheduling', async () => {
    
  855.     ReactNoop.render(<span prop="a:1" />);
    
  856.     expect(() => {
    
  857.       ReactNoop.batchedUpdates(() => {
    
  858.         ReactNoop.render(<span prop="a:2" />);
    
  859.         ReactNoop.render(<span prop="a:3" />);
    
  860.         ReactNoop.batchedUpdates(() => {
    
  861.           ReactNoop.render(<span prop="a:4" />);
    
  862.           ReactNoop.render(<span prop="a:5" />);
    
  863.           throw new Error('Hello');
    
  864.         });
    
  865.       });
    
  866.     }).toThrow('Hello');
    
  867.     await waitForAll([]);
    
  868.     expect(ReactNoop).toMatchRenderedOutput(<span prop="a:5" />);
    
  869.   });
    
  870. 
    
  871.   // TODO: Is this a breaking change?
    
  872.   it('defers additional sync work to a separate event after an error', async () => {
    
  873.     ReactNoop.render(<span prop="a:1" />);
    
  874.     expect(() => {
    
  875.       ReactNoop.flushSync(() => {
    
  876.         ReactNoop.batchedUpdates(() => {
    
  877.           ReactNoop.render(<span prop="a:2" />);
    
  878.           ReactNoop.render(<span prop="a:3" />);
    
  879.           throw new Error('Hello');
    
  880.         });
    
  881.       });
    
  882.     }).toThrow('Hello');
    
  883.     await waitForAll([]);
    
  884.     expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
    
  885.   });
    
  886. 
    
  887.   it('can schedule updates after uncaught error in render on mount', async () => {
    
  888.     function BrokenRender({unused}) {
    
  889.       Scheduler.log('BrokenRender');
    
  890.       throw new Error('Hello');
    
  891.     }
    
  892. 
    
  893.     function Foo({unused}) {
    
  894.       Scheduler.log('Foo');
    
  895.       return null;
    
  896.     }
    
  897. 
    
  898.     ReactNoop.render(<BrokenRender />);
    
  899.     await waitForThrow('Hello');
    
  900.     ReactNoop.render(<Foo />);
    
  901.     assertLog([
    
  902.       'BrokenRender',
    
  903.       // React retries one more time
    
  904.       'BrokenRender',
    
  905.       // Errored again on retry
    
  906.     ]);
    
  907.     await waitForAll(['Foo']);
    
  908.   });
    
  909. 
    
  910.   it('can schedule updates after uncaught error in render on update', async () => {
    
  911.     function BrokenRender({shouldThrow}) {
    
  912.       Scheduler.log('BrokenRender');
    
  913.       if (shouldThrow) {
    
  914.         throw new Error('Hello');
    
  915.       }
    
  916.       return null;
    
  917.     }
    
  918. 
    
  919.     function Foo({unused}) {
    
  920.       Scheduler.log('Foo');
    
  921.       return null;
    
  922.     }
    
  923. 
    
  924.     ReactNoop.render(<BrokenRender shouldThrow={false} />);
    
  925.     await waitForAll(['BrokenRender']);
    
  926. 
    
  927.     ReactNoop.render(<BrokenRender shouldThrow={true} />);
    
  928.     await waitForThrow('Hello');
    
  929.     assertLog([
    
  930.       'BrokenRender',
    
  931.       // React retries one more time
    
  932.       'BrokenRender',
    
  933.       // Errored again on retry
    
  934.     ]);
    
  935. 
    
  936.     ReactNoop.render(<Foo />);
    
  937.     await waitForAll(['Foo']);
    
  938.   });
    
  939. 
    
  940.   it('can schedule updates after uncaught error during unmounting', async () => {
    
  941.     class BrokenComponentWillUnmount extends React.Component {
    
  942.       render() {
    
  943.         return <div />;
    
  944.       }
    
  945.       componentWillUnmount() {
    
  946.         throw new Error('Hello');
    
  947.       }
    
  948.     }
    
  949. 
    
  950.     function Foo() {
    
  951.       Scheduler.log('Foo');
    
  952.       return null;
    
  953.     }
    
  954. 
    
  955.     ReactNoop.render(<BrokenComponentWillUnmount />);
    
  956.     await waitForAll([]);
    
  957. 
    
  958.     ReactNoop.render(<div />);
    
  959.     await waitForThrow('Hello');
    
  960. 
    
  961.     ReactNoop.render(<Foo />);
    
  962.     await waitForAll(['Foo']);
    
  963.   });
    
  964. 
    
  965.   it('should not attempt to recover an unmounting error boundary', async () => {
    
  966.     class Parent extends React.Component {
    
  967.       componentWillUnmount() {
    
  968.         Scheduler.log('Parent componentWillUnmount');
    
  969.       }
    
  970.       render() {
    
  971.         return <Boundary />;
    
  972.       }
    
  973.     }
    
  974. 
    
  975.     class Boundary extends React.Component {
    
  976.       componentDidCatch(e) {
    
  977.         Scheduler.log(`Caught error: ${e.message}`);
    
  978.       }
    
  979.       render() {
    
  980.         return <ThrowsOnUnmount />;
    
  981.       }
    
  982.     }
    
  983. 
    
  984.     class ThrowsOnUnmount extends React.Component {
    
  985.       componentWillUnmount() {
    
  986.         Scheduler.log('ThrowsOnUnmount componentWillUnmount');
    
  987.         throw new Error('unmount error');
    
  988.       }
    
  989.       render() {
    
  990.         return null;
    
  991.       }
    
  992.     }
    
  993. 
    
  994.     ReactNoop.render(<Parent />);
    
  995.     await waitForAll([]);
    
  996. 
    
  997.     // Because the error boundary is also unmounting,
    
  998.     // an error in ThrowsOnUnmount should be rethrown.
    
  999.     ReactNoop.render(null);
    
  1000.     await waitForThrow('unmount error');
    
  1001.     await assertLog([
    
  1002.       'Parent componentWillUnmount',
    
  1003.       'ThrowsOnUnmount componentWillUnmount',
    
  1004.     ]);
    
  1005. 
    
  1006.     ReactNoop.render(<Parent />);
    
  1007.   });
    
  1008. 
    
  1009.   it('can unmount an error boundary before it is handled', async () => {
    
  1010.     let parent;
    
  1011. 
    
  1012.     class Parent extends React.Component {
    
  1013.       state = {step: 0};
    
  1014.       render() {
    
  1015.         parent = this;
    
  1016.         return this.state.step === 0 ? <Boundary /> : null;
    
  1017.       }
    
  1018.     }
    
  1019. 
    
  1020.     class Boundary extends React.Component {
    
  1021.       componentDidCatch() {}
    
  1022.       render() {
    
  1023.         return <Child />;
    
  1024.       }
    
  1025.     }
    
  1026. 
    
  1027.     class Child extends React.Component {
    
  1028.       componentDidUpdate() {
    
  1029.         parent.setState({step: 1});
    
  1030.         throw new Error('update error');
    
  1031.       }
    
  1032.       render() {
    
  1033.         return null;
    
  1034.       }
    
  1035.     }
    
  1036. 
    
  1037.     ReactNoop.render(<Parent />);
    
  1038.     await waitForAll([]);
    
  1039. 
    
  1040.     ReactNoop.flushSync(() => {
    
  1041.       ReactNoop.render(<Parent />);
    
  1042.     });
    
  1043.   });
    
  1044. 
    
  1045.   it('continues work on other roots despite caught errors', async () => {
    
  1046.     class ErrorBoundary extends React.Component {
    
  1047.       state = {error: null};
    
  1048.       componentDidCatch(error) {
    
  1049.         this.setState({error});
    
  1050.       }
    
  1051.       render() {
    
  1052.         if (this.state.error) {
    
  1053.           return (
    
  1054.             <span prop={`Caught an error: ${this.state.error.message}.`} />
    
  1055.           );
    
  1056.         }
    
  1057.         return this.props.children;
    
  1058.       }
    
  1059.     }
    
  1060. 
    
  1061.     function BrokenRender(props) {
    
  1062.       throw new Error('Hello');
    
  1063.     }
    
  1064. 
    
  1065.     ReactNoop.renderToRootWithID(
    
  1066.       <ErrorBoundary>
    
  1067.         <BrokenRender />
    
  1068.       </ErrorBoundary>,
    
  1069.       'a',
    
  1070.     );
    
  1071.     ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
    
  1072.     await waitForAll([]);
    
  1073.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(
    
  1074.       <span prop="Caught an error: Hello." />,
    
  1075.     );
    
  1076.     await waitForAll([]);
    
  1077.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
    
  1078.   });
    
  1079. 
    
  1080.   it('continues work on other roots despite uncaught errors', async () => {
    
  1081.     function BrokenRender(props) {
    
  1082.       throw new Error(props.label);
    
  1083.     }
    
  1084. 
    
  1085.     ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
    
  1086.     await waitForThrow('a');
    
  1087.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
    
  1088. 
    
  1089.     ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
    
  1090.     ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
    
  1091.     await waitForThrow('a');
    
  1092. 
    
  1093.     await waitForAll([]);
    
  1094.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
    
  1095.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:2" />);
    
  1096. 
    
  1097.     ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
    
  1098.     ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
    
  1099.     await waitForThrow('b');
    
  1100.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:3" />);
    
  1101.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
    
  1102. 
    
  1103.     ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
    
  1104.     ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
    
  1105.     ReactNoop.renderToRootWithID(<span prop="c:4" />, 'c');
    
  1106.     await waitForThrow('b');
    
  1107.     await waitForAll([]);
    
  1108.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:4" />);
    
  1109.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
    
  1110.     expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:4" />);
    
  1111. 
    
  1112.     ReactNoop.renderToRootWithID(<span prop="a:5" />, 'a');
    
  1113.     ReactNoop.renderToRootWithID(<span prop="b:5" />, 'b');
    
  1114.     ReactNoop.renderToRootWithID(<span prop="c:5" />, 'c');
    
  1115.     ReactNoop.renderToRootWithID(<span prop="d:5" />, 'd');
    
  1116.     ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
    
  1117.     await waitForThrow('e');
    
  1118.     await waitForAll([]);
    
  1119.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:5" />);
    
  1120.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:5" />);
    
  1121.     expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:5" />);
    
  1122.     expect(ReactNoop.getChildrenAsJSX('d')).toEqual(<span prop="d:5" />);
    
  1123.     expect(ReactNoop.getChildrenAsJSX('e')).toEqual(null);
    
  1124. 
    
  1125.     ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
    
  1126.     ReactNoop.renderToRootWithID(<span prop="b:6" />, 'b');
    
  1127.     ReactNoop.renderToRootWithID(<BrokenRender label="c" />, 'c');
    
  1128.     ReactNoop.renderToRootWithID(<span prop="d:6" />, 'd');
    
  1129.     ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
    
  1130.     ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
    
  1131. 
    
  1132.     await waitForThrow('a');
    
  1133.     await waitForThrow('c');
    
  1134.     await waitForThrow('e');
    
  1135. 
    
  1136.     await waitForAll([]);
    
  1137.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
    
  1138.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:6" />);
    
  1139.     expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
    
  1140.     expect(ReactNoop.getChildrenAsJSX('d')).toEqual(<span prop="d:6" />);
    
  1141.     expect(ReactNoop.getChildrenAsJSX('e')).toEqual(null);
    
  1142.     expect(ReactNoop.getChildrenAsJSX('f')).toEqual(<span prop="f:6" />);
    
  1143. 
    
  1144.     ReactNoop.unmountRootWithID('a');
    
  1145.     ReactNoop.unmountRootWithID('b');
    
  1146.     ReactNoop.unmountRootWithID('c');
    
  1147.     ReactNoop.unmountRootWithID('d');
    
  1148.     ReactNoop.unmountRootWithID('e');
    
  1149.     ReactNoop.unmountRootWithID('f');
    
  1150.     await waitForAll([]);
    
  1151.     expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
    
  1152.     expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
    
  1153.     expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
    
  1154.     expect(ReactNoop.getChildrenAsJSX('d')).toEqual(null);
    
  1155.     expect(ReactNoop.getChildrenAsJSX('e')).toEqual(null);
    
  1156.     expect(ReactNoop.getChildrenAsJSX('f')).toEqual(null);
    
  1157.   });
    
  1158. 
    
  1159.   // NOTE: When legacy context is removed, it's probably fine to just delete
    
  1160.   // this test. There's plenty of test coverage of stack unwinding in general
    
  1161.   // because it's used for new context, suspense, and many other features.
    
  1162.   // It has to be tested independently for each feature anyway. So although it
    
  1163.   // doesn't look like it, this test is specific to legacy context.
    
  1164.   // @gate !disableLegacyContext
    
  1165.   it('unwinds the context stack correctly on error', async () => {
    
  1166.     class Provider extends React.Component {
    
  1167.       static childContextTypes = {message: PropTypes.string};
    
  1168.       static contextTypes = {message: PropTypes.string};
    
  1169.       getChildContext() {
    
  1170.         return {
    
  1171.           message: (this.context.message || '') + this.props.message,
    
  1172.         };
    
  1173.       }
    
  1174.       render() {
    
  1175.         return this.props.children;
    
  1176.       }
    
  1177.     }
    
  1178. 
    
  1179.     function Connector(props, context) {
    
  1180.       return <span prop={context.message} />;
    
  1181.     }
    
  1182. 
    
  1183.     Connector.contextTypes = {
    
  1184.       message: PropTypes.string,
    
  1185.     };
    
  1186. 
    
  1187.     function BadRender() {
    
  1188.       throw new Error('render error');
    
  1189.     }
    
  1190. 
    
  1191.     class Boundary extends React.Component {
    
  1192.       state = {error: null};
    
  1193.       componentDidCatch(error) {
    
  1194.         this.setState({error});
    
  1195.       }
    
  1196.       render() {
    
  1197.         return (
    
  1198.           <Provider message="b">
    
  1199.             <Provider message="c">
    
  1200.               <Provider message="d">
    
  1201.                 <Provider message="e">
    
  1202.                   {!this.state.error && <BadRender />}
    
  1203.                 </Provider>
    
  1204.               </Provider>
    
  1205.             </Provider>
    
  1206.           </Provider>
    
  1207.         );
    
  1208.       }
    
  1209.     }
    
  1210. 
    
  1211.     ReactNoop.render(
    
  1212.       <Provider message="a">
    
  1213.         <Boundary />
    
  1214.         <Connector />
    
  1215.       </Provider>,
    
  1216.     );
    
  1217.     await waitForAll([]);
    
  1218. 
    
  1219.     // If the context stack does not unwind, span will get 'abcde'
    
  1220.     expect(ReactNoop).toMatchRenderedOutput(<span prop="a" />);
    
  1221.   });
    
  1222. 
    
  1223.   it('catches reconciler errors in a boundary during mounting', async () => {
    
  1224.     class ErrorBoundary extends React.Component {
    
  1225.       state = {error: null};
    
  1226.       componentDidCatch(error) {
    
  1227.         this.setState({error});
    
  1228.       }
    
  1229.       render() {
    
  1230.         if (this.state.error) {
    
  1231.           return <span prop={this.state.error.message} />;
    
  1232.         }
    
  1233.         return this.props.children;
    
  1234.       }
    
  1235.     }
    
  1236.     const InvalidType = undefined;
    
  1237.     function BrokenRender(props) {
    
  1238.       return <InvalidType />;
    
  1239.     }
    
  1240. 
    
  1241.     ReactNoop.render(
    
  1242.       <ErrorBoundary>
    
  1243.         <BrokenRender />
    
  1244.       </ErrorBoundary>,
    
  1245.     );
    
  1246.     await expect(async () => await waitForAll([])).toErrorDev([
    
  1247.       'Warning: React.createElement: type is invalid -- expected a string',
    
  1248.       // React retries once on error
    
  1249.       'Warning: React.createElement: type is invalid -- expected a string',
    
  1250.     ]);
    
  1251.     expect(ReactNoop).toMatchRenderedOutput(
    
  1252.       <span
    
  1253.         prop={
    
  1254.           'Element type is invalid: expected a string (for built-in components) or ' +
    
  1255.           'a class/function (for composite components) but got: undefined.' +
    
  1256.           (__DEV__
    
  1257.             ? " You likely forgot to export your component from the file it's " +
    
  1258.               'defined in, or you might have mixed up default and named imports.' +
    
  1259.               '\n\nCheck the render method of `BrokenRender`.'
    
  1260.             : '')
    
  1261.         }
    
  1262.       />,
    
  1263.     );
    
  1264.   });
    
  1265. 
    
  1266.   it('catches reconciler errors in a boundary during update', async () => {
    
  1267.     class ErrorBoundary extends React.Component {
    
  1268.       state = {error: null};
    
  1269.       componentDidCatch(error) {
    
  1270.         this.setState({error});
    
  1271.       }
    
  1272.       render() {
    
  1273.         if (this.state.error) {
    
  1274.           return <span prop={this.state.error.message} />;
    
  1275.         }
    
  1276.         return this.props.children;
    
  1277.       }
    
  1278.     }
    
  1279. 
    
  1280.     const InvalidType = undefined;
    
  1281.     function BrokenRender(props) {
    
  1282.       return props.fail ? <InvalidType /> : <span />;
    
  1283.     }
    
  1284. 
    
  1285.     ReactNoop.render(
    
  1286.       <ErrorBoundary>
    
  1287.         <BrokenRender fail={false} />
    
  1288.       </ErrorBoundary>,
    
  1289.     );
    
  1290.     await waitForAll([]);
    
  1291. 
    
  1292.     ReactNoop.render(
    
  1293.       <ErrorBoundary>
    
  1294.         <BrokenRender fail={true} />
    
  1295.       </ErrorBoundary>,
    
  1296.     );
    
  1297.     await expect(async () => await waitForAll([])).toErrorDev([
    
  1298.       'Warning: React.createElement: type is invalid -- expected a string',
    
  1299.       // React retries once on error
    
  1300.       'Warning: React.createElement: type is invalid -- expected a string',
    
  1301.     ]);
    
  1302.     expect(ReactNoop).toMatchRenderedOutput(
    
  1303.       <span
    
  1304.         prop={
    
  1305.           'Element type is invalid: expected a string (for built-in components) or ' +
    
  1306.           'a class/function (for composite components) but got: undefined.' +
    
  1307.           (__DEV__
    
  1308.             ? " You likely forgot to export your component from the file it's " +
    
  1309.               'defined in, or you might have mixed up default and named imports.' +
    
  1310.               '\n\nCheck the render method of `BrokenRender`.'
    
  1311.             : '')
    
  1312.         }
    
  1313.       />,
    
  1314.     );
    
  1315.   });
    
  1316. 
    
  1317.   it('recovers from uncaught reconciler errors', async () => {
    
  1318.     const InvalidType = undefined;
    
  1319.     expect(() => ReactNoop.render(<InvalidType />)).toErrorDev(
    
  1320.       'Warning: React.createElement: type is invalid -- expected a string',
    
  1321.       {withoutStack: true},
    
  1322.     );
    
  1323.     await waitForThrow(
    
  1324.       'Element type is invalid: expected a string (for built-in components) or ' +
    
  1325.         'a class/function (for composite components) but got: undefined.' +
    
  1326.         (__DEV__
    
  1327.           ? " You likely forgot to export your component from the file it's " +
    
  1328.             'defined in, or you might have mixed up default and named imports.'
    
  1329.           : ''),
    
  1330.     );
    
  1331. 
    
  1332.     ReactNoop.render(<span prop="hi" />);
    
  1333.     await waitForAll([]);
    
  1334.     expect(ReactNoop).toMatchRenderedOutput(<span prop="hi" />);
    
  1335.   });
    
  1336. 
    
  1337.   it('unmounts components with uncaught errors', async () => {
    
  1338.     let inst;
    
  1339. 
    
  1340.     class BrokenRenderAndUnmount extends React.Component {
    
  1341.       state = {fail: false};
    
  1342.       componentWillUnmount() {
    
  1343.         Scheduler.log('BrokenRenderAndUnmount componentWillUnmount');
    
  1344.       }
    
  1345.       render() {
    
  1346.         inst = this;
    
  1347.         if (this.state.fail) {
    
  1348.           throw new Error('Hello.');
    
  1349.         }
    
  1350.         return null;
    
  1351.       }
    
  1352.     }
    
  1353. 
    
  1354.     class Parent extends React.Component {
    
  1355.       componentWillUnmount() {
    
  1356.         Scheduler.log('Parent componentWillUnmount [!]');
    
  1357.         throw new Error('One does not simply unmount me.');
    
  1358.       }
    
  1359.       render() {
    
  1360.         return this.props.children;
    
  1361.       }
    
  1362.     }
    
  1363. 
    
  1364.     ReactNoop.render(
    
  1365.       <Parent>
    
  1366.         <Parent>
    
  1367.           <BrokenRenderAndUnmount />
    
  1368.         </Parent>
    
  1369.       </Parent>,
    
  1370.     );
    
  1371.     await waitForAll([]);
    
  1372. 
    
  1373.     let aggregateError;
    
  1374.     try {
    
  1375.       ReactNoop.flushSync(() => {
    
  1376.         inst.setState({fail: true});
    
  1377.       });
    
  1378.     } catch (e) {
    
  1379.       aggregateError = e;
    
  1380.     }
    
  1381. 
    
  1382.     assertLog([
    
  1383.       // Attempt to clean up.
    
  1384.       // Errors in parents shouldn't stop children from unmounting.
    
  1385.       'Parent componentWillUnmount [!]',
    
  1386.       'Parent componentWillUnmount [!]',
    
  1387.       'BrokenRenderAndUnmount componentWillUnmount',
    
  1388.     ]);
    
  1389.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  1390. 
    
  1391.     // React threw both errors as a single AggregateError
    
  1392.     const errors = aggregateError.errors;
    
  1393.     expect(errors.length).toBe(2);
    
  1394.     expect(errors[0].message).toBe('Hello.');
    
  1395.     expect(errors[1].message).toBe('One does not simply unmount me.');
    
  1396.   });
    
  1397. 
    
  1398.   it('does not interrupt unmounting if detaching a ref throws', async () => {
    
  1399.     class Bar extends React.Component {
    
  1400.       componentWillUnmount() {
    
  1401.         Scheduler.log('Bar unmount');
    
  1402.       }
    
  1403.       render() {
    
  1404.         return <span prop="Bar" />;
    
  1405.       }
    
  1406.     }
    
  1407. 
    
  1408.     function barRef(inst) {
    
  1409.       if (inst === null) {
    
  1410.         Scheduler.log('barRef detach');
    
  1411.         throw new Error('Detach error');
    
  1412.       }
    
  1413.       Scheduler.log('barRef attach');
    
  1414.     }
    
  1415. 
    
  1416.     function Foo(props) {
    
  1417.       return <div>{props.hide ? null : <Bar ref={barRef} />}</div>;
    
  1418.     }
    
  1419. 
    
  1420.     ReactNoop.render(<Foo />);
    
  1421.     await waitForAll(['barRef attach']);
    
  1422.     expect(ReactNoop).toMatchRenderedOutput(
    
  1423.       <div>
    
  1424.         <span prop="Bar" />
    
  1425.       </div>,
    
  1426.     );
    
  1427. 
    
  1428.     // Unmount
    
  1429.     ReactNoop.render(<Foo hide={true} />);
    
  1430.     await waitForThrow('Detach error');
    
  1431.     assertLog([
    
  1432.       'barRef detach',
    
  1433.       // Bar should unmount even though its ref threw an error while detaching
    
  1434.       'Bar unmount',
    
  1435.     ]);
    
  1436.     // Because there was an error, entire tree should unmount
    
  1437.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  1438.   });
    
  1439. 
    
  1440.   it('handles error thrown by host config while working on failed root', async () => {
    
  1441.     ReactNoop.render(<errorInBeginPhase />);
    
  1442.     await waitForThrow('Error in host config.');
    
  1443.   });
    
  1444. 
    
  1445.   it('handles error thrown by top-level callback', async () => {
    
  1446.     ReactNoop.render(<div />, () => {
    
  1447.       throw new Error('Error!');
    
  1448.     });
    
  1449.     await waitForThrow('Error!');
    
  1450.   });
    
  1451. 
    
  1452.   it('error boundaries capture non-errors', async () => {
    
  1453.     spyOnProd(console, 'error').mockImplementation(() => {});
    
  1454.     spyOnDev(console, 'error').mockImplementation(() => {});
    
  1455. 
    
  1456.     class ErrorBoundary extends React.Component {
    
  1457.       state = {error: null};
    
  1458.       componentDidCatch(error) {
    
  1459.         // Should not be called
    
  1460.         Scheduler.log('componentDidCatch');
    
  1461.         this.setState({error});
    
  1462.       }
    
  1463.       render() {
    
  1464.         if (this.state.error) {
    
  1465.           Scheduler.log('ErrorBoundary (catch)');
    
  1466.           return (
    
  1467.             <span
    
  1468.               prop={`Caught an error: ${this.state.error.nonStandardMessage}`}
    
  1469.             />
    
  1470.           );
    
  1471.         }
    
  1472.         Scheduler.log('ErrorBoundary (try)');
    
  1473.         return this.props.children;
    
  1474.       }
    
  1475.     }
    
  1476. 
    
  1477.     function Indirection({children}) {
    
  1478.       Scheduler.log('Indirection');
    
  1479.       return children;
    
  1480.     }
    
  1481. 
    
  1482.     const notAnError = {nonStandardMessage: 'oops'};
    
  1483.     function BadRender({unused}) {
    
  1484.       Scheduler.log('BadRender');
    
  1485.       throw notAnError;
    
  1486.     }
    
  1487. 
    
  1488.     ReactNoop.render(
    
  1489.       <ErrorBoundary>
    
  1490.         <Indirection>
    
  1491.           <BadRender />
    
  1492.         </Indirection>
    
  1493.       </ErrorBoundary>,
    
  1494.     );
    
  1495. 
    
  1496.     await waitForAll([
    
  1497.       'ErrorBoundary (try)',
    
  1498.       'Indirection',
    
  1499.       'BadRender',
    
  1500. 
    
  1501.       // React retries one more time
    
  1502.       'ErrorBoundary (try)',
    
  1503.       'Indirection',
    
  1504.       'BadRender',
    
  1505. 
    
  1506.       // Errored again on retry. Now handle it.
    
  1507.       'componentDidCatch',
    
  1508.       'ErrorBoundary (catch)',
    
  1509.     ]);
    
  1510.     expect(ReactNoop).toMatchRenderedOutput(
    
  1511.       <span prop="Caught an error: oops" />,
    
  1512.     );
    
  1513. 
    
  1514.     if (__DEV__) {
    
  1515.       expect(console.error).toHaveBeenCalledTimes(1);
    
  1516.       expect(console.error.mock.calls[0][0]).toContain(
    
  1517.         'The above error occurred in the <BadRender> component:',
    
  1518.       );
    
  1519.     } else {
    
  1520.       expect(console.error).toHaveBeenCalledTimes(1);
    
  1521.       expect(console.error.mock.calls[0][0]).toBe(notAnError);
    
  1522.     }
    
  1523.   });
    
  1524. 
    
  1525.   // TODO: Error boundary does not catch promises
    
  1526. 
    
  1527.   it('continues working on siblings of a component that throws', async () => {
    
  1528.     class ErrorBoundary extends React.Component {
    
  1529.       state = {error: null};
    
  1530.       componentDidCatch(error) {
    
  1531.         Scheduler.log('componentDidCatch');
    
  1532.         this.setState({error});
    
  1533.       }
    
  1534.       render() {
    
  1535.         if (this.state.error) {
    
  1536.           Scheduler.log('ErrorBoundary (catch)');
    
  1537.           return <ErrorMessage error={this.state.error} />;
    
  1538.         }
    
  1539.         Scheduler.log('ErrorBoundary (try)');
    
  1540.         return this.props.children;
    
  1541.       }
    
  1542.     }
    
  1543. 
    
  1544.     function ErrorMessage({error}) {
    
  1545.       Scheduler.log('ErrorMessage');
    
  1546.       return <span prop={`Caught an error: ${error.message}`} />;
    
  1547.     }
    
  1548. 
    
  1549.     function BadRenderSibling({unused}) {
    
  1550.       Scheduler.log('BadRenderSibling');
    
  1551.       return null;
    
  1552.     }
    
  1553. 
    
  1554.     function BadRender({unused}) {
    
  1555.       Scheduler.log('throw');
    
  1556.       throw new Error('oops!');
    
  1557.     }
    
  1558. 
    
  1559.     ReactNoop.render(
    
  1560.       <ErrorBoundary>
    
  1561.         <BadRender />
    
  1562.         <BadRenderSibling />
    
  1563.         <BadRenderSibling />
    
  1564.       </ErrorBoundary>,
    
  1565.     );
    
  1566. 
    
  1567.     await waitForAll([
    
  1568.       'ErrorBoundary (try)',
    
  1569.       'throw',
    
  1570.       // Continue rendering siblings after BadRender throws
    
  1571. 
    
  1572.       // React retries one more time
    
  1573.       'ErrorBoundary (try)',
    
  1574.       'throw',
    
  1575. 
    
  1576.       // Errored again on retry. Now handle it.
    
  1577.       'componentDidCatch',
    
  1578.       'ErrorBoundary (catch)',
    
  1579.       'ErrorMessage',
    
  1580.     ]);
    
  1581.     expect(ReactNoop).toMatchRenderedOutput(
    
  1582.       <span prop="Caught an error: oops!" />,
    
  1583.     );
    
  1584.   });
    
  1585. 
    
  1586.   it('calls the correct lifecycles on the error boundary after catching an error (mixed)', async () => {
    
  1587.     // This test seems a bit contrived, but it's based on an actual regression
    
  1588.     // where we checked for the existence of didUpdate instead of didMount, and
    
  1589.     // didMount was not defined.
    
  1590.     function BadRender({unused}) {
    
  1591.       Scheduler.log('throw');
    
  1592.       throw new Error('oops!');
    
  1593.     }
    
  1594. 
    
  1595.     class Parent extends React.Component {
    
  1596.       state = {error: null, other: false};
    
  1597.       componentDidCatch(error) {
    
  1598.         Scheduler.log('did catch');
    
  1599.         this.setState({error});
    
  1600.       }
    
  1601.       componentDidUpdate() {
    
  1602.         Scheduler.log('did update');
    
  1603.       }
    
  1604.       render() {
    
  1605.         if (this.state.error) {
    
  1606.           Scheduler.log('render error message');
    
  1607.           return <span prop={`Caught an error: ${this.state.error.message}`} />;
    
  1608.         }
    
  1609.         Scheduler.log('render');
    
  1610.         return <BadRender />;
    
  1611.       }
    
  1612.     }
    
  1613. 
    
  1614.     ReactNoop.render(<Parent step={1} />);
    
  1615.     await waitFor([
    
  1616.       'render',
    
  1617.       'throw',
    
  1618.       'render',
    
  1619.       'throw',
    
  1620.       'did catch',
    
  1621.       'render error message',
    
  1622.       'did update',
    
  1623.     ]);
    
  1624.     expect(ReactNoop).toMatchRenderedOutput(
    
  1625.       <span prop="Caught an error: oops!" />,
    
  1626.     );
    
  1627.   });
    
  1628. 
    
  1629.   it('provides component stack to the error boundary with componentDidCatch', async () => {
    
  1630.     class ErrorBoundary extends React.Component {
    
  1631.       state = {error: null, errorInfo: null};
    
  1632.       componentDidCatch(error, errorInfo) {
    
  1633.         this.setState({error, errorInfo});
    
  1634.       }
    
  1635.       render() {
    
  1636.         if (this.state.errorInfo) {
    
  1637.           Scheduler.log('render error message');
    
  1638.           return (
    
  1639.             <span
    
  1640.               prop={`Caught an error:${normalizeCodeLocInfo(
    
  1641.                 this.state.errorInfo.componentStack,
    
  1642.               )}.`}
    
  1643.             />
    
  1644.           );
    
  1645.         }
    
  1646.         return this.props.children;
    
  1647.       }
    
  1648.     }
    
  1649. 
    
  1650.     function BrokenRender(props) {
    
  1651.       throw new Error('Hello');
    
  1652.     }
    
  1653. 
    
  1654.     ReactNoop.render(
    
  1655.       <ErrorBoundary>
    
  1656.         <BrokenRender />
    
  1657.       </ErrorBoundary>,
    
  1658.     );
    
  1659.     await waitForAll(['render error message']);
    
  1660.     expect(ReactNoop).toMatchRenderedOutput(
    
  1661.       <span
    
  1662.         prop={
    
  1663.           'Caught an error:\n' +
    
  1664.           '    in BrokenRender (at **)\n' +
    
  1665.           '    in ErrorBoundary (at **).'
    
  1666.         }
    
  1667.       />,
    
  1668.     );
    
  1669.   });
    
  1670. 
    
  1671.   it('does not provide component stack to the error boundary with getDerivedStateFromError', async () => {
    
  1672.     class ErrorBoundary extends React.Component {
    
  1673.       state = {error: null};
    
  1674.       static getDerivedStateFromError(error, errorInfo) {
    
  1675.         expect(errorInfo).toBeUndefined();
    
  1676.         return {error};
    
  1677.       }
    
  1678.       render() {
    
  1679.         if (this.state.error) {
    
  1680.           return <span prop={`Caught an error: ${this.state.error.message}`} />;
    
  1681.         }
    
  1682.         return this.props.children;
    
  1683.       }
    
  1684.     }
    
  1685. 
    
  1686.     function BrokenRender(props) {
    
  1687.       throw new Error('Hello');
    
  1688.     }
    
  1689. 
    
  1690.     ReactNoop.render(
    
  1691.       <ErrorBoundary>
    
  1692.         <BrokenRender />
    
  1693.       </ErrorBoundary>,
    
  1694.     );
    
  1695.     await waitForAll([]);
    
  1696.     expect(ReactNoop).toMatchRenderedOutput(
    
  1697.       <span prop="Caught an error: Hello" />,
    
  1698.     );
    
  1699.   });
    
  1700. 
    
  1701.   it('provides component stack even if overriding prepareStackTrace', async () => {
    
  1702.     Error.prepareStackTrace = function (error, callsites) {
    
  1703.       const stack = ['An error occurred:', error.message];
    
  1704.       for (let i = 0; i < callsites.length; i++) {
    
  1705.         const callsite = callsites[i];
    
  1706.         stack.push(
    
  1707.           '\t' + callsite.getFunctionName(),
    
  1708.           '\t\tat ' + callsite.getFileName(),
    
  1709.           '\t\ton line ' + callsite.getLineNumber(),
    
  1710.         );
    
  1711.       }
    
  1712. 
    
  1713.       return stack.join('\n');
    
  1714.     };
    
  1715. 
    
  1716.     class ErrorBoundary extends React.Component {
    
  1717.       state = {error: null, errorInfo: null};
    
  1718.       componentDidCatch(error, errorInfo) {
    
  1719.         this.setState({error, errorInfo});
    
  1720.       }
    
  1721.       render() {
    
  1722.         if (this.state.errorInfo) {
    
  1723.           Scheduler.log('render error message');
    
  1724.           return (
    
  1725.             <span
    
  1726.               prop={`Caught an error:${normalizeCodeLocInfo(
    
  1727.                 this.state.errorInfo.componentStack,
    
  1728.               )}.`}
    
  1729.             />
    
  1730.           );
    
  1731.         }
    
  1732.         return this.props.children;
    
  1733.       }
    
  1734.     }
    
  1735. 
    
  1736.     function BrokenRender(props) {
    
  1737.       throw new Error('Hello');
    
  1738.     }
    
  1739. 
    
  1740.     ReactNoop.render(
    
  1741.       <ErrorBoundary>
    
  1742.         <BrokenRender />
    
  1743.       </ErrorBoundary>,
    
  1744.     );
    
  1745.     await waitForAll(['render error message']);
    
  1746.     Error.prepareStackTrace = undefined;
    
  1747. 
    
  1748.     expect(ReactNoop).toMatchRenderedOutput(
    
  1749.       <span
    
  1750.         prop={
    
  1751.           'Caught an error:\n' +
    
  1752.           '    in BrokenRender (at **)\n' +
    
  1753.           '    in ErrorBoundary (at **).'
    
  1754.         }
    
  1755.       />,
    
  1756.     );
    
  1757.   });
    
  1758. 
    
  1759.   // @gate !disableModulePatternComponents
    
  1760.   it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', async () => {
    
  1761.     function Provider() {
    
  1762.       return {
    
  1763.         getChildContext() {
    
  1764.           return {foo: 'bar'};
    
  1765.         },
    
  1766.         render() {
    
  1767.           return 'Hi';
    
  1768.         },
    
  1769.       };
    
  1770.     }
    
  1771.     Provider.childContextTypes = {
    
  1772.       x: () => {},
    
  1773.     };
    
  1774.     Provider.getDerivedStateFromProps = () => {
    
  1775.       throw new Error('Oops!');
    
  1776.     };
    
  1777. 
    
  1778.     ReactNoop.render(<Provider />);
    
  1779.     await expect(async () => {
    
  1780.       await waitForThrow('Oops!');
    
  1781.     }).toErrorDev([
    
  1782.       'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
    
  1783.         'Change Provider to a class that extends React.Component instead. ' +
    
  1784.         "If you can't use a class try assigning the prototype on the function as a workaround. " +
    
  1785.         '`Provider.prototype = React.Component.prototype`. ' +
    
  1786.         "Don't use an arrow function since it cannot be called with `new` by React.",
    
  1787.     ]);
    
  1788.   });
    
  1789. 
    
  1790.   it('uncaught errors should be discarded if the render is aborted', async () => {
    
  1791.     const root = ReactNoop.createRoot();
    
  1792. 
    
  1793.     function Oops({unused}) {
    
  1794.       Scheduler.log('Oops');
    
  1795.       throw Error('Oops');
    
  1796.     }
    
  1797. 
    
  1798.     await act(async () => {
    
  1799.       React.startTransition(() => {
    
  1800.         root.render(<Oops />);
    
  1801.       });
    
  1802. 
    
  1803.       // Render past the component that throws, then yield.
    
  1804.       await waitFor(['Oops']);
    
  1805.       expect(root).toMatchRenderedOutput(null);
    
  1806.       // Interleaved update. When the root completes, instead of throwing the
    
  1807.       // error, it should try rendering again. This update will cause it to
    
  1808.       // recover gracefully.
    
  1809.       React.startTransition(() => {
    
  1810.         root.render('Everything is fine.');
    
  1811.       });
    
  1812.     });
    
  1813. 
    
  1814.     // Should finish without throwing.
    
  1815.     expect(root).toMatchRenderedOutput('Everything is fine.');
    
  1816.   });
    
  1817. 
    
  1818.   it('uncaught errors are discarded if the render is aborted, case 2', async () => {
    
  1819.     const {useState} = React;
    
  1820.     const root = ReactNoop.createRoot();
    
  1821. 
    
  1822.     let setShouldThrow;
    
  1823.     function Oops() {
    
  1824.       const [shouldThrow, _setShouldThrow] = useState(false);
    
  1825.       setShouldThrow = _setShouldThrow;
    
  1826.       if (shouldThrow) {
    
  1827.         throw Error('Oops');
    
  1828.       }
    
  1829.       return null;
    
  1830.     }
    
  1831. 
    
  1832.     function AllGood() {
    
  1833.       Scheduler.log('Everything is fine.');
    
  1834.       return 'Everything is fine.';
    
  1835.     }
    
  1836. 
    
  1837.     await act(() => {
    
  1838.       root.render(<Oops />);
    
  1839.     });
    
  1840. 
    
  1841.     await act(async () => {
    
  1842.       // Schedule a default pri and a low pri update on the root.
    
  1843.       root.render(<Oops />);
    
  1844.       React.startTransition(() => {
    
  1845.         root.render(<AllGood />);
    
  1846.       });
    
  1847. 
    
  1848.       // Render through just the default pri update. The low pri update remains on
    
  1849.       // the queue.
    
  1850.       await waitFor(['Everything is fine.']);
    
  1851. 
    
  1852.       // Schedule a discrete update on a child that triggers an error.
    
  1853.       // The root should capture this error. But since there's still a pending
    
  1854.       // update on the root, the error should be suppressed.
    
  1855.       ReactNoop.discreteUpdates(() => {
    
  1856.         setShouldThrow(true);
    
  1857.       });
    
  1858.     });
    
  1859.     // Should render the final state without throwing the error.
    
  1860.     assertLog(['Everything is fine.']);
    
  1861.     expect(root).toMatchRenderedOutput('Everything is fine.');
    
  1862.   });
    
  1863. 
    
  1864.   it("does not infinite loop if there's a render phase update in the same render as an error", async () => {
    
  1865.     // Some React features may schedule a render phase update as an
    
  1866.     // implementation detail. When an error is accompanied by a render phase
    
  1867.     // update, we assume that it comes from React internals, because render
    
  1868.     // phase updates triggered from userspace are not allowed (we log a
    
  1869.     // warning). So we keep attempting to recover until no more opaque
    
  1870.     // identifiers need to be upgraded. However, we should give up after some
    
  1871.     // point to prevent an infinite loop in the case where there is (by
    
  1872.     // accident) a render phase triggered from userspace.
    
  1873. 
    
  1874.     spyOnDev(console, 'error').mockImplementation(() => {});
    
  1875. 
    
  1876.     let numberOfThrows = 0;
    
  1877. 
    
  1878.     let setStateInRenderPhase;
    
  1879.     function Child() {
    
  1880.       const [, setState] = React.useState(0);
    
  1881.       setStateInRenderPhase = setState;
    
  1882.       return 'All good';
    
  1883.     }
    
  1884. 
    
  1885.     function App({shouldThrow}) {
    
  1886.       if (shouldThrow) {
    
  1887.         setStateInRenderPhase();
    
  1888.         numberOfThrows++;
    
  1889.         throw new Error('Oops!');
    
  1890.       }
    
  1891.       return <Child />;
    
  1892.     }
    
  1893. 
    
  1894.     const root = ReactNoop.createRoot();
    
  1895.     await act(() => {
    
  1896.       root.render(<App shouldThrow={false} />);
    
  1897.     });
    
  1898.     expect(root).toMatchRenderedOutput('All good');
    
  1899. 
    
  1900.     let error;
    
  1901.     try {
    
  1902.       await act(() => {
    
  1903.         root.render(<App shouldThrow={true} />);
    
  1904.       });
    
  1905.     } catch (e) {
    
  1906.       error = e;
    
  1907.     }
    
  1908. 
    
  1909.     expect(error.message).toBe('Oops!');
    
  1910.     expect(numberOfThrows < 100).toBe(true);
    
  1911. 
    
  1912.     if (__DEV__) {
    
  1913.       expect(console.error).toHaveBeenCalledTimes(2);
    
  1914.       expect(console.error.mock.calls[0][0]).toContain(
    
  1915.         'Cannot update a component (`%s`) while rendering a different component',
    
  1916.       );
    
  1917.       expect(console.error.mock.calls[1][0]).toContain(
    
  1918.         'The above error occurred in the <App> component',
    
  1919.       );
    
  1920.     }
    
  1921.   });
    
  1922. 
    
  1923.   if (global.__PERSISTENT__) {
    
  1924.     it('regression test: should fatal if error is thrown at the root', async () => {
    
  1925.       const root = ReactNoop.createRoot();
    
  1926.       root.render('Error when completing root');
    
  1927.       await waitForThrow('Error when completing root');
    
  1928.     });
    
  1929.   }
    
  1930. });