From 50dd20c2003225231ff06b50ecba5eea9f52a47e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 6 Dec 2018 13:55:38 -0800 Subject: [PATCH] Added ErrorBoundary tests for useEffect and useLayoutEffect (#14401) --- .../ReactErrorBoundaries-test.internal.js | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 4b7f5f3f6c5fd..0ee2d601bd6cb 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -28,6 +28,8 @@ describe('ReactErrorBoundaries', () => { let BrokenComponentWillMountErrorBoundary; let BrokenComponentDidMountErrorBoundary; let BrokenRender; + let BrokenUseEffect; + let BrokenUseLayoutEffect; let ErrorBoundary; let ErrorMessage; let NoopErrorBoundary; @@ -35,9 +37,11 @@ describe('ReactErrorBoundaries', () => { let Normal; beforeEach(() => { + jest.useFakeTimers(); jest.resetModules(); PropTypes = require('prop-types'); ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableHooks = true; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; ReactDOM = require('react-dom'); React = require('react'); @@ -386,6 +390,28 @@ describe('ReactErrorBoundaries', () => { } }; + BrokenUseEffect = props => { + log.push('BrokenUseEffect render'); + + React.useEffect(() => { + log.push('BrokenUseEffect useEffect [!]'); + throw new Error('Hello'); + }); + + return props.children; + }; + + BrokenUseLayoutEffect = props => { + log.push('BrokenUseLayoutEffect render'); + + React.useLayoutEffect(() => { + log.push('BrokenUseLayoutEffect useLayoutEffect [!]'); + throw new Error('Hello'); + }); + + return props.children; + }; + NoopErrorBoundary = class extends React.Component { constructor(props) { super(props); @@ -1795,6 +1821,67 @@ describe('ReactErrorBoundaries', () => { expect(log).toEqual(['ErrorBoundary componentWillUnmount']); }); + it('catches errors in useEffect', () => { + const container = document.createElement('div'); + ReactDOM.render( + + Initial value + , + container, + ); + expect(log).toEqual([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenUseEffect render', + 'ErrorBoundary componentDidMount', + ]); + + expect(container.firstChild.textContent).toBe('Initial value'); + log.length = 0; + + jest.runAllTimers(); + + // Flush passive effects and handle the error + expect(log).toEqual([ + 'BrokenUseEffect useEffect [!]', + // Handle the error + 'ErrorBoundary static getDerivedStateFromError', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', + ]); + + expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); + }); + + it('catches errors in useLayoutEffect', () => { + const container = document.createElement('div'); + ReactDOM.render( + + Initial value + , + container, + ); + expect(log).toEqual([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenUseLayoutEffect render', + 'BrokenUseLayoutEffect useLayoutEffect [!]', + // Fiber proceeds with the hooks + 'ErrorBoundary componentDidMount', + // The error propagates to the higher boundary + 'ErrorBoundary static getDerivedStateFromError', + // Fiber retries from the root + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', + ]); + + expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); + }); + it('propagates errors inside boundary during componentDidMount', () => { const container = document.createElement('div'); ReactDOM.render(