diff --git a/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js index 9ce6d9676141a..ad5826fe393ea 100644 --- a/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js @@ -10,7 +10,6 @@ describe('ReactDOMConsoleErrorReporting', () => { let act; let React; - let ReactDOM; let ReactDOMClient; let ErrorBoundary; @@ -23,7 +22,6 @@ describe('ReactDOMConsoleErrorReporting', () => { jest.resetModules(); act = require('internal-test-utils').act; React = require('react'); - ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); const InternalTestUtils = require('internal-test-utils'); @@ -607,537 +605,4 @@ describe('ReactDOMConsoleErrorReporting', () => { } }); }); - - describe('ReactDOM.render', () => { - it('logs errors during event handlers', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - return ( - - ); - } - - await act(() => { - ReactDOM.render(, container); - }); - - await act(() => { - container.firstChild.dispatchEvent( - new MouseEvent('click', { - bubbles: true, - }), - ); - }); - - if (__DEV__) { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported because we're in a browser click event: - expect.objectContaining({ - message: 'Boom', - }), - ], - [ - // This one is jsdom-only. Real browser deduplicates it. - // (In DEV, we have a nested event due to guarded callback.) - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported because we're in a browser click event: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // This one is jsdom-only. Real browser deduplicates it. - // (In DEV, we have a nested event due to guarded callback.) - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - ]); - } else { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported because we're in a browser click event: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported because we're in a browser click event: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - - it('logs render errors without an error boundary', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - throw Error('Boom'); - } - - expect(() => { - ReactDOM.render(, container); - }).toThrow('Boom'); - - if (__DEV__) { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported due to guarded callback: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported due to the guarded callback: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // Addendum by React: - expect.stringContaining( - 'The above error occurred in the component', - ), - ], - ]); - } else { - // The top-level error was caught with try/catch, and there's no guarded callback, - // so in production we don't see an error event. - expect(windowOnError.mock.calls).toEqual([]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported by React with no extra message: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - - it('logs render errors with an error boundary', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - throw Error('Boom'); - } - - await act(() => { - ReactDOM.render( - - - , - container, - ); - }); - - if (__DEV__) { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported due to guarded callback: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported by jsdom due to the guarded callback: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // Addendum by React: - expect.stringContaining( - 'The above error occurred in the component', - ), - ], - ]); - } else { - // The top-level error was caught with try/catch, and there's no guarded callback, - // so in production we don't see an error event. - expect(windowOnError.mock.calls).toEqual([]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported by React with no extra message: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - - it('logs layout effect errors without an error boundary', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - React.useLayoutEffect(() => { - throw Error('Boom'); - }, []); - return null; - } - - expect(() => { - ReactDOM.render(, container); - }).toThrow('Boom'); - - if (__DEV__) { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported due to guarded callback: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported due to the guarded callback: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // Addendum by React: - expect.stringContaining( - 'The above error occurred in the component', - ), - ], - ]); - } else { - // The top-level error was caught with try/catch, and there's no guarded callback, - // so in production we don't see an error event. - expect(windowOnError.mock.calls).toEqual([]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported by React with no extra message: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - - it('logs layout effect errors with an error boundary', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - React.useLayoutEffect(() => { - throw Error('Boom'); - }, []); - return null; - } - - await act(() => { - ReactDOM.render( - - - , - container, - ); - }); - - if (__DEV__) { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported due to guarded callback: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported by jsdom due to the guarded callback: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // Addendum by React: - expect.stringContaining( - 'The above error occurred in the component', - ), - ], - ]); - } else { - // The top-level error was caught with try/catch, and there's no guarded callback, - // so in production we don't see an error event. - expect(windowOnError.mock.calls).toEqual([]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported by React with no extra message: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - - it('logs passive effect errors without an error boundary', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - React.useEffect(() => { - throw Error('Boom'); - }, []); - return null; - } - - await act(async () => { - ReactDOM.render(, container); - await waitForThrow('Boom'); - }); - - if (__DEV__) { - expect(windowOnError.mock.calls).toEqual([ - [ - // Reported due to guarded callback: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported due to the guarded callback: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // Addendum by React: - expect.stringContaining( - 'The above error occurred in the component', - ), - ], - ]); - } else { - // The top-level error was caught with try/catch, and there's no guarded callback, - // so in production we don't see an error event. - expect(windowOnError.mock.calls).toEqual([]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported by React with no extra message: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - - it('logs passive effect errors with an error boundary', async () => { - spyOnDevAndProd(console, 'error'); - - function Foo() { - React.useEffect(() => { - throw Error('Boom'); - }, []); - return null; - } - - await act(() => { - ReactDOM.render( - - - , - container, - ); - }); - - if (__DEV__) { - // Reported due to guarded callback: - expect(windowOnError.mock.calls).toEqual([ - [ - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - [ - // Reported by jsdom due to the guarded callback: - expect.objectContaining({ - detail: expect.objectContaining({ - message: 'Boom', - }), - type: 'unhandled exception', - }), - ], - [ - // Addendum by React: - expect.stringContaining( - 'The above error occurred in the component', - ), - ], - ]); - } else { - // The top-level error was caught with try/catch, and there's no guarded callback, - // so in production we don't see an error event. - expect(windowOnError.mock.calls).toEqual([]); - expect(console.error.mock.calls).toEqual([ - [ - // Reported by React with no extra message: - expect.objectContaining({ - message: 'Boom', - }), - ], - ]); - } - - // Check next render doesn't throw. - windowOnError.mockReset(); - console.error.mockReset(); - await act(() => { - ReactDOM.render(, container); - }); - expect(container.textContent).toBe('OK'); - expect(windowOnError.mock.calls).toEqual([]); - if (__DEV__) { - expect(console.error.mock.calls).toEqual([ - [expect.stringContaining('ReactDOM.render is no longer supported')], - ]); - } - }); - }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReportingLegacy-test.js b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReportingLegacy-test.js new file mode 100644 index 0000000000000..bb1e9c83ecf65 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReportingLegacy-test.js @@ -0,0 +1,589 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +describe('ReactDOMConsoleErrorReporting', () => { + let act; + let React; + let ReactDOM; + + let ErrorBoundary; + let NoError; + let container; + let windowOnError; + let waitForThrow; + + beforeEach(() => { + jest.resetModules(); + act = require('internal-test-utils').act; + React = require('react'); + ReactDOM = require('react-dom'); + + const InternalTestUtils = require('internal-test-utils'); + waitForThrow = InternalTestUtils.waitForThrow; + + ErrorBoundary = class extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; + } + render() { + if (this.state.error) { + return

Caught: {this.state.error.message}

; + } + return this.props.children; + } + }; + NoError = function () { + return

OK

; + }; + container = document.createElement('div'); + document.body.appendChild(container); + windowOnError = jest.fn(); + window.addEventListener('error', windowOnError); + }); + + afterEach(() => { + document.body.removeChild(container); + window.removeEventListener('error', windowOnError); + jest.restoreAllMocks(); + }); + + describe('ReactDOM.render', () => { + it('logs errors during event handlers', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + return ( + + ); + } + + await act(() => { + ReactDOM.render(, container); + }); + + await act(() => { + container.firstChild.dispatchEvent( + new MouseEvent('click', { + bubbles: true, + }), + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported because we're in a browser click event: + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // This one is jsdom-only. Real browser deduplicates it. + // (In DEV, we have a nested event due to guarded callback.) + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported because we're in a browser click event: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // This one is jsdom-only. Real browser deduplicates it. + // (In DEV, we have a nested event due to guarded callback.) + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + ]); + } else { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported because we're in a browser click event: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported because we're in a browser click event: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs render errors without an error boundary', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + throw Error('Boom'); + } + + expect(() => { + ReactDOM.render(, container); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported due to the guarded callback: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + ]); + } else { + // The top-level error was caught with try/catch, and there's no guarded callback, + // so in production we don't see an error event. + expect(windowOnError.mock.calls).toEqual([]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs render errors with an error boundary', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + throw Error('Boom'); + } + + await act(() => { + ReactDOM.render( + + + , + container, + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported by jsdom due to the guarded callback: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + ]); + } else { + // The top-level error was caught with try/catch, and there's no guarded callback, + // so in production we don't see an error event. + expect(windowOnError.mock.calls).toEqual([]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs layout effect errors without an error boundary', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useLayoutEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + expect(() => { + ReactDOM.render(, container); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported due to the guarded callback: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + ]); + } else { + // The top-level error was caught with try/catch, and there's no guarded callback, + // so in production we don't see an error event. + expect(windowOnError.mock.calls).toEqual([]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs layout effect errors with an error boundary', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useLayoutEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + await act(() => { + ReactDOM.render( + + + , + container, + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported by jsdom due to the guarded callback: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + ]); + } else { + // The top-level error was caught with try/catch, and there's no guarded callback, + // so in production we don't see an error event. + expect(windowOnError.mock.calls).toEqual([]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs passive effect errors without an error boundary', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + await act(async () => { + ReactDOM.render(, container); + await waitForThrow('Boom'); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported due to the guarded callback: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + ]); + } else { + // The top-level error was caught with try/catch, and there's no guarded callback, + // so in production we don't see an error event. + expect(windowOnError.mock.calls).toEqual([]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs passive effect errors with an error boundary', async () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + await act(() => { + ReactDOM.render( + + + , + container, + ); + }); + + if (__DEV__) { + // Reported due to guarded callback: + expect(windowOnError.mock.calls).toEqual([ + [ + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported by jsdom due to the guarded callback: + expect.objectContaining({ + detail: expect.objectContaining({ + message: 'Boom', + }), + type: 'unhandled exception', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + ]); + } else { + // The top-level error was caught with try/catch, and there's no guarded callback, + // so in production we don't see an error event. + expect(windowOnError.mock.calls).toEqual([]); + expect(console.error.mock.calls).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.mockReset(); + await act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.mock.calls).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + }); +});