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')],
+ ]);
+ }
+ });
+ });
+});