diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index 838936a499..de4877b45f 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -404,20 +404,6 @@ function runActTests(label, render, unmount) { expect(container.innerHTML).toBe('1'); }); - it('propagates errors', async () => { - let err; - try { - await act(async () => { - await sleep(100); - throw new Error('some error'); - }); - } catch (_err) { - err = _err; - } finally { - expect(err instanceof Error).toBe(true); - expect(err.message).toBe('some error'); - } - }); it('can handle cascading promises', async () => { // this component triggers an effect, that waits a tick, // then sets state. repeats this 5 times. @@ -524,5 +510,111 @@ function runActTests(label, render, unmount) { }); } }); + describe('error propagation', () => { + it('propagates errors - sync', () => { + let err; + try { + act(() => { + throw new Error('some error'); + }); + } catch (_err) { + err = _err; + } finally { + expect(err instanceof Error).toBe(true); + expect(err.message).toBe('some error'); + } + }); + + it('should propagate errors from effects - sync', () => { + function App() { + React.useEffect(() => { + throw new Error('oh no'); + }); + return null; + } + let error; + + try { + act(() => { + render(, container); + }); + } catch (_error) { + error = _error; + } finally { + expect(error instanceof Error).toBe(true); + expect(error.message).toBe('oh no'); + } + }); + + it('propagates errors - async', async () => { + let err; + try { + await act(async () => { + await sleep(100); + throw new Error('some error'); + }); + } catch (_err) { + err = _err; + } finally { + expect(err instanceof Error).toBe(true); + expect(err.message).toBe('some error'); + } + }); + + it('should cleanup after errors - sync', () => { + function App() { + React.useEffect(() => { + Scheduler.yieldValue('oh yes'); + }); + return null; + } + let error; + try { + act(() => { + throw new Error('oh no'); + }); + } catch (_error) { + error = _error; + } finally { + expect(error instanceof Error).toBe(true); + expect(error.message).toBe('oh no'); + // should be able to render components after this tho + act(() => { + render(, container); + }); + expect(Scheduler).toHaveYielded(['oh yes']); + } + }); + + it('should cleanup after errors - async', async () => { + function App() { + async function somethingAsync() { + await null; + Scheduler.yieldValue('oh yes'); + } + React.useEffect(() => { + somethingAsync(); + }); + return null; + } + let error; + try { + await act(async () => { + await sleep(100); + throw new Error('oh no'); + }); + } catch (_error) { + error = _error; + } finally { + expect(error instanceof Error).toBe(true); + expect(error.message).toBe('oh no'); + // should be able to render components after this tho + await act(async () => { + render(, container); + }); + expect(Scheduler).toHaveYielded(['oh yes']); + } + }); + }); }); } diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js index e6dc6e3880..19ed32342f 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js +++ b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js @@ -108,7 +108,15 @@ function act(callback: () => Thenable) { } } - const result = batchedUpdates(callback); + let result; + try { + result = batchedUpdates(callback); + } catch (error) { + // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth + onDone(); + throw error; + } + if ( result !== null && typeof result === 'object' && diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 25effd1c20..ac7ab62829 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -724,7 +724,15 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { } } - const result = batchedUpdates(callback); + let result; + try { + result = batchedUpdates(callback); + } catch (error) { + // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth + onDone(); + throw error; + } + if ( result !== null && typeof result === 'object' && diff --git a/packages/react-test-renderer/src/ReactTestRendererAct.js b/packages/react-test-renderer/src/ReactTestRendererAct.js index ffd55af9eb..ae5eba265b 100644 --- a/packages/react-test-renderer/src/ReactTestRendererAct.js +++ b/packages/react-test-renderer/src/ReactTestRendererAct.js @@ -89,7 +89,15 @@ function act(callback: () => Thenable) { } } - const result = batchedUpdates(callback); + let result; + try { + result = batchedUpdates(callback); + } catch (error) { + // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth + onDone(); + throw error; + } + if ( result !== null && typeof result === 'object' &&