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. 
    
  8. 'use strict';
    
  9. 
    
  10. describe('ReactDOMConsoleErrorReporting', () => {
    
  11.   let act;
    
  12.   let React;
    
  13.   let ReactDOM;
    
  14.   let ReactDOMClient;
    
  15. 
    
  16.   let ErrorBoundary;
    
  17.   let NoError;
    
  18.   let container;
    
  19.   let windowOnError;
    
  20.   let waitForThrow;
    
  21. 
    
  22.   beforeEach(() => {
    
  23.     jest.resetModules();
    
  24.     act = require('internal-test-utils').act;
    
  25.     React = require('react');
    
  26.     ReactDOM = require('react-dom');
    
  27.     ReactDOMClient = require('react-dom/client');
    
  28. 
    
  29.     const InternalTestUtils = require('internal-test-utils');
    
  30.     waitForThrow = InternalTestUtils.waitForThrow;
    
  31. 
    
  32.     ErrorBoundary = class extends React.Component {
    
  33.       state = {error: null};
    
  34.       static getDerivedStateFromError(error) {
    
  35.         return {error};
    
  36.       }
    
  37.       render() {
    
  38.         if (this.state.error) {
    
  39.           return <h1>Caught: {this.state.error.message}</h1>;
    
  40.         }
    
  41.         return this.props.children;
    
  42.       }
    
  43.     };
    
  44.     NoError = function () {
    
  45.       return <h1>OK</h1>;
    
  46.     };
    
  47.     container = document.createElement('div');
    
  48.     document.body.appendChild(container);
    
  49.     windowOnError = jest.fn();
    
  50.     window.addEventListener('error', windowOnError);
    
  51.   });
    
  52. 
    
  53.   afterEach(() => {
    
  54.     document.body.removeChild(container);
    
  55.     window.removeEventListener('error', windowOnError);
    
  56.     jest.restoreAllMocks();
    
  57.   });
    
  58. 
    
  59.   describe('ReactDOMClient.createRoot', () => {
    
  60.     it('logs errors during event handlers', async () => {
    
  61.       spyOnDevAndProd(console, 'error');
    
  62. 
    
  63.       function Foo() {
    
  64.         return (
    
  65.           <button
    
  66.             onClick={() => {
    
  67.               throw Error('Boom');
    
  68.             }}>
    
  69.             click me
    
  70.           </button>
    
  71.         );
    
  72.       }
    
  73. 
    
  74.       const root = ReactDOMClient.createRoot(container);
    
  75.       await act(() => {
    
  76.         root.render(<Foo />);
    
  77.       });
    
  78. 
    
  79.       await act(() => {
    
  80.         container.firstChild.dispatchEvent(
    
  81.           new MouseEvent('click', {
    
  82.             bubbles: true,
    
  83.           }),
    
  84.         );
    
  85.       });
    
  86. 
    
  87.       if (__DEV__) {
    
  88.         expect(windowOnError.mock.calls).toEqual([
    
  89.           [
    
  90.             // Reported because we're in a browser click event:
    
  91.             expect.objectContaining({
    
  92.               message: 'Boom',
    
  93.             }),
    
  94.           ],
    
  95.           [
    
  96.             // This one is jsdom-only. Real browser deduplicates it.
    
  97.             // (In DEV, we have a nested event due to guarded callback.)
    
  98.             expect.objectContaining({
    
  99.               message: 'Boom',
    
  100.             }),
    
  101.           ],
    
  102.         ]);
    
  103.         expect(console.error.mock.calls).toEqual([
    
  104.           [
    
  105.             // Reported because we're in a browser click event:
    
  106.             expect.objectContaining({
    
  107.               detail: expect.objectContaining({
    
  108.                 message: 'Boom',
    
  109.               }),
    
  110.               type: 'unhandled exception',
    
  111.             }),
    
  112.           ],
    
  113.           [
    
  114.             // This one is jsdom-only. Real browser deduplicates it.
    
  115.             // (In DEV, we have a nested event due to guarded callback.)
    
  116.             expect.objectContaining({
    
  117.               detail: expect.objectContaining({
    
  118.                 message: 'Boom',
    
  119.               }),
    
  120.               type: 'unhandled exception',
    
  121.             }),
    
  122.           ],
    
  123.         ]);
    
  124.       } else {
    
  125.         expect(windowOnError.mock.calls).toEqual([
    
  126.           [
    
  127.             // Reported because we're in a browser click event:
    
  128.             expect.objectContaining({
    
  129.               message: 'Boom',
    
  130.             }),
    
  131.           ],
    
  132.         ]);
    
  133.         expect(console.error.mock.calls).toEqual([
    
  134.           [
    
  135.             // Reported because we're in a browser click event:
    
  136.             expect.objectContaining({
    
  137.               detail: expect.objectContaining({
    
  138.                 message: 'Boom',
    
  139.               }),
    
  140.               type: 'unhandled exception',
    
  141.             }),
    
  142.           ],
    
  143.         ]);
    
  144.       }
    
  145. 
    
  146.       // Check next render doesn't throw.
    
  147.       windowOnError.mockReset();
    
  148.       console.error.mockReset();
    
  149.       await act(() => {
    
  150.         root.render(<NoError />);
    
  151.       });
    
  152.       expect(container.textContent).toBe('OK');
    
  153.       expect(windowOnError.mock.calls).toEqual([]);
    
  154.       if (__DEV__) {
    
  155.         expect(console.error.mock.calls).toEqual([]);
    
  156.       }
    
  157.     });
    
  158. 
    
  159.     it('logs render errors without an error boundary', async () => {
    
  160.       spyOnDevAndProd(console, 'error');
    
  161. 
    
  162.       function Foo() {
    
  163.         throw Error('Boom');
    
  164.       }
    
  165. 
    
  166.       const root = ReactDOMClient.createRoot(container);
    
  167.       await act(async () => {
    
  168.         root.render(<Foo />);
    
  169.         await waitForThrow('Boom');
    
  170.       });
    
  171. 
    
  172.       if (__DEV__) {
    
  173.         expect(windowOnError.mock.calls).toEqual([
    
  174.           [
    
  175.             // Reported due to guarded callback:
    
  176.             expect.objectContaining({
    
  177.               message: 'Boom',
    
  178.             }),
    
  179.           ],
    
  180.           [
    
  181.             // This is only duplicated with createRoot
    
  182.             // because it retries once with a sync render.
    
  183.             expect.objectContaining({
    
  184.               message: 'Boom',
    
  185.             }),
    
  186.           ],
    
  187.         ]);
    
  188.         expect(console.error.mock.calls).toEqual([
    
  189.           [
    
  190.             // Reported due to the guarded callback:
    
  191.             expect.objectContaining({
    
  192.               detail: expect.objectContaining({
    
  193.                 message: 'Boom',
    
  194.               }),
    
  195.               type: 'unhandled exception',
    
  196.             }),
    
  197.           ],
    
  198.           [
    
  199.             // This is only duplicated with createRoot
    
  200.             // because it retries once with a sync render.
    
  201.             expect.objectContaining({
    
  202.               detail: expect.objectContaining({
    
  203.                 message: 'Boom',
    
  204.               }),
    
  205.               type: 'unhandled exception',
    
  206.             }),
    
  207.           ],
    
  208.           [
    
  209.             // Addendum by React:
    
  210.             expect.stringContaining(
    
  211.               'The above error occurred in the <Foo> component',
    
  212.             ),
    
  213.           ],
    
  214.         ]);
    
  215.       } else {
    
  216.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  217.         // so in production we don't see an error event.
    
  218.         expect(windowOnError.mock.calls).toEqual([]);
    
  219.         expect(console.error.mock.calls).toEqual([
    
  220.           [
    
  221.             // Reported by React with no extra message:
    
  222.             expect.objectContaining({
    
  223.               message: 'Boom',
    
  224.             }),
    
  225.           ],
    
  226.         ]);
    
  227.       }
    
  228. 
    
  229.       // Check next render doesn't throw.
    
  230.       windowOnError.mockReset();
    
  231.       console.error.mockReset();
    
  232.       await act(() => {
    
  233.         root.render(<NoError />);
    
  234.       });
    
  235.       expect(container.textContent).toBe('OK');
    
  236.       expect(windowOnError.mock.calls).toEqual([]);
    
  237.       if (__DEV__) {
    
  238.         expect(console.error.mock.calls).toEqual([]);
    
  239.       }
    
  240.     });
    
  241. 
    
  242.     it('logs render errors with an error boundary', async () => {
    
  243.       spyOnDevAndProd(console, 'error');
    
  244. 
    
  245.       function Foo() {
    
  246.         throw Error('Boom');
    
  247.       }
    
  248. 
    
  249.       const root = ReactDOMClient.createRoot(container);
    
  250.       await act(() => {
    
  251.         root.render(
    
  252.           <ErrorBoundary>
    
  253.             <Foo />
    
  254.           </ErrorBoundary>,
    
  255.         );
    
  256.       });
    
  257. 
    
  258.       if (__DEV__) {
    
  259.         expect(windowOnError.mock.calls).toEqual([
    
  260.           [
    
  261.             // Reported due to guarded callback:
    
  262.             expect.objectContaining({
    
  263.               message: 'Boom',
    
  264.             }),
    
  265.           ],
    
  266.           [
    
  267.             // This is only duplicated with createRoot
    
  268.             // because it retries once with a sync render.
    
  269.             expect.objectContaining({
    
  270.               message: 'Boom',
    
  271.             }),
    
  272.           ],
    
  273.         ]);
    
  274.         expect(console.error.mock.calls).toEqual([
    
  275.           [
    
  276.             // Reported by jsdom due to the guarded callback:
    
  277.             expect.objectContaining({
    
  278.               detail: expect.objectContaining({
    
  279.                 message: 'Boom',
    
  280.               }),
    
  281.               type: 'unhandled exception',
    
  282.             }),
    
  283.           ],
    
  284.           [
    
  285.             // This is only duplicated with createRoot
    
  286.             // because it retries once with a sync render.
    
  287.             expect.objectContaining({
    
  288.               detail: expect.objectContaining({
    
  289.                 message: 'Boom',
    
  290.               }),
    
  291.               type: 'unhandled exception',
    
  292.             }),
    
  293.           ],
    
  294.           [
    
  295.             // Addendum by React:
    
  296.             expect.stringContaining(
    
  297.               'The above error occurred in the <Foo> component',
    
  298.             ),
    
  299.           ],
    
  300.         ]);
    
  301.       } else {
    
  302.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  303.         // so in production we don't see an error event.
    
  304.         expect(windowOnError.mock.calls).toEqual([]);
    
  305.         expect(console.error.mock.calls).toEqual([
    
  306.           [
    
  307.             // Reported by React with no extra message:
    
  308.             expect.objectContaining({
    
  309.               message: 'Boom',
    
  310.             }),
    
  311.           ],
    
  312.         ]);
    
  313.       }
    
  314. 
    
  315.       // Check next render doesn't throw.
    
  316.       windowOnError.mockReset();
    
  317.       console.error.mockReset();
    
  318.       await act(() => {
    
  319.         root.render(<NoError />);
    
  320.       });
    
  321.       expect(container.textContent).toBe('OK');
    
  322.       expect(windowOnError.mock.calls).toEqual([]);
    
  323.       if (__DEV__) {
    
  324.         expect(console.error.mock.calls).toEqual([]);
    
  325.       }
    
  326.     });
    
  327. 
    
  328.     it('logs layout effect errors without an error boundary', async () => {
    
  329.       spyOnDevAndProd(console, 'error');
    
  330. 
    
  331.       function Foo() {
    
  332.         React.useLayoutEffect(() => {
    
  333.           throw Error('Boom');
    
  334.         }, []);
    
  335.         return null;
    
  336.       }
    
  337. 
    
  338.       const root = ReactDOMClient.createRoot(container);
    
  339.       await act(async () => {
    
  340.         root.render(<Foo />);
    
  341.         await waitForThrow('Boom');
    
  342.       });
    
  343. 
    
  344.       if (__DEV__) {
    
  345.         expect(windowOnError.mock.calls).toEqual([
    
  346.           [
    
  347.             // Reported due to guarded callback:
    
  348.             expect.objectContaining({
    
  349.               message: 'Boom',
    
  350.             }),
    
  351.           ],
    
  352.         ]);
    
  353.         expect(console.error.mock.calls).toEqual([
    
  354.           [
    
  355.             // Reported due to the guarded callback:
    
  356.             expect.objectContaining({
    
  357.               detail: expect.objectContaining({
    
  358.                 message: 'Boom',
    
  359.               }),
    
  360.               type: 'unhandled exception',
    
  361.             }),
    
  362.           ],
    
  363.           [
    
  364.             // Addendum by React:
    
  365.             expect.stringContaining(
    
  366.               'The above error occurred in the <Foo> component',
    
  367.             ),
    
  368.           ],
    
  369.         ]);
    
  370.       } else {
    
  371.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  372.         // so in production we don't see an error event.
    
  373.         expect(windowOnError.mock.calls).toEqual([]);
    
  374.         expect(console.error.mock.calls).toEqual([
    
  375.           [
    
  376.             // Reported by React with no extra message:
    
  377.             expect.objectContaining({
    
  378.               message: 'Boom',
    
  379.             }),
    
  380.           ],
    
  381.         ]);
    
  382.       }
    
  383. 
    
  384.       // Check next render doesn't throw.
    
  385.       windowOnError.mockReset();
    
  386.       console.error.mockReset();
    
  387.       await act(() => {
    
  388.         root.render(<NoError />);
    
  389.       });
    
  390.       expect(container.textContent).toBe('OK');
    
  391.       expect(windowOnError.mock.calls).toEqual([]);
    
  392.       if (__DEV__) {
    
  393.         expect(console.error.mock.calls).toEqual([]);
    
  394.       }
    
  395.     });
    
  396. 
    
  397.     it('logs layout effect errors with an error boundary', async () => {
    
  398.       spyOnDevAndProd(console, 'error');
    
  399. 
    
  400.       function Foo() {
    
  401.         React.useLayoutEffect(() => {
    
  402.           throw Error('Boom');
    
  403.         }, []);
    
  404.         return null;
    
  405.       }
    
  406. 
    
  407.       const root = ReactDOMClient.createRoot(container);
    
  408.       await act(() => {
    
  409.         root.render(
    
  410.           <ErrorBoundary>
    
  411.             <Foo />
    
  412.           </ErrorBoundary>,
    
  413.         );
    
  414.       });
    
  415. 
    
  416.       if (__DEV__) {
    
  417.         expect(windowOnError.mock.calls).toEqual([
    
  418.           [
    
  419.             // Reported due to guarded callback:
    
  420.             expect.objectContaining({
    
  421.               message: 'Boom',
    
  422.             }),
    
  423.           ],
    
  424.         ]);
    
  425.         expect(console.error.mock.calls).toEqual([
    
  426.           [
    
  427.             // Reported by jsdom due to the guarded callback:
    
  428.             expect.objectContaining({
    
  429.               detail: expect.objectContaining({
    
  430.                 message: 'Boom',
    
  431.               }),
    
  432.               type: 'unhandled exception',
    
  433.             }),
    
  434.           ],
    
  435.           [
    
  436.             // Addendum by React:
    
  437.             expect.stringContaining(
    
  438.               'The above error occurred in the <Foo> component',
    
  439.             ),
    
  440.           ],
    
  441.         ]);
    
  442.       } else {
    
  443.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  444.         // so in production we don't see an error event.
    
  445.         expect(windowOnError.mock.calls).toEqual([]);
    
  446.         expect(console.error.mock.calls).toEqual([
    
  447.           [
    
  448.             // Reported by React with no extra message:
    
  449.             expect.objectContaining({
    
  450.               message: 'Boom',
    
  451.             }),
    
  452.           ],
    
  453.         ]);
    
  454.       }
    
  455. 
    
  456.       // Check next render doesn't throw.
    
  457.       windowOnError.mockReset();
    
  458.       console.error.mockReset();
    
  459.       await act(() => {
    
  460.         root.render(<NoError />);
    
  461.       });
    
  462.       expect(container.textContent).toBe('OK');
    
  463.       expect(windowOnError.mock.calls).toEqual([]);
    
  464.       if (__DEV__) {
    
  465.         expect(console.error.mock.calls).toEqual([]);
    
  466.       }
    
  467.     });
    
  468. 
    
  469.     it('logs passive effect errors without an error boundary', async () => {
    
  470.       spyOnDevAndProd(console, 'error');
    
  471. 
    
  472.       function Foo() {
    
  473.         React.useEffect(() => {
    
  474.           throw Error('Boom');
    
  475.         }, []);
    
  476.         return null;
    
  477.       }
    
  478. 
    
  479.       const root = ReactDOMClient.createRoot(container);
    
  480.       await act(async () => {
    
  481.         root.render(<Foo />);
    
  482.         await waitForThrow('Boom');
    
  483.       });
    
  484. 
    
  485.       if (__DEV__) {
    
  486.         expect(windowOnError.mock.calls).toEqual([
    
  487.           [
    
  488.             // Reported due to guarded callback:
    
  489.             expect.objectContaining({
    
  490.               message: 'Boom',
    
  491.             }),
    
  492.           ],
    
  493.         ]);
    
  494.         expect(console.error.mock.calls).toEqual([
    
  495.           [
    
  496.             // Reported due to the guarded callback:
    
  497.             expect.objectContaining({
    
  498.               detail: expect.objectContaining({
    
  499.                 message: 'Boom',
    
  500.               }),
    
  501.               type: 'unhandled exception',
    
  502.             }),
    
  503.           ],
    
  504.           [
    
  505.             // Addendum by React:
    
  506.             expect.stringContaining(
    
  507.               'The above error occurred in the <Foo> component',
    
  508.             ),
    
  509.           ],
    
  510.         ]);
    
  511.       } else {
    
  512.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  513.         // so in production we don't see an error event.
    
  514.         expect(windowOnError.mock.calls).toEqual([]);
    
  515.         expect(console.error.mock.calls).toEqual([
    
  516.           [
    
  517.             // Reported by React with no extra message:
    
  518.             expect.objectContaining({
    
  519.               message: 'Boom',
    
  520.             }),
    
  521.           ],
    
  522.         ]);
    
  523.       }
    
  524. 
    
  525.       // Check next render doesn't throw.
    
  526.       windowOnError.mockReset();
    
  527.       console.error.mockReset();
    
  528.       await act(() => {
    
  529.         root.render(<NoError />);
    
  530.       });
    
  531.       expect(container.textContent).toBe('OK');
    
  532.       expect(windowOnError.mock.calls).toEqual([]);
    
  533.       if (__DEV__) {
    
  534.         expect(console.error.mock.calls).toEqual([]);
    
  535.       }
    
  536.     });
    
  537. 
    
  538.     it('logs passive effect errors with an error boundary', async () => {
    
  539.       spyOnDevAndProd(console, 'error');
    
  540. 
    
  541.       function Foo() {
    
  542.         React.useEffect(() => {
    
  543.           throw Error('Boom');
    
  544.         }, []);
    
  545.         return null;
    
  546.       }
    
  547. 
    
  548.       const root = ReactDOMClient.createRoot(container);
    
  549.       await act(() => {
    
  550.         root.render(
    
  551.           <ErrorBoundary>
    
  552.             <Foo />
    
  553.           </ErrorBoundary>,
    
  554.         );
    
  555.       });
    
  556. 
    
  557.       if (__DEV__) {
    
  558.         expect(windowOnError.mock.calls).toEqual([
    
  559.           [
    
  560.             // Reported due to guarded callback:
    
  561.             expect.objectContaining({
    
  562.               message: 'Boom',
    
  563.             }),
    
  564.           ],
    
  565.         ]);
    
  566.         expect(console.error.mock.calls).toEqual([
    
  567.           [
    
  568.             // Reported by jsdom due to the guarded callback:
    
  569.             expect.objectContaining({
    
  570.               detail: expect.objectContaining({
    
  571.                 message: 'Boom',
    
  572.               }),
    
  573.               type: 'unhandled exception',
    
  574.             }),
    
  575.           ],
    
  576.           [
    
  577.             // Addendum by React:
    
  578.             expect.stringContaining(
    
  579.               'The above error occurred in the <Foo> component',
    
  580.             ),
    
  581.           ],
    
  582.         ]);
    
  583.       } else {
    
  584.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  585.         // so in production we don't see an error event.
    
  586.         expect(windowOnError.mock.calls).toEqual([]);
    
  587.         expect(console.error.mock.calls).toEqual([
    
  588.           [
    
  589.             // Reported by React with no extra message:
    
  590.             expect.objectContaining({
    
  591.               message: 'Boom',
    
  592.             }),
    
  593.           ],
    
  594.         ]);
    
  595.       }
    
  596. 
    
  597.       // Check next render doesn't throw.
    
  598.       windowOnError.mockReset();
    
  599.       console.error.mockReset();
    
  600.       await act(() => {
    
  601.         root.render(<NoError />);
    
  602.       });
    
  603.       expect(container.textContent).toBe('OK');
    
  604.       expect(windowOnError.mock.calls).toEqual([]);
    
  605.       if (__DEV__) {
    
  606.         expect(console.error.mock.calls).toEqual([]);
    
  607.       }
    
  608.     });
    
  609.   });
    
  610. 
    
  611.   describe('ReactDOM.render', () => {
    
  612.     it('logs errors during event handlers', async () => {
    
  613.       spyOnDevAndProd(console, 'error');
    
  614. 
    
  615.       function Foo() {
    
  616.         return (
    
  617.           <button
    
  618.             onClick={() => {
    
  619.               throw Error('Boom');
    
  620.             }}>
    
  621.             click me
    
  622.           </button>
    
  623.         );
    
  624.       }
    
  625. 
    
  626.       await act(() => {
    
  627.         ReactDOM.render(<Foo />, container);
    
  628.       });
    
  629. 
    
  630.       await act(() => {
    
  631.         container.firstChild.dispatchEvent(
    
  632.           new MouseEvent('click', {
    
  633.             bubbles: true,
    
  634.           }),
    
  635.         );
    
  636.       });
    
  637. 
    
  638.       if (__DEV__) {
    
  639.         expect(windowOnError.mock.calls).toEqual([
    
  640.           [
    
  641.             // Reported because we're in a browser click event:
    
  642.             expect.objectContaining({
    
  643.               message: 'Boom',
    
  644.             }),
    
  645.           ],
    
  646.           [
    
  647.             // This one is jsdom-only. Real browser deduplicates it.
    
  648.             // (In DEV, we have a nested event due to guarded callback.)
    
  649.             expect.objectContaining({
    
  650.               message: 'Boom',
    
  651.             }),
    
  652.           ],
    
  653.         ]);
    
  654.         expect(console.error.mock.calls).toEqual([
    
  655.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  656.           [
    
  657.             // Reported because we're in a browser click event:
    
  658.             expect.objectContaining({
    
  659.               detail: expect.objectContaining({
    
  660.                 message: 'Boom',
    
  661.               }),
    
  662.               type: 'unhandled exception',
    
  663.             }),
    
  664.           ],
    
  665.           [
    
  666.             // This one is jsdom-only. Real browser deduplicates it.
    
  667.             // (In DEV, we have a nested event due to guarded callback.)
    
  668.             expect.objectContaining({
    
  669.               detail: expect.objectContaining({
    
  670.                 message: 'Boom',
    
  671.               }),
    
  672.               type: 'unhandled exception',
    
  673.             }),
    
  674.           ],
    
  675.         ]);
    
  676.       } else {
    
  677.         expect(windowOnError.mock.calls).toEqual([
    
  678.           [
    
  679.             // Reported because we're in a browser click event:
    
  680.             expect.objectContaining({
    
  681.               message: 'Boom',
    
  682.             }),
    
  683.           ],
    
  684.         ]);
    
  685.         expect(console.error.mock.calls).toEqual([
    
  686.           [
    
  687.             // Reported because we're in a browser click event:
    
  688.             expect.objectContaining({
    
  689.               detail: expect.objectContaining({
    
  690.                 message: 'Boom',
    
  691.               }),
    
  692.               type: 'unhandled exception',
    
  693.             }),
    
  694.           ],
    
  695.         ]);
    
  696.       }
    
  697. 
    
  698.       // Check next render doesn't throw.
    
  699.       windowOnError.mockReset();
    
  700.       console.error.mockReset();
    
  701.       await act(() => {
    
  702.         ReactDOM.render(<NoError />, container);
    
  703.       });
    
  704.       expect(container.textContent).toBe('OK');
    
  705.       expect(windowOnError.mock.calls).toEqual([]);
    
  706.       if (__DEV__) {
    
  707.         expect(console.error.mock.calls).toEqual([
    
  708.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  709.         ]);
    
  710.       }
    
  711.     });
    
  712. 
    
  713.     it('logs render errors without an error boundary', async () => {
    
  714.       spyOnDevAndProd(console, 'error');
    
  715. 
    
  716.       function Foo() {
    
  717.         throw Error('Boom');
    
  718.       }
    
  719. 
    
  720.       expect(() => {
    
  721.         ReactDOM.render(<Foo />, container);
    
  722.       }).toThrow('Boom');
    
  723. 
    
  724.       if (__DEV__) {
    
  725.         expect(windowOnError.mock.calls).toEqual([
    
  726.           [
    
  727.             // Reported due to guarded callback:
    
  728.             expect.objectContaining({
    
  729.               message: 'Boom',
    
  730.             }),
    
  731.           ],
    
  732.         ]);
    
  733.         expect(console.error.mock.calls).toEqual([
    
  734.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  735.           [
    
  736.             // Reported due to the guarded callback:
    
  737.             expect.objectContaining({
    
  738.               detail: expect.objectContaining({
    
  739.                 message: 'Boom',
    
  740.               }),
    
  741.               type: 'unhandled exception',
    
  742.             }),
    
  743.           ],
    
  744.           [
    
  745.             // Addendum by React:
    
  746.             expect.stringContaining(
    
  747.               'The above error occurred in the <Foo> component',
    
  748.             ),
    
  749.           ],
    
  750.         ]);
    
  751.       } else {
    
  752.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  753.         // so in production we don't see an error event.
    
  754.         expect(windowOnError.mock.calls).toEqual([]);
    
  755.         expect(console.error.mock.calls).toEqual([
    
  756.           [
    
  757.             // Reported by React with no extra message:
    
  758.             expect.objectContaining({
    
  759.               message: 'Boom',
    
  760.             }),
    
  761.           ],
    
  762.         ]);
    
  763.       }
    
  764. 
    
  765.       // Check next render doesn't throw.
    
  766.       windowOnError.mockReset();
    
  767.       console.error.mockReset();
    
  768.       await act(() => {
    
  769.         ReactDOM.render(<NoError />, container);
    
  770.       });
    
  771.       expect(container.textContent).toBe('OK');
    
  772.       expect(windowOnError.mock.calls).toEqual([]);
    
  773.       if (__DEV__) {
    
  774.         expect(console.error.mock.calls).toEqual([
    
  775.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  776.         ]);
    
  777.       }
    
  778.     });
    
  779. 
    
  780.     it('logs render errors with an error boundary', async () => {
    
  781.       spyOnDevAndProd(console, 'error');
    
  782. 
    
  783.       function Foo() {
    
  784.         throw Error('Boom');
    
  785.       }
    
  786. 
    
  787.       await act(() => {
    
  788.         ReactDOM.render(
    
  789.           <ErrorBoundary>
    
  790.             <Foo />
    
  791.           </ErrorBoundary>,
    
  792.           container,
    
  793.         );
    
  794.       });
    
  795. 
    
  796.       if (__DEV__) {
    
  797.         expect(windowOnError.mock.calls).toEqual([
    
  798.           [
    
  799.             // Reported due to guarded callback:
    
  800.             expect.objectContaining({
    
  801.               message: 'Boom',
    
  802.             }),
    
  803.           ],
    
  804.         ]);
    
  805.         expect(console.error.mock.calls).toEqual([
    
  806.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  807.           [
    
  808.             // Reported by jsdom due to the guarded callback:
    
  809.             expect.objectContaining({
    
  810.               detail: expect.objectContaining({
    
  811.                 message: 'Boom',
    
  812.               }),
    
  813.               type: 'unhandled exception',
    
  814.             }),
    
  815.           ],
    
  816.           [
    
  817.             // Addendum by React:
    
  818.             expect.stringContaining(
    
  819.               'The above error occurred in the <Foo> component',
    
  820.             ),
    
  821.           ],
    
  822.         ]);
    
  823.       } else {
    
  824.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  825.         // so in production we don't see an error event.
    
  826.         expect(windowOnError.mock.calls).toEqual([]);
    
  827.         expect(console.error.mock.calls).toEqual([
    
  828.           [
    
  829.             // Reported by React with no extra message:
    
  830.             expect.objectContaining({
    
  831.               message: 'Boom',
    
  832.             }),
    
  833.           ],
    
  834.         ]);
    
  835.       }
    
  836. 
    
  837.       // Check next render doesn't throw.
    
  838.       windowOnError.mockReset();
    
  839.       console.error.mockReset();
    
  840.       await act(() => {
    
  841.         ReactDOM.render(<NoError />, container);
    
  842.       });
    
  843.       expect(container.textContent).toBe('OK');
    
  844.       expect(windowOnError.mock.calls).toEqual([]);
    
  845.       if (__DEV__) {
    
  846.         expect(console.error.mock.calls).toEqual([
    
  847.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  848.         ]);
    
  849.       }
    
  850.     });
    
  851. 
    
  852.     it('logs layout effect errors without an error boundary', async () => {
    
  853.       spyOnDevAndProd(console, 'error');
    
  854. 
    
  855.       function Foo() {
    
  856.         React.useLayoutEffect(() => {
    
  857.           throw Error('Boom');
    
  858.         }, []);
    
  859.         return null;
    
  860.       }
    
  861. 
    
  862.       expect(() => {
    
  863.         ReactDOM.render(<Foo />, container);
    
  864.       }).toThrow('Boom');
    
  865. 
    
  866.       if (__DEV__) {
    
  867.         expect(windowOnError.mock.calls).toEqual([
    
  868.           [
    
  869.             // Reported due to guarded callback:
    
  870.             expect.objectContaining({
    
  871.               message: 'Boom',
    
  872.             }),
    
  873.           ],
    
  874.         ]);
    
  875.         expect(console.error.mock.calls).toEqual([
    
  876.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  877.           [
    
  878.             // Reported due to the guarded callback:
    
  879.             expect.objectContaining({
    
  880.               detail: expect.objectContaining({
    
  881.                 message: 'Boom',
    
  882.               }),
    
  883.               type: 'unhandled exception',
    
  884.             }),
    
  885.           ],
    
  886.           [
    
  887.             // Addendum by React:
    
  888.             expect.stringContaining(
    
  889.               'The above error occurred in the <Foo> component',
    
  890.             ),
    
  891.           ],
    
  892.         ]);
    
  893.       } else {
    
  894.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  895.         // so in production we don't see an error event.
    
  896.         expect(windowOnError.mock.calls).toEqual([]);
    
  897.         expect(console.error.mock.calls).toEqual([
    
  898.           [
    
  899.             // Reported by React with no extra message:
    
  900.             expect.objectContaining({
    
  901.               message: 'Boom',
    
  902.             }),
    
  903.           ],
    
  904.         ]);
    
  905.       }
    
  906. 
    
  907.       // Check next render doesn't throw.
    
  908.       windowOnError.mockReset();
    
  909.       console.error.mockReset();
    
  910.       await act(() => {
    
  911.         ReactDOM.render(<NoError />, container);
    
  912.       });
    
  913.       expect(container.textContent).toBe('OK');
    
  914.       expect(windowOnError.mock.calls).toEqual([]);
    
  915.       if (__DEV__) {
    
  916.         expect(console.error.mock.calls).toEqual([
    
  917.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  918.         ]);
    
  919.       }
    
  920.     });
    
  921. 
    
  922.     it('logs layout effect errors with an error boundary', async () => {
    
  923.       spyOnDevAndProd(console, 'error');
    
  924. 
    
  925.       function Foo() {
    
  926.         React.useLayoutEffect(() => {
    
  927.           throw Error('Boom');
    
  928.         }, []);
    
  929.         return null;
    
  930.       }
    
  931. 
    
  932.       await act(() => {
    
  933.         ReactDOM.render(
    
  934.           <ErrorBoundary>
    
  935.             <Foo />
    
  936.           </ErrorBoundary>,
    
  937.           container,
    
  938.         );
    
  939.       });
    
  940. 
    
  941.       if (__DEV__) {
    
  942.         expect(windowOnError.mock.calls).toEqual([
    
  943.           [
    
  944.             // Reported due to guarded callback:
    
  945.             expect.objectContaining({
    
  946.               message: 'Boom',
    
  947.             }),
    
  948.           ],
    
  949.         ]);
    
  950.         expect(console.error.mock.calls).toEqual([
    
  951.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  952.           [
    
  953.             // Reported by jsdom due to the guarded callback:
    
  954.             expect.objectContaining({
    
  955.               detail: expect.objectContaining({
    
  956.                 message: 'Boom',
    
  957.               }),
    
  958.               type: 'unhandled exception',
    
  959.             }),
    
  960.           ],
    
  961.           [
    
  962.             // Addendum by React:
    
  963.             expect.stringContaining(
    
  964.               'The above error occurred in the <Foo> component',
    
  965.             ),
    
  966.           ],
    
  967.         ]);
    
  968.       } else {
    
  969.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  970.         // so in production we don't see an error event.
    
  971.         expect(windowOnError.mock.calls).toEqual([]);
    
  972.         expect(console.error.mock.calls).toEqual([
    
  973.           [
    
  974.             // Reported by React with no extra message:
    
  975.             expect.objectContaining({
    
  976.               message: 'Boom',
    
  977.             }),
    
  978.           ],
    
  979.         ]);
    
  980.       }
    
  981. 
    
  982.       // Check next render doesn't throw.
    
  983.       windowOnError.mockReset();
    
  984.       console.error.mockReset();
    
  985.       await act(() => {
    
  986.         ReactDOM.render(<NoError />, container);
    
  987.       });
    
  988.       expect(container.textContent).toBe('OK');
    
  989.       expect(windowOnError.mock.calls).toEqual([]);
    
  990.       if (__DEV__) {
    
  991.         expect(console.error.mock.calls).toEqual([
    
  992.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  993.         ]);
    
  994.       }
    
  995.     });
    
  996. 
    
  997.     it('logs passive effect errors without an error boundary', async () => {
    
  998.       spyOnDevAndProd(console, 'error');
    
  999. 
    
  1000.       function Foo() {
    
  1001.         React.useEffect(() => {
    
  1002.           throw Error('Boom');
    
  1003.         }, []);
    
  1004.         return null;
    
  1005.       }
    
  1006. 
    
  1007.       await act(async () => {
    
  1008.         ReactDOM.render(<Foo />, container);
    
  1009.         await waitForThrow('Boom');
    
  1010.       });
    
  1011. 
    
  1012.       if (__DEV__) {
    
  1013.         expect(windowOnError.mock.calls).toEqual([
    
  1014.           [
    
  1015.             // Reported due to guarded callback:
    
  1016.             expect.objectContaining({
    
  1017.               message: 'Boom',
    
  1018.             }),
    
  1019.           ],
    
  1020.         ]);
    
  1021.         expect(console.error.mock.calls).toEqual([
    
  1022.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  1023.           [
    
  1024.             // Reported due to the guarded callback:
    
  1025.             expect.objectContaining({
    
  1026.               detail: expect.objectContaining({
    
  1027.                 message: 'Boom',
    
  1028.               }),
    
  1029.               type: 'unhandled exception',
    
  1030.             }),
    
  1031.           ],
    
  1032.           [
    
  1033.             // Addendum by React:
    
  1034.             expect.stringContaining(
    
  1035.               'The above error occurred in the <Foo> component',
    
  1036.             ),
    
  1037.           ],
    
  1038.         ]);
    
  1039.       } else {
    
  1040.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  1041.         // so in production we don't see an error event.
    
  1042.         expect(windowOnError.mock.calls).toEqual([]);
    
  1043.         expect(console.error.mock.calls).toEqual([
    
  1044.           [
    
  1045.             // Reported by React with no extra message:
    
  1046.             expect.objectContaining({
    
  1047.               message: 'Boom',
    
  1048.             }),
    
  1049.           ],
    
  1050.         ]);
    
  1051.       }
    
  1052. 
    
  1053.       // Check next render doesn't throw.
    
  1054.       windowOnError.mockReset();
    
  1055.       console.error.mockReset();
    
  1056.       await act(() => {
    
  1057.         ReactDOM.render(<NoError />, container);
    
  1058.       });
    
  1059.       expect(container.textContent).toBe('OK');
    
  1060.       expect(windowOnError.mock.calls).toEqual([]);
    
  1061.       if (__DEV__) {
    
  1062.         expect(console.error.mock.calls).toEqual([
    
  1063.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  1064.         ]);
    
  1065.       }
    
  1066.     });
    
  1067. 
    
  1068.     it('logs passive effect errors with an error boundary', async () => {
    
  1069.       spyOnDevAndProd(console, 'error');
    
  1070. 
    
  1071.       function Foo() {
    
  1072.         React.useEffect(() => {
    
  1073.           throw Error('Boom');
    
  1074.         }, []);
    
  1075.         return null;
    
  1076.       }
    
  1077. 
    
  1078.       await act(() => {
    
  1079.         ReactDOM.render(
    
  1080.           <ErrorBoundary>
    
  1081.             <Foo />
    
  1082.           </ErrorBoundary>,
    
  1083.           container,
    
  1084.         );
    
  1085.       });
    
  1086. 
    
  1087.       if (__DEV__) {
    
  1088.         // Reported due to guarded callback:
    
  1089.         expect(windowOnError.mock.calls).toEqual([
    
  1090.           [
    
  1091.             expect.objectContaining({
    
  1092.               message: 'Boom',
    
  1093.             }),
    
  1094.           ],
    
  1095.         ]);
    
  1096.         expect(console.error.mock.calls).toEqual([
    
  1097.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  1098.           [
    
  1099.             // Reported by jsdom due to the guarded callback:
    
  1100.             expect.objectContaining({
    
  1101.               detail: expect.objectContaining({
    
  1102.                 message: 'Boom',
    
  1103.               }),
    
  1104.               type: 'unhandled exception',
    
  1105.             }),
    
  1106.           ],
    
  1107.           [
    
  1108.             // Addendum by React:
    
  1109.             expect.stringContaining(
    
  1110.               'The above error occurred in the <Foo> component',
    
  1111.             ),
    
  1112.           ],
    
  1113.         ]);
    
  1114.       } else {
    
  1115.         // The top-level error was caught with try/catch, and there's no guarded callback,
    
  1116.         // so in production we don't see an error event.
    
  1117.         expect(windowOnError.mock.calls).toEqual([]);
    
  1118.         expect(console.error.mock.calls).toEqual([
    
  1119.           [
    
  1120.             // Reported by React with no extra message:
    
  1121.             expect.objectContaining({
    
  1122.               message: 'Boom',
    
  1123.             }),
    
  1124.           ],
    
  1125.         ]);
    
  1126.       }
    
  1127. 
    
  1128.       // Check next render doesn't throw.
    
  1129.       windowOnError.mockReset();
    
  1130.       console.error.mockReset();
    
  1131.       await act(() => {
    
  1132.         ReactDOM.render(<NoError />, container);
    
  1133.       });
    
  1134.       expect(container.textContent).toBe('OK');
    
  1135.       expect(windowOnError.mock.calls).toEqual([]);
    
  1136.       if (__DEV__) {
    
  1137.         expect(console.error.mock.calls).toEqual([
    
  1138.           [expect.stringContaining('ReactDOM.render is no longer supported')],
    
  1139.         ]);
    
  1140.       }
    
  1141.     });
    
  1142.   });
    
  1143. });