forked from facebook/react
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathReactIncrementalErrorLogging-test.internal.js
More file actions
253 lines (224 loc) · 7.86 KB
/
ReactIncrementalErrorLogging-test.internal.js
File metadata and controls
253 lines (224 loc) · 7.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
let React;
let ReactNoop;
describe('ReactIncrementalErrorLogging', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
});
it('should log errors that occur during the begin phase', () => {
// Errors are redundantly logged in production mode by ReactFiberErrorLogger.
// It's okay to ignore them for the purpose of this test.
spyOnProd(console, 'error');
class ErrorThrowingComponent extends React.Component {
componentWillMount() {
const error = new Error('componentWillMount error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
}
render() {
return <div />;
}
}
ReactNoop.render(
<div>
<span>
<ErrorThrowingComponent />
</span>
</div>,
);
expect(() => {
expect(ReactNoop.flushDeferredPri).toWarnDev(
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
' in ErrorThrowingComponent (at **)\n' +
' in span (at **)\n' +
' in div (at **)\n\n' +
'Consider adding an error boundary to your tree to customize error handling behavior.',
);
}).toThrowError('componentWillMount error');
});
it('should log errors that occur during the commit phase', () => {
// Errors are redundantly logged in production mode by ReactFiberErrorLogger.
// It's okay to ignore them for the purpose of this test.
spyOnProd(console, 'error');
class ErrorThrowingComponent extends React.Component {
componentDidMount() {
const error = new Error('componentDidMount error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
}
render() {
return <div />;
}
}
ReactNoop.render(
<div>
<span>
<ErrorThrowingComponent />
</span>
</div>,
);
expect(() => {
expect(ReactNoop.flushDeferredPri).toWarnDev(
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
' in ErrorThrowingComponent (at **)\n' +
' in span (at **)\n' +
' in div (at **)\n\n' +
'Consider adding an error boundary to your tree to customize error handling behavior.',
);
}).toThrowError('componentDidMount error');
});
it('should ignore errors thrown in log method to prevent cycle', () => {
jest.resetModules();
jest.mock('../ReactFiberErrorLogger');
try {
React = require('react');
ReactNoop = require('react-noop-renderer');
// TODO Update this test to use toWarnDev() matcher if possible
spyOnDevAndProd(console, 'error');
class ErrorThrowingComponent extends React.Component {
render() {
throw new Error('render error');
}
}
const logCapturedErrorCalls = [];
const ReactFiberErrorLogger = require('../ReactFiberErrorLogger');
ReactFiberErrorLogger.logCapturedError.mockImplementation(
capturedError => {
logCapturedErrorCalls.push(capturedError);
const error = new Error('logCapturedError error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
},
);
try {
ReactNoop.render(
<div>
<span>
<ErrorThrowingComponent />
</span>
</div>,
);
ReactNoop.flushDeferredPri();
} catch (error) {}
expect(logCapturedErrorCalls.length).toBe(1);
// The error thrown in logCapturedError should also be logged
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0].message).toContain(
'logCapturedError error',
);
} finally {
jest.unmock('../ReactFiberErrorLogger');
}
});
it('should relay info about error boundary and retry attempts if applicable', () => {
// Errors are redundantly logged in production mode by ReactFiberErrorLogger.
// It's okay to ignore them for the purpose of this test.
spyOnProd(console, 'error');
class ParentComponent extends React.Component {
render() {
return <ErrorBoundaryComponent />;
}
}
let handleErrorCalls = [];
let renderAttempts = 0;
class ErrorBoundaryComponent extends React.Component {
componentDidCatch(error) {
handleErrorCalls.push(error);
this.setState({}); // Render again
}
render() {
return <ErrorThrowingComponent />;
}
}
class ErrorThrowingComponent extends React.Component {
componentDidMount() {
const error = new Error('componentDidMount error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
}
render() {
renderAttempts++;
return <div />;
}
}
ReactNoop.render(<ParentComponent />);
expect(() => {
expect(ReactNoop.flush).toWarnDev([
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
' in ErrorThrowingComponent (at **)\n' +
' in ErrorBoundaryComponent (at **)\n' +
' in ParentComponent (at **)\n\n' +
'React will try to recreate this component tree from scratch ' +
'using the error boundary you provided, ErrorBoundaryComponent.',
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
' in ErrorThrowingComponent (at **)\n' +
' in ErrorBoundaryComponent (at **)\n' +
' in ParentComponent (at **)\n\n' +
'This error was initially handled by the error boundary ErrorBoundaryComponent.\n' +
'Recreating the tree from scratch failed so React will unmount the tree.',
]);
}).toThrowError('componentDidMount error');
expect(renderAttempts).toBe(2);
expect(handleErrorCalls.length).toBe(1);
});
});
it('resets instance variables before unmounting failed node', () => {
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
this.setState({error});
}
render() {
return this.state.error ? null : this.props.children;
}
}
class Foo extends React.Component {
state = {step: 0};
componentDidMount() {
this.setState({step: 1});
}
componentWillUnmount() {
ReactNoop.yield('componentWillUnmount: ' + this.state.step);
}
render() {
ReactNoop.yield('render: ' + this.state.step);
if (this.state.step > 0) {
throw new Error('oops');
}
return null;
}
}
ReactNoop.render(
<ErrorBoundary>
<Foo />
</ErrorBoundary>,
);
expect(ReactNoop.flush()).toEqual([
'render: 0',
'render: 1',
'componentWillUnmount: 0',
]);
});