@samitouri / QOS-React / commits / f51abe0b55

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 }