diff --git a/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js new file mode 100644 index 0000000000000..bbaffe5489b52 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js @@ -0,0 +1,1120 @@ +/** + * Copyright (c) Facebook, Inc. and its 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; + + beforeEach(() => { + jest.resetModules(); + act = require('jest-react').act; + React = require('react'); + ReactDOM = require('react-dom'); + + 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); + }); + + describe('ReactDOM.createRoot', () => { + it('logs errors during event handlers', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + return ( + + ); + } + + const root = ReactDOM.createRoot(container); + act(() => { + root.render(); + }); + + 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported because we're in a browser click event: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + 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.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } else { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported because we're in a browser click event: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported because we're in a browser click event: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + + it('logs render errors without an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + throw Error('Boom'); + } + + const root = ReactDOM.createRoot(container); + expect(() => { + act(() => { + root.render(); + }); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // TODO: This is duplicated only with createRoot. Why? + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // TODO: This is duplicated only with createRoot. Why? + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + + it('logs render errors with an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + throw Error('Boom'); + } + + const root = ReactDOM.createRoot(container); + act(() => { + root.render( + + + , + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // TODO: This is duplicated only with createRoot. Why? + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by jsdom due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // Addendum by React: + expect.stringContaining( + 'The above error occurred in the component', + ), + ], + [ + // TODO: This is duplicated only with createRoot. Why? + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // TODO: This is duplicated only with createRoot. Why? + 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // TODO: This is duplicated only with createRoot. Why? + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs layout effect errors without an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useLayoutEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + const root = ReactDOM.createRoot(container); + expect(() => { + act(() => { + root.render(); + }); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs layout effect errors with an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useLayoutEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + const root = ReactDOM.createRoot(container); + act(() => { + root.render( + + + , + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by jsdom due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs passive effect errors without an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + const root = ReactDOM.createRoot(container); + expect(() => { + act(() => { + root.render(); + }); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs passive effect errors with an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + const root = ReactDOM.createRoot(container); + act(() => { + root.render( + + + , + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by jsdom due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + root.render(); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([]); + } + }); + }); + + describe('ReactDOM.render', () => { + it('logs errors during event handlers', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + return ( + + ); + } + + act(() => { + ReactDOM.render(, container); + }); + + 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.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported because we're in a browser click event: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + 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.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } else { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported because we're in a browser click event: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [ + // Reported because we're in a browser click event: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs render errors without an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + throw Error('Boom'); + } + + expect(() => { + act(() => { + ReactDOM.render(, container); + }); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + it('logs render errors with an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + throw Error('Boom'); + } + + act(() => { + ReactDOM.render( + + + , + container, + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported by jsdom due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs layout effect errors without an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useLayoutEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + expect(() => { + act(() => { + ReactDOM.render(, container); + }); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs layout effect errors with an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useLayoutEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + act(() => { + ReactDOM.render( + + + , + container, + ); + }); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported by jsdom due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs passive effect errors without an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + expect(() => { + act(() => { + ReactDOM.render(, container); + }); + }).toThrow('Boom'); + + if (__DEV__) { + expect(windowOnError.mock.calls).toEqual([ + [ + // Reported due to guarded callback: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + + // TODO: this is broken due to https://github.com/facebook/react/issues/21712. + xit('logs passive effect errors with an error boundary', () => { + spyOnDevAndProd(console, 'error'); + + function Foo() { + React.useEffect(() => { + throw Error('Boom'); + }, []); + return null; + } + + act(() => { + ReactDOM.render( + + + , + container, + ); + }); + + if (__DEV__) { + // Reported due to guarded callback: + expect(windowOnError.mock.calls).toEqual([ + [ + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + [ + // Reported by jsdom due to the guarded callback: + expect.stringContaining('Error: Uncaught [Error: Boom]'), + expect.objectContaining({ + message: 'Boom', + }), + ], + [ + // 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.calls.all().map(c => c.args)).toEqual([ + [ + // Reported by React with no extra message: + expect.objectContaining({ + message: 'Boom', + }), + ], + ]); + } + + // Check next render doesn't throw. + windowOnError.mockReset(); + console.error.calls.reset(); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe('OK'); + expect(windowOnError.mock.calls).toEqual([]); + if (__DEV__) { + expect(console.error.calls.all().map(c => c.args)).toEqual([ + [expect.stringContaining('ReactDOM.render is no longer supported')], + ]); + } + }); + }); +});