From 48c14b779134b270a054db99352c1a1a3643511b Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 26 Jan 2024 16:46:03 -0800 Subject: [PATCH 1/7] Convert ReactErrorBoundaries-test.internal.js to createRoot --- .../ReactErrorBoundaries-test.internal.js | 1600 +++++++++++------ 1 file changed, 1012 insertions(+), 588 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 040234f5c8059..de2bbff875741 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -12,6 +12,7 @@ let PropTypes; let React; let ReactDOM; +let ReactDOMClient; let act; let ReactFeatureFlags; let Scheduler; @@ -44,6 +45,7 @@ describe('ReactErrorBoundaries', () => { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; ReactDOM = require('react-dom'); + ReactDOMClient = require('react-dom/client'); React = require('react'); act = require('internal-test-utils').act; Scheduler = require('scheduler'); @@ -576,109 +578,160 @@ describe('ReactErrorBoundaries', () => { }; }); - it('does not swallow exceptions on mounting without boundaries', () => { + it('does not swallow exceptions on mounting without boundaries', async () => { let container = document.createElement('div'); - expect(() => { - ReactDOM.render(, container); + let root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(async () => { + root.render(, container); + }); }).toThrow('Hello'); container = document.createElement('div'); - expect(() => { - ReactDOM.render(, container); + root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(async () => { + root.render(); + }); }).toThrow('Hello'); container = document.createElement('div'); - expect(() => { - ReactDOM.render(, container); + root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(async () => { + root.render(); + }); }).toThrow('Hello'); }); - it('does not swallow exceptions on updating without boundaries', () => { + it('does not swallow exceptions on updating without boundaries', async () => { let container = document.createElement('div'); - ReactDOM.render(, container); - expect(() => { - ReactDOM.render(, container); + let root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); + await expect(async () => { + await act(async () => { + root.render(); + }); }).toThrow('Hello'); container = document.createElement('div'); - ReactDOM.render(, container); - expect(() => { - ReactDOM.render(, container); + root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); + await expect(async () => { + await act(async () => { + root.render(); + }); }).toThrow('Hello'); container = document.createElement('div'); - ReactDOM.render(, container); - expect(() => { - ReactDOM.render(, container); + root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); + await expect(async () => { + await act(async () => { + root.render(); + }); }).toThrow('Hello'); }); - it('does not swallow exceptions on unmounting without boundaries', () => { + it('does not swallow exceptions on unmounting without boundaries', async () => { const container = document.createElement('div'); - ReactDOM.render(, container); - expect(() => { - ReactDOM.unmountComponentAtNode(container); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); + await expect(async () => { + await act(async () => { + root.unmount(); + }); }).toThrow('Hello'); }); - it('prevents errors from leaking into other roots', () => { + it('prevents errors from leaking into other roots', async () => { const container1 = document.createElement('div'); + const root1 = ReactDOMClient.createRoot(container1); const container2 = document.createElement('div'); + const root2 = ReactDOMClient.createRoot(container2); const container3 = document.createElement('div'); + const root3 = ReactDOMClient.createRoot(container3); - ReactDOM.render(Before 1, container1); - expect(() => { - ReactDOM.render(, container2); + await act(async () => { + root1.render(Before 1); + }); + await expect(async () => { + await act(async () => { + root2.render(); + }); }).toThrow('Hello'); - ReactDOM.render( - - - , - container3, - ); + await act(async () => { + root3.render( + + + , + ); + }); expect(container1.firstChild.textContent).toBe('Before 1'); expect(container2.firstChild).toBe(null); expect(container3.firstChild.textContent).toBe('Caught an error: Hello.'); - ReactDOM.render(After 1, container1); - ReactDOM.render(After 2, container2); - ReactDOM.render( - After 3, - container3, - ); + await act(async () => { + root1.render(After 1); + }); + await act(async () => { + root2.render(After 2); + }); + await act(async () => { + root3.render(After 3); + }); expect(container1.firstChild.textContent).toBe('After 1'); expect(container2.firstChild.textContent).toBe('After 2'); expect(container3.firstChild.textContent).toBe('After 3'); - ReactDOM.unmountComponentAtNode(container1); - ReactDOM.unmountComponentAtNode(container2); - ReactDOM.unmountComponentAtNode(container3); + await act(async () => { + root1.unmount(); + }); + await act(async () => { + root2.unmount(); + }); + await act(async () => { + root3.unmount(); + }); expect(container1.firstChild).toBe(null); expect(container2.firstChild).toBe(null); expect(container3.firstChild).toBe(null); }); - it('logs a single error when using error boundary', () => { + it('logs a single error when using error boundary', async () => { const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); spyOnDev(console, 'error'); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); if (__DEV__) { expect(console.error).toHaveBeenCalledTimes(2); expect(console.error.mock.calls[0][0]).toContain( - 'ReactDOM.render is no longer supported', - ); - expect(console.error.mock.calls[1][0]).toContain( 'The above error occurred in the component:', ); } expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -689,21 +742,34 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('renders an error state if child throws in render', () => { + it('renders an error state if child throws in render', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -716,21 +782,34 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('renders an error state if child throws in constructor', () => { + it('renders an error state if child throws in constructor', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -741,21 +820,32 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenConstructor constructor [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('renders an error state if child throws in componentWillMount', () => { + it('renders an error state if child throws in componentWillMount', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -767,15 +857,25 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenComponentWillMount constructor', + 'BrokenComponentWillMount componentWillMount [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); // @gate !disableLegacyContext || !__DEV__ - it('renders an error state if context provider throws in componentWillMount', () => { + it('renders an error state if context provider throws in componentWillMount', async () => { class BrokenComponentWillMountWithContext extends React.Component { static childContextTypes = {foo: PropTypes.number}; getChildContext() { @@ -790,18 +890,20 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); // @gate !disableModulePatternComponents // @gate !disableLegacyContext - it('renders an error state if module-style context provider throws in componentWillMount', () => { + it('renders an error state if module-style context provider throws in componentWillMount', async () => { function BrokenComponentWillMountWithContext() { return { getChildContext() { @@ -820,13 +922,16 @@ describe('ReactErrorBoundaries', () => { }; const container = document.createElement('div'); - expect(() => - ReactDOM.render( - - - , - container, - ), + const root = ReactDOMClient.createRoot(container); + await expect( + async () => + await act(async () => { + root.render( + + + , + ); + }), ).toErrorDev( 'Warning: The component appears to be a function component that ' + 'returns a class instance. ' + @@ -839,19 +944,33 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); - it('mounts the error message if mounting fails', () => { + it('mounts the error message if mounting fails', async () => { function renderError(error) { return ; } const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); assertLog([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', + 'ErrorMessage constructor', + 'ErrorMessage componentWillMount', + 'ErrorMessage render', 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -868,23 +987,27 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog([ 'ErrorBoundary componentWillUnmount', 'ErrorMessage componentWillUnmount', ]); }); - it('propagates errors on retry on mounting', () => { + it('propagates errors on retry on mounting', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -907,21 +1030,43 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'RetryErrorBoundary constructor', + 'RetryErrorBoundary componentWillMount', + 'RetryErrorBoundary render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'RetryErrorBoundary static getDerivedStateFromError [!]', + 'RetryErrorBoundary componentWillMount', + 'RetryErrorBoundary render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('propagates errors inside boundary during componentWillMount', () => { + it('propagates errors inside boundary during componentWillMount', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -933,23 +1078,35 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenComponentWillMountErrorBoundary constructor', + 'BrokenComponentWillMountErrorBoundary componentWillMount [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('propagates errors inside boundary while rendering error state', () => { + it('propagates errors inside boundary while rendering error state', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -969,23 +1126,42 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenRenderErrorBoundary constructor', + 'BrokenRenderErrorBoundary componentWillMount', + 'BrokenRenderErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'BrokenRenderErrorBoundary static getDerivedStateFromError', + 'BrokenRenderErrorBoundary componentWillMount', + 'BrokenRenderErrorBoundary render error [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('does not call componentWillUnmount when aborting initial mount', () => { + it('does not call componentWillUnmount when aborting initial mount', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -1004,14 +1180,28 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'Normal constructor', + 'Normal componentWillMount', + 'Normal render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('resets callback refs if mounting aborts', () => { + it('resets callback refs if mounting aborts', async () => { function childRef(x) { Scheduler.log('Child ref is set to ' + x); } @@ -1020,13 +1210,15 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - ReactDOM.render( - -
- - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + +
+ + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -1039,29 +1231,42 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'Error message ref is set to [object HTMLDivElement]', 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog([ 'ErrorBoundary componentWillUnmount', 'Error message ref is set to null', ]); }); - it('resets object refs if mounting aborts', () => { + it('resets object refs if mounting aborts', async () => { const childRef = React.createRef(); const errorMessageRef = React.createRef(); const container = document.createElement('div'); - ReactDOM.render( - -
- - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + +
+ + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -1074,25 +1279,38 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render error', 'ErrorBoundary componentDidMount', ]); expect(errorMessageRef.current.toString()).toEqual( '[object HTMLDivElement]', ); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); expect(errorMessageRef.current).toEqual(null); }); - it('successfully mounts if no error occurs', () => { + it('successfully mounts if no error occurs', async () => { const container = document.createElement('div'); - ReactDOM.render( - -
Mounted successfully.
-
, - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + +
Mounted successfully.
+
, + ); + }); expect(container.firstChild.textContent).toBe('Mounted successfully.'); assertLog([ 'ErrorBoundary constructor', @@ -1101,27 +1319,32 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches if child throws in constructor during update', () => { + it('catches if child throws in constructor during update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - - , - container, - ); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1141,32 +1364,50 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'Normal componentWillReceiveProps', + 'Normal componentWillUpdate', + 'Normal render', + 'Normal2 constructor', + 'Normal2 componentWillMount', + 'Normal2 render', + 'BrokenConstructor constructor [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', 'Normal componentWillUnmount', 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches if child throws in componentWillMount during update', () => { + it('catches if child throws in componentWillMount during update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - - , - container, - ); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1187,32 +1428,51 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'Normal componentWillReceiveProps', + 'Normal componentWillUpdate', + 'Normal render', + 'Normal2 constructor', + 'Normal2 componentWillMount', + 'Normal2 render', + 'BrokenComponentWillMount constructor', + 'BrokenComponentWillMount componentWillMount [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', 'Normal componentWillUnmount', 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches if child throws in componentWillReceiveProps during update', () => { + it('catches if child throws in componentWillReceiveProps during update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - , - container, - ); + await act(async () => { + root.render( + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1228,33 +1488,48 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'Normal componentWillReceiveProps', + 'Normal componentWillUpdate', + 'Normal render', + 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', 'Normal componentWillUnmount', 'BrokenComponentWillReceiveProps componentWillUnmount', 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches if child throws in componentWillUpdate during update', () => { + it('catches if child throws in componentWillUpdate during update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - , - container, - ); + await act(async () => { + root.render( + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1270,33 +1545,49 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'Normal componentWillReceiveProps', + 'Normal componentWillUpdate', + 'Normal render', + 'BrokenComponentWillUpdate componentWillReceiveProps', + 'BrokenComponentWillUpdate componentWillUpdate [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', 'Normal componentWillUnmount', 'BrokenComponentWillUpdate componentWillUnmount', 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches if child throws in render during update', () => { + it('catches if child throws in render during update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - - , - container, - ); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1317,15 +1608,32 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'Normal componentWillReceiveProps', + 'Normal componentWillUpdate', + 'Normal render', + 'Normal2 constructor', + 'Normal2 componentWillMount', + 'Normal2 render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', 'Normal componentWillUnmount', 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('keeps refs up-to-date during updates', () => { + it('keeps refs up-to-date during updates', async () => { function child1Ref(x) { Scheduler.log('Child1 ref is set to ' + x); } @@ -1337,12 +1645,14 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - ReactDOM.render( - -
- , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + +
+ , + ); + }); assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', @@ -1351,14 +1661,15 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - ReactDOM.render( - -
-
- - , - container, - ); + await act(async () => { + root.render( + +
+
+ + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1372,6 +1683,15 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', // Update Child1 ref since Child1 has been unmounted // Child2 ref is never set because its mounting aborted 'Child1 ref is set to null', @@ -1379,31 +1699,36 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog([ 'ErrorBoundary componentWillUnmount', 'Error message ref is set to null', ]); }); - it('recovers from componentWillUnmount errors on update', () => { + it('recovers from componentWillUnmount errors on update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1437,31 +1762,36 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('recovers from nested componentWillUnmount errors on update', () => { + it('recovers from nested componentWillUnmount errors on update', async () => { const container = document.createElement('div'); - ReactDOM.render( - - + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + - - - , - container, - ); + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - - , - container, - ); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1496,11 +1826,13 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('picks the right boundary when handling unmounting errors', () => { + it('picks the right boundary when handling unmounting errors', async () => { function renderInnerError(error) { return
Caught an inner error: {error.message}.
; } @@ -1509,31 +1841,34 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - ReactDOM.render( - + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( - - - , - container, - ); + logName="OuterErrorBoundary" + renderError={renderOuterError}> + + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - + await act(async () => { + root.render( - , - container, - ); + logName="OuterErrorBoundary" + renderError={renderOuterError}> + + , + ); + }); expect(container.textContent).toBe('Caught an inner error: Hello.'); assertLog([ // Update outer boundary @@ -1559,39 +1894,45 @@ describe('ReactErrorBoundaries', () => { 'InnerErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog([ 'OuterErrorBoundary componentWillUnmount', 'InnerErrorBoundary componentWillUnmount', ]); }); - it('can recover from error state', () => { + it('can recover from error state', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); // Error boundary doesn't retry by itself: expect(container.textContent).toBe('Caught an error: Hello.'); // Force the success path: Scheduler.unstable_clearLog(); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); expect(container.textContent).not.toContain('Caught an error'); assertLog([ 'ErrorBoundary componentWillReceiveProps', @@ -1606,75 +1947,96 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog([ 'ErrorBoundary componentWillUnmount', 'Normal componentWillUnmount', ]); }); - it('can update multiple times in error state', () => { + it('can update multiple times in error state', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); - ReactDOM.render(
Other screen
, container); + await act(async () => { + root.render(
Other screen
); + }); expect(container.textContent).toBe('Other screen'); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); }); - it("doesn't get into inconsistent state during removals", () => { + it("doesn't get into inconsistent state during removals", async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + , + ); + }); - ReactDOM.render(, container); + await act(async () => { + root.render(); + }); expect(container.textContent).toBe('Caught an error: Hello.'); Scheduler.unstable_clearLog(); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it("doesn't get into inconsistent state during additions", () => { + it("doesn't get into inconsistent state during additions", async () => { const container = document.createElement('div'); - ReactDOM.render(, container); - ReactDOM.render( - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); Scheduler.unstable_clearLog(); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it("doesn't get into inconsistent state during reorders", () => { + it("doesn't get into inconsistent state during reorders", async () => { function getAMixOfNormalAndBrokenRenderElements() { const elements = []; for (let i = 0; i < 100; i++) { @@ -1704,25 +2066,34 @@ describe('ReactErrorBoundaries', () => { let fail = false; const container = document.createElement('div'); - ReactDOM.render( - {getAMixOfNormalAndBrokenRenderElements()}, - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + {getAMixOfNormalAndBrokenRenderElements()} + , + ); + }); expect(container.textContent).not.toContain('Caught an error'); fail = true; - ReactDOM.render( - {getAMixOfNormalAndBrokenRenderElements()}, - container, - ); + await act(async () => { + root.render( + + {getAMixOfNormalAndBrokenRenderElements()} + , + ); + }); expect(container.textContent).toBe('Caught an error: Hello.'); Scheduler.unstable_clearLog(); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches errors originating downstream', () => { + it('catches errors originating downstream', async () => { let fail = false; class Stateful extends React.Component { state = {shouldThrow: false}; @@ -1738,12 +2109,14 @@ describe('ReactErrorBoundaries', () => { let statefulInst; const container = document.createElement('div'); - ReactDOM.render( - - (statefulInst = inst)} /> - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + (statefulInst = inst)} /> + , + ); + }); Scheduler.unstable_clearLog(); expect(() => { @@ -1751,30 +2124,26 @@ describe('ReactErrorBoundaries', () => { statefulInst.forceUpdate(); }).not.toThrow(); - assertLog([ - 'Stateful render [!]', - 'ErrorBoundary static getDerivedStateFromError', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]); - - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches errors in componentDidMount', () => { + it('catches errors in componentDidMount', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + + + , + ); + }); assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', @@ -1817,26 +2186,31 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('catches errors in componentDidUpdate', () => { + it('catches errors in componentDidUpdate', async () => { const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); assertLog([ 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', @@ -1855,33 +2229,30 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); it('catches errors in useEffect', async () => { const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); await act(() => { - ReactDOM.render( + root.render( Initial value , - container, ); - assertLog([ - 'ErrorBoundary constructor', - 'ErrorBoundary componentWillMount', - 'ErrorBoundary render success', - 'BrokenUseEffect render', - 'ErrorBoundary componentDidMount', - ]); - - expect(container.firstChild.textContent).toBe('Initial value'); - Scheduler.unstable_clearLog(); }); // verify flushed passive effects and handle the error assertLog([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenUseEffect render', + 'ErrorBoundary componentDidMount', 'BrokenUseEffect useEffect [!]', // Handle the error 'ErrorBoundary static getDerivedStateFromError', @@ -1889,18 +2260,19 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary render error', 'ErrorBoundary componentDidUpdate', ]); - expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); - it('catches errors in useLayoutEffect', () => { + it('catches errors in useLayoutEffect', async () => { const container = document.createElement('div'); - ReactDOM.render( - - Initial value - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + Initial value + , + ); + }); assertLog([ 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', @@ -1920,18 +2292,20 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); - it('propagates errors inside boundary during componentDidMount', () => { + it('propagates errors inside boundary during componentDidMount', async () => { const container = document.createElement('div'); - ReactDOM.render( - - ( -
We should never catch our own error: {error.message}.
- )} - /> -
, - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + ( +
We should never catch our own error: {error.message}.
+ )} + /> +
, + ); + }); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ 'ErrorBoundary constructor', @@ -1952,11 +2326,13 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog(['ErrorBoundary componentWillUnmount']); }); - it('calls static getDerivedStateFromError for each error that is captured', () => { + it('calls static getDerivedStateFromError for each error that is captured', async () => { function renderUnmountError(error) { return
Caught an unmounting error: {error.message}.
; } @@ -1965,40 +2341,43 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - ReactDOM.render( - - - - - - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + + + + + + + + , + ); + }); Scheduler.unstable_clearLog(); - ReactDOM.render( - - - - - - - , - container, - ); + await act(async () => { + root.render( + + + + + + + , + ); + }); expect(container.firstChild.textContent).toBe( 'Caught an unmounting error: E2.' + 'Caught an updating error: E4.', @@ -2048,7 +2427,9 @@ describe('ReactErrorBoundaries', () => { 'InnerUpdateBoundary componentDidUpdate', ]); - ReactDOM.unmountComponentAtNode(container); + await act(async () => { + root.unmount(); + }); assertLog([ 'OuterErrorBoundary componentWillUnmount', 'InnerUnmountBoundary componentWillUnmount', @@ -2056,7 +2437,7 @@ describe('ReactErrorBoundaries', () => { ]); }); - it('discards a bad root if the root component fails', () => { + it('discards a bad root if the root component fails', async () => { const X = null; const Y = undefined; let err1; @@ -2064,7 +2445,13 @@ describe('ReactErrorBoundaries', () => { try { const container = document.createElement('div'); - expect(() => ReactDOM.render(, container)).toErrorDev( + const root = ReactDOMClient.createRoot(container); + await expect( + async () => + await act(async () => { + root.render(, container); + }), + ).toErrorDev( 'React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function ' + '(for composite components) but got: null.', @@ -2074,7 +2461,13 @@ describe('ReactErrorBoundaries', () => { } try { const container = document.createElement('div'); - expect(() => ReactDOM.render(, container)).toErrorDev( + const root = ReactDOMClient.createRoot(container); + await expect( + async () => + await act(async () => { + root.render(, container); + }), + ).toErrorDev( 'React.createElement: type is invalid -- expected a string ' + '(for built-in components) or a class/function ' + '(for composite components) but got: undefined.', @@ -2087,19 +2480,23 @@ describe('ReactErrorBoundaries', () => { expect(err2.message).toMatch(/got: undefined/); }); - it('renders empty output if error boundary does not handle the error', () => { + it('renders empty output if error boundary does not handle the error', async () => { const container = document.createElement('div'); - expect(() => - ReactDOM.render( -
- Sibling - - - -
, - container, - ), - ).toThrow('Hello'); + const root = ReactDOMClient.createRoot(container); + + await expect(async () => { + await act(async () => { + root.render( +
+ Sibling + + + +
, + ); + }); + }).toThrow('Hello'); + expect(container.innerHTML).toBe(''); assertLog([ 'NoopErrorBoundary constructor', @@ -2117,7 +2514,7 @@ describe('ReactErrorBoundaries', () => { ]); }); - it('passes first error when two errors happen in commit', () => { + it('passes first error when two errors happen in commit', async () => { const errors = []; let caughtError; class Parent extends React.Component { @@ -2140,10 +2537,13 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); try { // Here, we test the behavior where there is no error boundary and we // delegate to the host root. - ReactDOM.render(, container); + await act(async () => { + root.render(); + }); } catch (e) { if (e.message !== 'parent sad' && e.message !== 'child sad') { throw e; @@ -2156,19 +2556,22 @@ describe('ReactErrorBoundaries', () => { expect(caughtError.message).toBe('child sad'); }); - it('propagates uncaught error inside unbatched initial mount', () => { + it('propagates uncaught error inside unbatched initial mount', async () => { function Foo() { throw new Error('foo error'); } const container = document.createElement('div'); - expect(() => { - ReactDOM.unstable_batchedUpdates(() => { - ReactDOM.render(, container); + const root = ReactDOMClient.createRoot(container); + await expect(async () => { + ReactDOM.unstable_batchedUpdates(async () => { + await act(async () => { + root.render(); + }); }); }).toThrow('foo error'); }); - it('handles errors that occur in before-mutation commit hook', () => { + it('handles errors that occur in before-mutation commit hook', async () => { const errors = []; let caughtError; class Parent extends React.Component { @@ -2193,9 +2596,14 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - ReactDOM.render(, container); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); try { - ReactDOM.render(, container); + await act(async () => { + root.render(); + }); } catch (e) { if (e.message !== 'parent sad' && e.message !== 'child sad') { throw e; @@ -2208,7 +2616,7 @@ describe('ReactErrorBoundaries', () => { expect(caughtError.message).toBe('child sad'); }); - it('should warn if an error boundary with only componentDidCatch does not update state', () => { + it('should warn if an error boundary with only componentDidCatch does not update state', async () => { class InvalidErrorBoundary extends React.Component { componentDidCatch(error, info) { // This component does not define getDerivedStateFromError(). @@ -2225,13 +2633,15 @@ describe('ReactErrorBoundaries', () => { }; const container = document.createElement('div'); - expect(() => { - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(async () => { + root.render( + + + , + ); + }); }).toErrorDev( 'InvalidErrorBoundary: Error boundaries should implement getDerivedStateFromError(). ' + 'In that method, return a state update to display an error message or fallback UI.', @@ -2239,7 +2649,7 @@ describe('ReactErrorBoundaries', () => { expect(container.textContent).toBe(''); }); - it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', () => { + it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', async () => { let componentDidCatchError, getDerivedStateFromErrorError; class ErrorBoundaryWithBothMethods extends React.Component { state = {error: null}; @@ -2261,34 +2671,39 @@ describe('ReactErrorBoundaries', () => { }; const container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); expect(container.textContent).toBe('ErrorBoundary'); expect(componentDidCatchError).toBe(thrownError); expect(getDerivedStateFromErrorError).toBe(thrownError); }); - it('should catch errors from invariants in completion phase', () => { + it('should catch errors from invariants in completion phase', async () => { const container = document.createElement('div'); - ReactDOM.render( - - -
- - , - container, - ); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + +
+ + , + ); + }); expect(container.textContent).toContain( 'Caught an error: input is a void element tag', ); }); - it('should catch errors from errors in the throw phase from boundaries', () => { + it('should catch errors from errors in the throw phase from boundaries', async () => { const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); const thrownError = new Error('original error'); const Throws = () => { @@ -2304,22 +2719,24 @@ describe('ReactErrorBoundaries', () => { } } - ReactDOM.render( - - - - - , - container, - ); + await act(async () => { + root.render( + + + + + , + ); + }); expect(container.textContent).toContain( 'Caught an error: gotta catch em all', ); }); - it('should protect errors from errors in the stack generation', () => { + it('should protect errors from errors in the stack generation', async () => { const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); const evilError = { message: 'gotta catch em all', @@ -2340,19 +2757,20 @@ describe('ReactErrorBoundaries', () => { return ; } - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); expect(container.textContent).toContain( 'Caught an error: gotta catch em all.', ); }); - it('catches errors thrown in componentWillUnmount', () => { + it('catches errors thrown in componentWillUnmount', async () => { class LocalErrorBoundary extends React.Component { state = {error: null}; static getDerivedStateFromError(error) { @@ -2392,16 +2810,18 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - - ReactDOM.render( - - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + + await act(async () => { + root.render( + + + + + + , + ); + }); expect(container.firstChild.textContent).toBe('sibling'); expect(container.lastChild.textContent).toBe('broken'); @@ -2412,12 +2832,13 @@ describe('ReactErrorBoundaries', () => { 'BrokenComponentWillUnmount render', ]); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); // React should skip over the unmounting boundary and find the nearest still-mounted boundary. expect(container.firstChild.textContent).toBe('OuterFallback'); @@ -2432,7 +2853,7 @@ describe('ReactErrorBoundaries', () => { ]); }); - it('catches errors thrown while detaching refs', () => { + it('catches errors thrown while detaching refs', async () => { class LocalErrorBoundary extends React.Component { state = {error: null}; static getDerivedStateFromError(error) { @@ -2474,16 +2895,18 @@ describe('ReactErrorBoundaries', () => { } const container = document.createElement('div'); - - ReactDOM.render( - - - - - - , - container, - ); + const root = ReactDOMClient.createRoot(container); + + await act(async () => { + root.render( + + + + + + , + ); + }); expect(container.firstChild.textContent).toBe('sibling'); expect(container.lastChild.textContent).toBe('ref'); @@ -2495,12 +2918,13 @@ describe('ReactErrorBoundaries', () => { 'LocalBrokenCallbackRef ref true', ]); - ReactDOM.render( - - - , - container, - ); + await act(async () => { + root.render( + + + , + ); + }); // React should skip over the unmounting boundary and find the nearest still-mounted boundary. expect(container.firstChild.textContent).toBe('OuterFallback'); From 7f6a0821412011c4f5a9cc2176dc7c3b0920400e Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 30 Jan 2024 11:28:18 -0800 Subject: [PATCH 2/7] Fix broken async jest tests --- .../ReactErrorBoundaries-test.internal.js | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index de2bbff875741..3f3e40b1161ab 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -583,9 +583,9 @@ describe('ReactErrorBoundaries', () => { let root = ReactDOMClient.createRoot(container); await expect(async () => { await act(async () => { - root.render(, container); + root.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); @@ -593,7 +593,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); @@ -601,7 +601,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); }); it('does not swallow exceptions on updating without boundaries', async () => { @@ -614,7 +614,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); @@ -625,7 +625,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); @@ -636,7 +636,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); }); it('does not swallow exceptions on unmounting without boundaries', async () => { @@ -649,7 +649,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root.unmount(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); }); it('prevents errors from leaking into other roots', async () => { @@ -667,7 +667,7 @@ describe('ReactErrorBoundaries', () => { await act(async () => { root2.render(); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); await act(async () => { root3.render( @@ -718,7 +718,7 @@ describe('ReactErrorBoundaries', () => { ); }); if (__DEV__) { - expect(console.error).toHaveBeenCalledTimes(2); + expect(console.error).toHaveBeenCalledTimes(1); expect(console.error.mock.calls[0][0]).toContain( 'The above error occurred in the component:', ); @@ -726,12 +726,6 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); assertLog([ - 'BrokenRender constructor', - 'BrokenRender componentWillMount', - 'BrokenRender render [!]', - 'BrokenRender constructor', - 'BrokenRender componentWillMount', - 'BrokenRender render [!]', 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -2495,7 +2489,7 @@ describe('ReactErrorBoundaries', () => {
, ); }); - }).toThrow('Hello'); + }).rejects.toThrow('Hello'); expect(container.innerHTML).toBe(''); assertLog([ @@ -2511,6 +2505,17 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', + 'NoopErrorBoundary constructor', + 'NoopErrorBoundary componentWillMount', + 'NoopErrorBoundary render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + 'NoopErrorBoundary static getDerivedStateFromError', + 'NoopErrorBoundary render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', ]); }); @@ -2563,12 +2568,12 @@ describe('ReactErrorBoundaries', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await expect(async () => { - ReactDOM.unstable_batchedUpdates(async () => { + await ReactDOM.unstable_batchedUpdates(async () => { await act(async () => { root.render(); }); }); - }).toThrow('foo error'); + }).rejects.toThrow('foo error'); }); it('handles errors that occur in before-mutation commit hook', async () => { @@ -2757,13 +2762,15 @@ describe('ReactErrorBoundaries', () => { return ; } - await act(async () => { - root.render( - - - , - ); - }); + await expect(async () => { + await act(async () => { + root.render( + + + , + ); + }); + }).rejects.toThrow('gotta catch em all'); expect(container.textContent).toContain( 'Caught an error: gotta catch em all.', From f791466b74deadc8a576d22badf24005548bb804 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 2 Feb 2024 13:34:41 -0800 Subject: [PATCH 3/7] Add retry comments and remove act from unmount calls --- .../ReactErrorBoundaries-test.internal.js | 199 ++++++++---------- 1 file changed, 92 insertions(+), 107 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 3f3e40b1161ab..f693e0fa32c48 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -646,9 +646,7 @@ describe('ReactErrorBoundaries', () => { root.render(); }); await expect(async () => { - await act(async () => { - root.unmount(); - }); + root.unmount(); }).rejects.toThrow('Hello'); }); @@ -691,16 +689,9 @@ describe('ReactErrorBoundaries', () => { expect(container1.firstChild.textContent).toBe('After 1'); expect(container2.firstChild.textContent).toBe('After 2'); expect(container3.firstChild.textContent).toBe('After 3'); - - await act(async () => { - root1.unmount(); - }); - await act(async () => { - root2.unmount(); - }); - await act(async () => { - root3.unmount(); - }); + root1.unmount(); + root2.unmount(); + root3.unmount(); expect(container1.firstChild).toBe(null); expect(container2.firstChild).toBe(null); expect(container3.firstChild).toBe(null); @@ -736,6 +727,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -748,9 +742,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -776,6 +768,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -788,9 +783,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -814,6 +807,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -824,9 +820,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -851,6 +845,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -862,9 +859,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -965,6 +960,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorMessage constructor', 'ErrorMessage componentWillMount', 'ErrorMessage render', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -981,9 +979,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog([ 'ErrorBoundary componentWillUnmount', 'ErrorMessage componentWillUnmount', @@ -1024,6 +1020,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1045,9 +1044,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1072,6 +1069,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1083,9 +1083,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1120,6 +1118,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1138,9 +1139,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1174,6 +1173,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1189,9 +1191,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1225,6 +1225,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1238,9 +1241,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog([ 'ErrorBoundary componentWillUnmount', 'Error message ref is set to null', @@ -1273,6 +1274,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1288,9 +1292,7 @@ describe('ReactErrorBoundaries', () => { '[object HTMLDivElement]', ); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); expect(errorMessageRef.current).toEqual(null); }); @@ -1313,9 +1315,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidMount', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1358,6 +1358,9 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1375,9 +1378,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1422,6 +1423,9 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1440,9 +1444,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1482,6 +1484,9 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1497,9 +1502,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1539,6 +1542,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1555,9 +1561,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1602,6 +1606,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1621,9 +1628,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1677,6 +1682,9 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1693,9 +1701,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog([ 'ErrorBoundary componentWillUnmount', 'Error message ref is set to null', @@ -1756,9 +1762,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1820,9 +1824,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -1888,9 +1890,7 @@ describe('ReactErrorBoundaries', () => { 'InnerErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog([ 'OuterErrorBoundary componentWillUnmount', 'InnerErrorBoundary componentWillUnmount', @@ -1941,9 +1941,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog([ 'ErrorBoundary componentWillUnmount', 'Normal componentWillUnmount', @@ -1976,9 +1974,7 @@ describe('ReactErrorBoundaries', () => { }); expect(container.textContent).toBe('Other screen'); - await act(async () => { - root.unmount(); - }); + root.unmount(); }); it("doesn't get into inconsistent state during removals", async () => { @@ -2000,9 +1996,7 @@ describe('ReactErrorBoundaries', () => { expect(container.textContent).toBe('Caught an error: Hello.'); Scheduler.unstable_clearLog(); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2024,9 +2018,7 @@ describe('ReactErrorBoundaries', () => { expect(container.textContent).toBe('Caught an error: Hello.'); Scheduler.unstable_clearLog(); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2081,9 +2073,7 @@ describe('ReactErrorBoundaries', () => { expect(container.textContent).toBe('Caught an error: Hello.'); Scheduler.unstable_clearLog(); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2117,10 +2107,7 @@ describe('ReactErrorBoundaries', () => { fail = true; statefulInst.forceUpdate(); }).not.toThrow(); - - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2180,9 +2167,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2223,9 +2208,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2242,6 +2225,9 @@ describe('ReactErrorBoundaries', () => { // verify flushed passive effects and handle the error assertLog([ + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -2320,9 +2306,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog(['ErrorBoundary componentWillUnmount']); }); @@ -2421,9 +2405,7 @@ describe('ReactErrorBoundaries', () => { 'InnerUpdateBoundary componentDidUpdate', ]); - await act(async () => { - root.unmount(); - }); + root.unmount(); assertLog([ 'OuterErrorBoundary componentWillUnmount', 'InnerUnmountBoundary componentWillUnmount', @@ -2505,6 +2487,9 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', + // Retry because the error may be caused by a dependency on concurrent + // state like a store. Retrying can recover from errors that will + // succeed in the next render such as tearing. 'NoopErrorBoundary constructor', 'NoopErrorBoundary componentWillMount', 'NoopErrorBoundary render', From bf2e0c6b0a7cf9a9dec45b5e0ee0e008733a1fd4 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 2 Feb 2024 13:36:48 -0800 Subject: [PATCH 4/7] merge changes --- packages/shared/ReactVersion.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/shared/ReactVersion.js b/packages/shared/ReactVersion.js index d0a14cfff7806..9055167e29cc5 100644 --- a/packages/shared/ReactVersion.js +++ b/packages/shared/ReactVersion.js @@ -1,16 +1 @@ -/** - * 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. - */ - -// TODO: this is special because it gets imported during build. -// -// TODO: 18.0.0 has not been released to NPM; -// It exists as a placeholder so that DevTools can support work tag changes between releases. -// When we next publish a release, update the matching TODO in backend/renderer.js -// TODO: This module is used both by the release scripts and to expose a version -// at runtime. We should instead inject the version number as part of the build -// process, and use the ReactVersions.js module as the single source of truth. -export default '18.2.0'; +export default '18.3.0-PLACEHOLDER'; From 15b6f16afecb838ee5ffd4ebe0b9387ee55abf9c Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 2 Feb 2024 13:38:50 -0800 Subject: [PATCH 5/7] fix unintional change to ReactVersion.js --- packages/shared/ReactVersion.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/shared/ReactVersion.js b/packages/shared/ReactVersion.js index 9055167e29cc5..bb90dec81ac20 100644 --- a/packages/shared/ReactVersion.js +++ b/packages/shared/ReactVersion.js @@ -1 +1,16 @@ -export default '18.3.0-PLACEHOLDER'; +/** + * 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. + */ + +// TODO: this is special because it gets imported during build. +// +// TODO: 18.0.0 has not been released to NPM; +// It exists as a placeholder so that DevTools can support work tag changes between releases. +// When we next publish a release, update the matching TODO in backend/renderer.js +// TODO: This module is used both by the release scripts and to expose a version +// at runtime. We should instead inject the version number as part of the build +// process, and use the ReactVersions.js module as the single source of truth. +export default '18.2.0'; \ No newline at end of file From c34bc47a05edc891446874054e4fa177a03e20df Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 2 Feb 2024 13:40:14 -0800 Subject: [PATCH 6/7] revert ReactVersion.js --- packages/shared/ReactVersion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/ReactVersion.js b/packages/shared/ReactVersion.js index bb90dec81ac20..d0a14cfff7806 100644 --- a/packages/shared/ReactVersion.js +++ b/packages/shared/ReactVersion.js @@ -13,4 +13,4 @@ // TODO: This module is used both by the release scripts and to expose a version // at runtime. We should instead inject the version number as part of the build // process, and use the ReactVersions.js module as the single source of truth. -export default '18.2.0'; \ No newline at end of file +export default '18.2.0'; From 9704f0bee604fa77599b8b68de718610bf2ba45d Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 2 Feb 2024 17:13:36 -0800 Subject: [PATCH 7/7] simplify error retry comments --- .../ReactErrorBoundaries-test.internal.js | 76 +++++-------------- 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index f693e0fa32c48..403a3de7b6b17 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -727,9 +727,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -768,9 +766,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -807,9 +803,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -845,9 +839,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -960,9 +952,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorMessage constructor', 'ErrorMessage componentWillMount', 'ErrorMessage render', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1020,9 +1010,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1069,9 +1057,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1118,9 +1104,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1173,9 +1157,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1225,9 +1207,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1274,9 +1254,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillMount', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -1358,9 +1336,7 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1423,9 +1399,7 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1484,9 +1458,7 @@ describe('ReactErrorBoundaries', () => { // Render the error message 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1542,9 +1514,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1606,9 +1576,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -1682,9 +1650,7 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary static getDerivedStateFromError', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render error', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary componentWillReceiveProps', 'ErrorBoundary componentWillUpdate', 'ErrorBoundary render success', @@ -2225,9 +2191,7 @@ describe('ReactErrorBoundaries', () => { // verify flushed passive effects and handle the error assertLog([ - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'ErrorBoundary constructor', 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', @@ -2487,9 +2451,7 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - // Retry because the error may be caused by a dependency on concurrent - // state like a store. Retrying can recover from errors that will - // succeed in the next render such as tearing. + // logs for error retry 'NoopErrorBoundary constructor', 'NoopErrorBoundary componentWillMount', 'NoopErrorBoundary render',