@samitouri / QOS-React / commits / b1c988d0de

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.

Dan Abramov committed Nov 30, 2016 at 17:00 UTC b1c988d0de5cdc479db4fa4ea9414ca79cd9073c
2 files changed +96
scripts/fiber/tests-passing.txt
+3
@@ -1087,6 +1087,9 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
1087 * can schedule updates after uncaught error during umounting
1088 * continues work on other roots despite caught errors
1089 * continues work on other roots despite uncaught errors
1090 +* catches reconciler errors in a boundary during mounting
1091 +* catches reconciler errors in a boundary during update
1092 +* recovers from uncaught reconciler errors
1093
1094 src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js
1095 * handles isMounted even when the initial render is deferred
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
+93
@@ -275,4 +275,97 @@ describe('ReactIncrementalErrorHandling', () => {
275 expect(ReactNoop.getChildren('e')).toEqual(null);
276 expect(ReactNoop.getChildren('f')).toEqual(null);
277 });
278 +
279 + it('catches reconciler errors in a boundary during mounting', () => {
280 + spyOn(console, 'error');
281 +
282 + class ErrorBoundary extends React.Component {
283 + state = {error: null};
284 + unstable_handleError(error) {
285 + this.setState({error});
286 + }
287 + render() {
288 + if (this.state.error) {
289 + return <span prop={this.state.error.message} />;
290 + }
291 + return this.props.children;
292 + }
293 + }
294 +
295 + const InvalidType = undefined;
296 + const brokenElement = <InvalidType />;
297 + function BrokenRender(props) {
298 + return brokenElement;
299 + }
300 +
301 + ReactNoop.render(
302 + <ErrorBoundary>
303 + <BrokenRender />
304 + </ErrorBoundary>
305 + );
306 + ReactNoop.flush();
307 + expect(ReactNoop.getChildren()).toEqual([span(
308 + 'Element type is invalid: expected a string (for built-in components) or ' +
309 + 'a class/function (for composite components) but got: undefined.'
310 + )]);
311 + expect(console.error.calls.count()).toBe(1);
312 + });
313 +
314 + it('catches reconciler errors in a boundary during update', () => {
315 + spyOn(console, 'error');
316 +
317 + class ErrorBoundary extends React.Component {
318 + state = {error: null};
319 + unstable_handleError(error) {
320 + this.setState({error});
321 + }
322 + render() {
323 + if (this.state.error) {
324 + return <span prop={this.state.error.message} />;
325 + }
326 + return this.props.children;
327 + }
328 + }
329 +
330 + const InvalidType = undefined;
331 + const brokenElement = <InvalidType />;
332 + function BrokenRender(props) {
333 + return props.fail ? brokenElement : <span />;
334 + }
335 +
336 + ReactNoop.render(
337 + <ErrorBoundary>
338 + <BrokenRender fail={false} />
339 + </ErrorBoundary>
340 + );
341 + ReactNoop.flush();
342 +
343 + ReactNoop.render(
344 + <ErrorBoundary>
345 + <BrokenRender fail={true} />
346 + </ErrorBoundary>
347 + );
348 + ReactNoop.flush();
349 + expect(ReactNoop.getChildren()).toEqual([span(
350 + 'Element type is invalid: expected a string (for built-in components) or ' +
351 + 'a class/function (for composite components) but got: undefined.'
352 + )]);
353 + expect(console.error.calls.count()).toBe(1);
354 + });
355 +
356 + it('recovers from uncaught reconciler errors', () => {
357 + spyOn(console, 'error');
358 + const InvalidType = undefined;
359 + ReactNoop.render(<InvalidType />);
360 + expect(() => {
361 + ReactNoop.flush();
362 + }).toThrowError(
363 + 'Element type is invalid: expected a string (for built-in components) or ' +
364 + 'a class/function (for composite components) but got: undefined.'
365 + );
366 +
367 + ReactNoop.render(<span prop="hi" />);
368 + ReactNoop.flush();
369 + expect(ReactNoop.getChildren()).toEqual([span('hi')]);
370 + });
371 });