Add a failing test for multiple independent boundaries
Now that commits are treated as atomic, it is possible that componentDidMount, componentDidUpdate, or componentWillUnmount threw in multiple places during the commit. We need to make sure we notify all affected boundaries of the first errors in them.
Dan Abramov committed
Oct 28, 2016 at 15:42 UTC
f51abe0b552ceabfa926957337d182d6f7173344
1 file changed
+121
-20
src/renderers/shared/stack/reconciler/__tests__/ReactErrorBoundaries-test.js
+121
-20
@@ -195,6 +195,9 @@ describe('ReactErrorBoundaries', () => {
195
};
196
197
BrokenComponentDidUpdate = class extends React.Component {
198
+ static defaultProps = {
199
+ errorText: 'Hello'
200
+ };
201
constructor(props) {
202
super(props);
203
log.push('BrokenComponentDidUpdate constructor');
@@ -217,7 +220,7 @@ describe('ReactErrorBoundaries', () => {
220
}
221
componentDidUpdate() {
222
log.push('BrokenComponentDidUpdate componentDidUpdate [!]');
220
- throw new Error('Hello');
223
+ throw new Error(this.props.errorText);
224
}
225
componentWillUnmount() {
226
log.push('BrokenComponentDidUpdate componentWillUnmount');
@@ -225,6 +228,9 @@ describe('ReactErrorBoundaries', () => {
228
};
229
230
BrokenComponentWillUnmount = class extends React.Component {
231
+ static defaultProps = {
232
+ errorText: 'Hello'
233
+ };
234
constructor(props) {
235
super(props);
236
log.push('BrokenComponentWillUnmount constructor');
@@ -250,7 +256,7 @@ describe('ReactErrorBoundaries', () => {
256
}
257
componentWillUnmount() {
258
log.push('BrokenComponentWillUnmount componentWillUnmount [!]');
253
- throw new Error('Hello');
259
+ throw new Error(this.props.errorText);
260
}
261
};
262
@@ -1418,10 +1424,10 @@ describe('ReactErrorBoundaries', () => {
1424
});
1425
1426
it('picks the right boundary when handling unmounting errors', () => {
1421
- function renderInnerError(error, props) {
1427
+ function renderInnerError(error) {
1428
return <div>Caught an inner error: {error.message}.</div>;
1429
}
1424
- function renderOuterError(error, props) {
1430
+ function renderOuterError(error) {
1431
return <div>Caught an outer error: {error.message}.</div>;
1432
}
1433
@@ -1756,6 +1762,47 @@ describe('ReactErrorBoundaries', () => {
1762
]);
1763
});
1764
1765
+ it('catches errors in componentDidUpdate', () => {
1766
+ var container = document.createElement('div');
1767
+ ReactDOM.render(
1768
+ <ErrorBoundary>
1769
+ <BrokenComponentDidUpdate />
1770
+ </ErrorBoundary>,
1771
+ container
1772
+ );
1773
+
1774
+ log.length = 0;
1775
+ ReactDOM.render(
1776
+ <ErrorBoundary>
1777
+ <BrokenComponentDidUpdate />
1778
+ </ErrorBoundary>,
1779
+ container
1780
+ );
1781
+ expect(log).toEqual([
1782
+ 'ErrorBoundary componentWillReceiveProps',
1783
+ 'ErrorBoundary componentWillUpdate',
1784
+ 'ErrorBoundary render success',
1785
+ 'BrokenComponentDidUpdate componentWillReceiveProps',
1786
+ 'BrokenComponentDidUpdate componentWillUpdate',
1787
+ 'BrokenComponentDidUpdate render',
1788
+ // All lifecycles run
1789
+ 'BrokenComponentDidUpdate componentDidUpdate [!]',
1790
+ 'ErrorBoundary componentDidUpdate',
1791
+ // Then, error is handled
1792
+ 'ErrorBoundary unstable_handleError',
1793
+ 'ErrorBoundary componentWillUpdate',
1794
+ 'ErrorBoundary render error',
1795
+ 'BrokenComponentDidUpdate componentWillUnmount',
1796
+ 'ErrorBoundary componentDidUpdate',
1797
+ ]);
1798
+
1799
+ log.length = 0;
1800
+ ReactDOM.unmountComponentAtNode(container);
1801
+ expect(log).toEqual([
1802
+ 'ErrorBoundary componentWillUnmount',
1803
+ ]);
1804
+ });
1805
+
1806
it('propagates errors inside boundary during componentDidMount', () => {
1807
var container = document.createElement('div');
1808
ReactDOM.render(
@@ -1796,44 +1843,98 @@ describe('ReactErrorBoundaries', () => {
1843
]);
1844
});
1845
1799
- it('catches errors in componentDidUpdate', () => {
1846
+ it('lets different boundaries catch their own first errors', () => {
1847
+ function renderUnmountError(error) {
1848
+ return <div>Caught an unmounting error: {error.message}.</div>;
1849
+ }
1850
+ function renderUpdateError(error) {
1851
+ return <div>Caught an updating error: {error.message}.</div>;
1852
+ }
1853
+
1854
var container = document.createElement('div');
1855
ReactDOM.render(
1802
- <ErrorBoundary>
1803
- <BrokenComponentDidUpdate />
1856
+ <ErrorBoundary logName="OuterErrorBoundary">
1857
+ <ErrorBoundary logName="InnerUnmountBoundary" renderError={renderUnmountError}>
1858
+ <BrokenComponentWillUnmount errorText="E1" />
1859
+ <BrokenComponentWillUnmount errorText="E2" />
1860
+ </ErrorBoundary>
1861
+ <ErrorBoundary logName="InnerUpdateBoundary" renderError={renderUpdateError}>
1862
+ <BrokenComponentDidUpdate errorText="E3" />
1863
+ <BrokenComponentDidUpdate errorText="E4" />
1864
+ </ErrorBoundary>
1865
</ErrorBoundary>,
1866
container
1867
);
1868
1869
log.length = 0;
1870
ReactDOM.render(
1810
- <ErrorBoundary>
1811
- <BrokenComponentDidUpdate />
1871
+ <ErrorBoundary logName="OuterErrorBoundary">
1872
+ <ErrorBoundary logName="InnerUnmountBoundary" renderError={renderUnmountError} />
1873
+ <ErrorBoundary logName="InnerUpdateBoundary" renderError={renderUpdateError}>
1874
+ <BrokenComponentDidUpdate errorText="E3" />
1875
+ <BrokenComponentDidUpdate errorText="E4" />
1876
+ </ErrorBoundary>
1877
</ErrorBoundary>,
1878
container
1879
);
1880
+
1881
+ expect(container.firstChild.textContent).toBe(
1882
+ 'Caught an unmounting error: E1.' +
1883
+ 'Caught an updating error: E3.'
1884
+ );
1885
expect(log).toEqual([
1816
- 'ErrorBoundary componentWillReceiveProps',
1817
- 'ErrorBoundary componentWillUpdate',
1818
- 'ErrorBoundary render success',
1886
+ // Begin update phase
1887
+ 'OuterErrorBoundary componentWillReceiveProps',
1888
+ 'OuterErrorBoundary componentWillUpdate',
1889
+ 'OuterErrorBoundary render success',
1890
+ 'InnerUnmountBoundary componentWillReceiveProps',
1891
+ 'InnerUnmountBoundary componentWillUpdate',
1892
+ 'InnerUnmountBoundary render success',
1893
+ 'InnerUpdateBoundary componentWillReceiveProps',
1894
+ 'InnerUpdateBoundary componentWillUpdate',
1895
+ 'InnerUpdateBoundary render success',
1896
+ // First come the updates
1897
'BrokenComponentDidUpdate componentWillReceiveProps',
1898
'BrokenComponentDidUpdate componentWillUpdate',
1899
'BrokenComponentDidUpdate render',
1822
- // All lifecycles run
1900
+ 'BrokenComponentDidUpdate componentWillReceiveProps',
1901
+ 'BrokenComponentDidUpdate componentWillUpdate',
1902
+ 'BrokenComponentDidUpdate render',
1903
+ // We're in commit phase now, deleting
1904
+ 'BrokenComponentWillUnmount componentWillUnmount [!]',
1905
+ 'BrokenComponentWillUnmount componentWillUnmount [!]',
1906
+ // Continue despite errors, handle them after commit is done
1907
+ 'InnerUnmountBoundary componentDidUpdate',
1908
+ // We're still in commit phase, now calling update lifecycles
1909
'BrokenComponentDidUpdate componentDidUpdate [!]',
1824
- 'ErrorBoundary componentDidUpdate',
1825
- // Then, error is handled
1826
- 'ErrorBoundary unstable_handleError',
1827
- 'ErrorBoundary componentWillUpdate',
1828
- 'ErrorBoundary render error',
1910
+ // Again, continue despite errors, we'll handle them later
1911
+ 'BrokenComponentDidUpdate componentDidUpdate [!]',
1912
+ 'InnerUpdateBoundary componentDidUpdate',
1913
+ 'OuterErrorBoundary componentDidUpdate',
1914
+ // The interesting part starts now.
1915
+ // Acknowledge errors independently but don't update yet:
1916
+ 'InnerUnmountBoundary unstable_handleError',
1917
+ 'InnerUpdateBoundary unstable_handleError',
1918
+ // Only two of four errors are acknowledged: one per boundary.
1919
+ // The rest are likely cascading and we ignore them.
1920
+ // Now update:
1921
+ 'InnerUnmountBoundary componentWillUpdate',
1922
+ 'InnerUnmountBoundary render error',
1923
+ 'InnerUpdateBoundary componentWillUpdate',
1924
+ 'InnerUpdateBoundary render error',
1925
+ // Commit
1926
'BrokenComponentDidUpdate componentWillUnmount',
1830
- 'ErrorBoundary componentDidUpdate',
1927
+ 'BrokenComponentDidUpdate componentWillUnmount',
1928
+ 'InnerUnmountBoundary componentDidUpdate',
1929
+ 'InnerUpdateBoundary componentDidUpdate',
1930
]);
1931
1932
log.length = 0;
1933
ReactDOM.unmountComponentAtNode(container);
1934
expect(log).toEqual([
1836
- 'ErrorBoundary componentWillUnmount',
1935
+ 'OuterErrorBoundary componentWillUnmount',
1936
+ 'InnerUnmountBoundary componentWillUnmount',
1937
+ 'InnerUpdateBoundary componentWillUnmount',
1938
]);
1939
});
1940
}