From b1c988d0de5cdc479db4fa4ea9414ca79cd9073c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 30 Nov 2016 17:00:14 +0000 Subject: [PATCH] Add tests for recovery from errors thrown in the reconciler (#8462) Test that errors in the reconciler can be caught by error boundaries, and that we can still schedule updates if they are uncaught. --- scripts/fiber/tests-passing.txt | 3 + .../ReactIncrementalErrorHandling-test.js | 93 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 7562b2881b..a780be9589 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1087,6 +1087,9 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js * can schedule updates after uncaught error during umounting * continues work on other roots despite caught errors * continues work on other roots despite uncaught errors +* catches reconciler errors in a boundary during mounting +* catches reconciler errors in a boundary during update +* recovers from uncaught reconciler errors src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js * handles isMounted even when the initial render is deferred diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js index 0f55886650..aef659cc35 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js @@ -275,4 +275,97 @@ describe('ReactIncrementalErrorHandling', () => { expect(ReactNoop.getChildren('e')).toEqual(null); expect(ReactNoop.getChildren('f')).toEqual(null); }); + + it('catches reconciler errors in a boundary during mounting', () => { + spyOn(console, 'error'); + + class ErrorBoundary extends React.Component { + state = {error: null}; + unstable_handleError(error) { + this.setState({error}); + } + render() { + if (this.state.error) { + return ; + } + return this.props.children; + } + } + + const InvalidType = undefined; + const brokenElement = ; + function BrokenRender(props) { + return brokenElement; + } + + ReactNoop.render( + + + + ); + ReactNoop.flush(); + expect(ReactNoop.getChildren()).toEqual([span( + 'Element type is invalid: expected a string (for built-in components) or ' + + 'a class/function (for composite components) but got: undefined.' + )]); + expect(console.error.calls.count()).toBe(1); + }); + + it('catches reconciler errors in a boundary during update', () => { + spyOn(console, 'error'); + + class ErrorBoundary extends React.Component { + state = {error: null}; + unstable_handleError(error) { + this.setState({error}); + } + render() { + if (this.state.error) { + return ; + } + return this.props.children; + } + } + + const InvalidType = undefined; + const brokenElement = ; + function BrokenRender(props) { + return props.fail ? brokenElement : ; + } + + ReactNoop.render( + + + + ); + ReactNoop.flush(); + + ReactNoop.render( + + + + ); + ReactNoop.flush(); + expect(ReactNoop.getChildren()).toEqual([span( + 'Element type is invalid: expected a string (for built-in components) or ' + + 'a class/function (for composite components) but got: undefined.' + )]); + expect(console.error.calls.count()).toBe(1); + }); + + it('recovers from uncaught reconciler errors', () => { + spyOn(console, 'error'); + const InvalidType = undefined; + ReactNoop.render(); + expect(() => { + ReactNoop.flush(); + }).toThrowError( + 'Element type is invalid: expected a string (for built-in components) or ' + + 'a class/function (for composite components) but got: undefined.' + ); + + ReactNoop.render(); + ReactNoop.flush(); + expect(ReactNoop.getChildren()).toEqual([span('hi')]); + }); });