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