Add more tests for error boundaries
Dan Abramov committed
Oct 28, 2016 at 15:29 UTC
bd3c90ac6f9b601e0c0b53ae38b0e8d4bbc5ac03
1 file changed
+299
-162
src/renderers/shared/stack/reconciler/__tests__/ReactErrorBoundaries-test.js
+299
-162
@@ -28,6 +28,7 @@ describe('ReactErrorBoundaries', () => {
28
var BrokenComponentWillUnmount;
29
var BrokenRenderErrorBoundary;
30
var BrokenComponentWillMountErrorBoundary;
31
+ var BrokenComponentDidMountErrorBoundary;
32
var BrokenRender;
33
var ErrorBoundary;
34
var ErrorMessage;
@@ -283,6 +284,36 @@ describe('ReactErrorBoundaries', () => {
284
}
285
};
286
287
+ BrokenComponentDidMountErrorBoundary = class extends React.Component {
288
+ constructor(props) {
289
+ super(props);
290
+ this.state = {error: null};
291
+ log.push('BrokenComponentDidMountErrorBoundary constructor');
292
+ }
293
+ render() {
294
+ if (this.state.error) {
295
+ log.push('BrokenComponentDidMountErrorBoundary render error');
296
+ return <div>Caught an error: {this.state.error.message}.</div>;
297
+ }
298
+ log.push('BrokenComponentDidMountErrorBoundary render success');
299
+ return <div>{this.props.children}</div>;
300
+ }
301
+ componentWillMount() {
302
+ log.push('BrokenComponentDidMountErrorBoundary componentWillMount');
303
+ }
304
+ componentDidMount() {
305
+ log.push('BrokenComponentDidMountErrorBoundary componentDidMount [!]');
306
+ throw new Error('Hello');
307
+ }
308
+ componentWillUnmount() {
309
+ log.push('BrokenComponentDidMountErrorBoundary componentWillUnmount');
310
+ }
311
+ unstable_handleError(error) {
312
+ log.push('BrokenComponentDidMountErrorBoundary unstable_handleError');
313
+ this.setState({error});
314
+ }
315
+ };
316
+
317
BrokenRenderErrorBoundary = class extends React.Component {
318
constructor(props) {
319
super(props);
@@ -397,43 +428,44 @@ describe('ReactErrorBoundaries', () => {
428
};
429
430
ErrorBoundary = class extends React.Component {
400
- constructor() {
401
- super();
431
+ constructor(props) {
432
+ super(props);
433
this.state = {error: null};
403
- log.push('ErrorBoundary constructor');
434
+ log.push(`${this.props.logName} constructor`);
435
}
436
render() {
437
if (this.state.error && !this.props.forceRetry) {
407
- log.push('ErrorBoundary render error');
438
+ log.push(`${this.props.logName} render error`);
439
return this.props.renderError(this.state.error, this.props);
440
}
410
- log.push('ErrorBoundary render success');
441
+ log.push(`${this.props.logName} render success`);
442
return <div>{this.props.children}</div>;
443
}
444
unstable_handleError(error) {
414
- log.push('ErrorBoundary unstable_handleError');
445
+ log.push(`${this.props.logName} unstable_handleError`);
446
this.setState({error});
447
}
448
componentWillMount() {
418
- log.push('ErrorBoundary componentWillMount');
449
+ log.push(`${this.props.logName} componentWillMount`);
450
}
451
componentDidMount() {
421
- log.push('ErrorBoundary componentDidMount');
452
+ log.push(`${this.props.logName} componentDidMount`);
453
}
454
componentWillReceiveProps() {
424
- log.push('ErrorBoundary componentWillReceiveProps');
455
+ log.push(`${this.props.logName} componentWillReceiveProps`);
456
}
457
componentWillUpdate() {
427
- log.push('ErrorBoundary componentWillUpdate');
458
+ log.push(`${this.props.logName} componentWillUpdate`);
459
}
460
componentDidUpdate() {
430
- log.push('ErrorBoundary componentDidUpdate');
461
+ log.push(`${this.props.logName} componentDidUpdate`);
462
}
463
componentWillUnmount() {
433
- log.push('ErrorBoundary componentWillUnmount');
464
+ log.push(`${this.props.logName} componentWillUnmount`);
465
}
466
};
467
ErrorBoundary.defaultProps = {
468
+ logName: 'ErrorBoundary',
469
renderError(error, props) {
470
return (
471
<div ref={props.errorMessageRef}>
@@ -464,53 +496,6 @@ describe('ReactErrorBoundaries', () => {
496
};
497
});
498
467
- if (ReactDOMFeatureFlags.useFiber) {
468
- // This test implements a new feature in Fiber.
469
- it('catches errors originating downstream', () => {
470
- var fail = false;
471
- class Stateful extends React.Component {
472
- state = {shouldThrow: false};
473
-
474
- render() {
475
- if (fail) {
476
- log.push('Stateful render [!]');
477
- throw new Error('Hello');
478
- }
479
- return <div>{this.props.children}</div>;
480
- }
481
- }
482
-
483
- var statefulInst;
484
- var container = document.createElement('div');
485
- ReactDOM.render(
486
- <ErrorBoundary>
487
- <Stateful ref={inst => statefulInst = inst} />
488
- </ErrorBoundary>,
489
- container
490
- );
491
-
492
- log.length = 0;
493
- expect(() => {
494
- fail = true;
495
- statefulInst.forceUpdate();
496
- }).not.toThrow();
497
-
498
- expect(log).toEqual([
499
- 'Stateful render [!]',
500
- 'ErrorBoundary unstable_handleError',
501
- 'ErrorBoundary componentWillUpdate',
502
- 'ErrorBoundary render error',
503
- 'ErrorBoundary componentDidUpdate',
504
- ]);
505
-
506
- log.length = 0;
507
- ReactDOM.unmountComponentAtNode(container);
508
- expect(log).toEqual([
509
- 'ErrorBoundary componentWillUnmount',
510
- ]);
511
- });
512
- }
513
-
499
it('renders an error state if child throws in render', () => {
500
var container = document.createElement('div');
501
ReactDOM.render(
@@ -661,65 +646,6 @@ describe('ReactErrorBoundaries', () => {
646
]);
647
});
648
664
- if (ReactDOMFeatureFlags.useFiber) {
665
- // This test implements a new feature in Fiber.
666
- it('catches errors in componentDidMount', () => {
667
- var container = document.createElement('div');
668
- ReactDOM.render(
669
- <ErrorBoundary>
670
- <BrokenComponentWillUnmount>
671
- <Normal />
672
- </BrokenComponentWillUnmount>
673
- <BrokenComponentDidMount />
674
- <Normal logName="LastChild" />
675
- </ErrorBoundary>,
676
- container
677
- );
678
- expect(log).toEqual([
679
- 'ErrorBoundary constructor',
680
- 'ErrorBoundary componentWillMount',
681
- 'ErrorBoundary render success',
682
- 'BrokenComponentWillUnmount constructor',
683
- 'BrokenComponentWillUnmount componentWillMount',
684
- 'BrokenComponentWillUnmount render',
685
- 'Normal constructor',
686
- 'Normal componentWillMount',
687
- 'Normal render',
688
- 'BrokenComponentDidMount constructor',
689
- 'BrokenComponentDidMount componentWillMount',
690
- 'BrokenComponentDidMount render',
691
- 'LastChild constructor',
692
- 'LastChild componentWillMount',
693
- 'LastChild render',
694
- // Start flushing didMount queue
695
- 'Normal componentDidMount',
696
- 'BrokenComponentWillUnmount componentDidMount',
697
- 'BrokenComponentDidMount componentDidMount [!]',
698
- // Continue despite the error
699
- 'LastChild componentDidMount',
700
- 'ErrorBoundary componentDidMount',
701
- // Now we are ready to handle the error
702
- 'ErrorBoundary unstable_handleError',
703
- 'ErrorBoundary componentWillUpdate',
704
- 'ErrorBoundary render error',
705
- // Safely unmount every child
706
- 'BrokenComponentWillUnmount componentWillUnmount [!]',
707
- // Continue unmounting safely despite any errors
708
- 'Normal componentWillUnmount',
709
- 'BrokenComponentDidMount componentWillUnmount',
710
- 'LastChild componentWillUnmount',
711
- // The update has finished
712
- 'ErrorBoundary componentDidUpdate',
713
- ]);
714
-
715
- log.length = 0;
716
- ReactDOM.unmountComponentAtNode(container);
717
- expect(log).toEqual([
718
- 'ErrorBoundary componentWillUnmount',
719
- ]);
720
- });
721
- }
722
-
649
it('propagates errors on retry on mounting', () => {
650
var container = document.createElement('div');
651
ReactDOM.render(
@@ -1356,50 +1282,6 @@ describe('ReactErrorBoundaries', () => {
1282
]);
1283
});
1284
1359
- if (ReactDOMFeatureFlags.useFiber) {
1360
- // This test implements a new feature in Fiber.
1361
- it('catches errors in componentDidUpdate', () => {
1362
- var container = document.createElement('div');
1363
- ReactDOM.render(
1364
- <ErrorBoundary>
1365
- <BrokenComponentDidUpdate />
1366
- </ErrorBoundary>,
1367
- container
1368
- );
1369
-
1370
- log.length = 0;
1371
- ReactDOM.render(
1372
- <ErrorBoundary>
1373
- <BrokenComponentDidUpdate />
1374
- </ErrorBoundary>,
1375
- container
1376
- );
1377
- expect(log).toEqual([
1378
- 'ErrorBoundary componentWillReceiveProps',
1379
- 'ErrorBoundary componentWillUpdate',
1380
- 'ErrorBoundary render success',
1381
- 'BrokenComponentDidUpdate componentWillReceiveProps',
1382
- 'BrokenComponentDidUpdate componentWillUpdate',
1383
- 'BrokenComponentDidUpdate render',
1384
- // All lifecycles run
1385
- 'BrokenComponentDidUpdate componentDidUpdate [!]',
1386
- 'ErrorBoundary componentDidUpdate',
1387
- // Then, error is handled
1388
- 'ErrorBoundary unstable_handleError',
1389
- 'ErrorBoundary componentWillUpdate',
1390
- 'ErrorBoundary render error',
1391
- 'BrokenComponentDidUpdate componentWillUnmount',
1392
- 'ErrorBoundary componentDidUpdate',
1393
- ]);
1394
-
1395
- log.length = 0;
1396
- ReactDOM.unmountComponentAtNode(container);
1397
- expect(log).toEqual([
1398
- 'ErrorBoundary componentWillUnmount',
1399
- ]);
1400
- });
1401
- }
1402
-
1285
it('recovers from componentWillUnmount errors on update', () => {
1286
var container = document.createElement('div');
1287
ReactDOM.render(
@@ -1535,6 +1417,76 @@ describe('ReactErrorBoundaries', () => {
1417
]);
1418
});
1419
1420
+ it('picks the right boundary when handling unmounting errors', () => {
1421
+ function renderInnerError(error, props) {
1422
+ return <div>Caught an inner error: {error.message}.</div>;
1423
+ }
1424
+ function renderOuterError(error, props) {
1425
+ return <div>Caught an outer error: {error.message}.</div>;
1426
+ }
1427
+
1428
+ var container = document.createElement('div');
1429
+ ReactDOM.render(
1430
+ <ErrorBoundary logName="OuterErrorBoundary" renderError={renderOuterError}>
1431
+ <ErrorBoundary logName="InnerErrorBoundary" renderError={renderInnerError}>
1432
+ <BrokenComponentWillUnmount />
1433
+ </ErrorBoundary>
1434
+ </ErrorBoundary>,
1435
+ container
1436
+ );
1437
+
1438
+ log.length = 0;
1439
+ ReactDOM.render(
1440
+ <ErrorBoundary logName="OuterErrorBoundary" renderError={renderOuterError}>
1441
+ <ErrorBoundary logName="InnerErrorBoundary" renderError={renderInnerError} />
1442
+ </ErrorBoundary>,
1443
+ container
1444
+ );
1445
+ expect(container.textContent).toBe('Caught an inner error: Hello.');
1446
+ expect(log).toEqual([
1447
+ // Update outer boundary
1448
+ 'OuterErrorBoundary componentWillReceiveProps',
1449
+ 'OuterErrorBoundary componentWillUpdate',
1450
+ 'OuterErrorBoundary render success',
1451
+ // Update inner boundary
1452
+ 'InnerErrorBoundary componentWillReceiveProps',
1453
+ 'InnerErrorBoundary componentWillUpdate',
1454
+ 'InnerErrorBoundary render success',
1455
+ // Try unmounting child
1456
+ 'BrokenComponentWillUnmount componentWillUnmount [!]',
1457
+ ...(ReactDOMFeatureFlags.useFiber ? [
1458
+ // Fiber proceeds with lifecycles despite errors
1459
+ // Inner and outer boundaries have updated in this phase
1460
+ 'InnerErrorBoundary componentDidUpdate',
1461
+ 'OuterErrorBoundary componentDidUpdate',
1462
+ // Now that commit phase is done, Fiber handles errors
1463
+ // Only inner boundary receives the error:
1464
+ 'InnerErrorBoundary unstable_handleError',
1465
+ 'InnerErrorBoundary componentWillUpdate',
1466
+ // Render an error now
1467
+ 'InnerErrorBoundary render error',
1468
+ // In Fiber, this was a local update to the
1469
+ // inner boundary so only its hook fires
1470
+ 'InnerErrorBoundary componentDidUpdate',
1471
+ ] : [
1472
+ // Stack will handle error immediately
1473
+ 'InnerErrorBoundary unstable_handleError',
1474
+ 'InnerErrorBoundary render error',
1475
+ // In stack, this was a part of the update to the
1476
+ // outer boundary so both lifecycles fire
1477
+ 'InnerErrorBoundary componentDidUpdate',
1478
+ 'OuterErrorBoundary componentDidUpdate',
1479
+ ]),
1480
+ ]);
1481
+
1482
+ log.length = 0;
1483
+ ReactDOM.unmountComponentAtNode(container);
1484
+ expect(log).toEqual([
1485
+ 'OuterErrorBoundary componentWillUnmount',
1486
+ 'InnerErrorBoundary componentWillUnmount',
1487
+ ]);
1488
+ });
1489
+
1490
it('can recover from error state', () => {
1491
var container = document.createElement('div');
1492
ReactDOM.render(
@@ -1701,4 +1653,189 @@ describe('ReactErrorBoundaries', () => {
1653
'ErrorBoundary componentWillUnmount',
1654
]);
1655
});
1656
+
1657
+ // The tests below implement new features in Fiber.
1658
+ if (ReactDOMFeatureFlags.useFiber) {
1659
+ it('catches errors originating downstream', () => {
1660
+ var fail = false;
1661
+ class Stateful extends React.Component {
1662
+ state = {shouldThrow: false};
1663
+
1664
+ render() {
1665
+ if (fail) {
1666
+ log.push('Stateful render [!]');
1667
+ throw new Error('Hello');
1668
+ }
1669
+ return <div>{this.props.children}</div>;
1670
+ }
1671
+ }
1672
+
1673
+ var statefulInst;
1674
+ var container = document.createElement('div');
1675
+ ReactDOM.render(
1676
+ <ErrorBoundary>
1677
+ <Stateful ref={inst => statefulInst = inst} />
1678
+ </ErrorBoundary>,
1679
+ container
1680
+ );
1681
+
1682
+ log.length = 0;
1683
+ expect(() => {
1684
+ fail = true;
1685
+ statefulInst.forceUpdate();
1686
+ }).not.toThrow();
1687
+
1688
+ expect(log).toEqual([
1689
+ 'Stateful render [!]',
1690
+ 'ErrorBoundary unstable_handleError',
1691
+ 'ErrorBoundary componentWillUpdate',
1692
+ 'ErrorBoundary render error',
1693
+ 'ErrorBoundary componentDidUpdate',
1694
+ ]);
1695
+
1696
+ log.length = 0;
1697
+ ReactDOM.unmountComponentAtNode(container);
1698
+ expect(log).toEqual([
1699
+ 'ErrorBoundary componentWillUnmount',
1700
+ ]);
1701
+ });
1702
+
1703
+ it('catches errors in componentDidMount', () => {
1704
+ var container = document.createElement('div');
1705
+ ReactDOM.render(
1706
+ <ErrorBoundary>
1707
+ <BrokenComponentWillUnmount>
1708
+ <Normal />
1709
+ </BrokenComponentWillUnmount>
1710
+ <BrokenComponentDidMount />
1711
+ <Normal logName="LastChild" />
1712
+ </ErrorBoundary>,
1713
+ container
1714
+ );
1715
+ expect(log).toEqual([
1716
+ 'ErrorBoundary constructor',
1717
+ 'ErrorBoundary componentWillMount',
1718
+ 'ErrorBoundary render success',
1719
+ 'BrokenComponentWillUnmount constructor',
1720
+ 'BrokenComponentWillUnmount componentWillMount',
1721
+ 'BrokenComponentWillUnmount render',
1722
+ 'Normal constructor',
1723
+ 'Normal componentWillMount',
1724
+ 'Normal render',
1725
+ 'BrokenComponentDidMount constructor',
1726
+ 'BrokenComponentDidMount componentWillMount',
1727
+ 'BrokenComponentDidMount render',
1728
+ 'LastChild constructor',
1729
+ 'LastChild componentWillMount',
1730
+ 'LastChild render',
1731
+ // Start flushing didMount queue
1732
+ 'Normal componentDidMount',
1733
+ 'BrokenComponentWillUnmount componentDidMount',
1734
+ 'BrokenComponentDidMount componentDidMount [!]',
1735
+ // Continue despite the error
1736
+ 'LastChild componentDidMount',
1737
+ 'ErrorBoundary componentDidMount',
1738
+ // Now we are ready to handle the error
1739
+ 'ErrorBoundary unstable_handleError',
1740
+ 'ErrorBoundary componentWillUpdate',
1741
+ 'ErrorBoundary render error',
1742
+ // Safely unmount every child
1743
+ 'BrokenComponentWillUnmount componentWillUnmount [!]',
1744
+ // Continue unmounting safely despite any errors
1745
+ 'Normal componentWillUnmount',
1746
+ 'BrokenComponentDidMount componentWillUnmount',
1747
+ 'LastChild componentWillUnmount',
1748
+ // The update has finished
1749
+ 'ErrorBoundary componentDidUpdate',
1750
+ ]);
1751
+
1752
+ log.length = 0;
1753
+ ReactDOM.unmountComponentAtNode(container);
1754
+ expect(log).toEqual([
1755
+ 'ErrorBoundary componentWillUnmount',
1756
+ ]);
1757
+ });
1758
+
1759
+ it('propagates errors inside boundary during componentDidMount', () => {
1760
+ var container = document.createElement('div');
1761
+ ReactDOM.render(
1762
+ <ErrorBoundary>
1763
+ <BrokenComponentDidMountErrorBoundary
1764
+ renderError={error => (
1765
+ <div>
1766
+ We should never catch our own error: {error.message}.
1767
+ </div>
1768
+ )} />
1769
+ </ErrorBoundary>,
1770
+ container
1771
+ );
1772
+ expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
1773
+ expect(log).toEqual([
1774
+ 'ErrorBoundary constructor',
1775
+ 'ErrorBoundary componentWillMount',
1776
+ 'ErrorBoundary render success',
1777
+ 'BrokenComponentDidMountErrorBoundary constructor',
1778
+ 'BrokenComponentDidMountErrorBoundary componentWillMount',
1779
+ 'BrokenComponentDidMountErrorBoundary render success',
1780
+ 'BrokenComponentDidMountErrorBoundary componentDidMount [!]',
1781
+ // Fiber proceeds with the hooks
1782
+ 'ErrorBoundary componentDidMount',
1783
+ // The error propagates to the higher boundary
1784
+ 'ErrorBoundary unstable_handleError',
1785
+ // Fiber retries from the root
1786
+ 'ErrorBoundary componentWillUpdate',
1787
+ 'ErrorBoundary render error',
1788
+ 'BrokenComponentDidMountErrorBoundary componentWillUnmount',
1789
+ 'ErrorBoundary componentDidUpdate',
1790
+ ]);
1791
+
1792
+ log.length = 0;
1793
+ ReactDOM.unmountComponentAtNode(container);
1794
+ expect(log).toEqual([
1795
+ 'ErrorBoundary componentWillUnmount',
1796
+ ]);
1797
+ });
1798
+
1799
+ it('catches errors in componentDidUpdate', () => {
1800
+ var container = document.createElement('div');
1801
+ ReactDOM.render(
1802
+ <ErrorBoundary>
1803
+ <BrokenComponentDidUpdate />
1804
+ </ErrorBoundary>,
1805
+ container
1806
+ );
1807
+
1808
+ log.length = 0;
1809
+ ReactDOM.render(
1810
+ <ErrorBoundary>
1811
+ <BrokenComponentDidUpdate />
1812
+ </ErrorBoundary>,
1813
+ container
1814
+ );
1815
+ expect(log).toEqual([
1816
+ 'ErrorBoundary componentWillReceiveProps',
1817
+ 'ErrorBoundary componentWillUpdate',
1818
+ 'ErrorBoundary render success',
1819
+ 'BrokenComponentDidUpdate componentWillReceiveProps',
1820
+ 'BrokenComponentDidUpdate componentWillUpdate',
1821
+ 'BrokenComponentDidUpdate render',
1822
+ // All lifecycles run
1823
+ 'BrokenComponentDidUpdate componentDidUpdate [!]',
1824
+ 'ErrorBoundary componentDidUpdate',
1825
+ // Then, error is handled
1826
+ 'ErrorBoundary unstable_handleError',
1827
+ 'ErrorBoundary componentWillUpdate',
1828
+ 'ErrorBoundary render error',
1829
+ 'BrokenComponentDidUpdate componentWillUnmount',
1830
+ 'ErrorBoundary componentDidUpdate',
1831
+ ]);
1832
+
1833
+ log.length = 0;
1834
+ ReactDOM.unmountComponentAtNode(container);
1835
+ expect(log).toEqual([
1836
+ 'ErrorBoundary componentWillUnmount',
1837
+ ]);
1838
+ });
1839
+ }
1840
+
1841
});