Remove accidentally duplicated tests (#11675)
* Remove accidentally duplicated tests * Refactor: move unmock() call into the only test needing it This makes the intent more explicit.
Dan Abramov committed
Nov 27, 2017 at 22:38 UTC
878feebe34594fab111e47569a3a41a2f47199f1
1 file changed
+160
-1094
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.internal.js
+160
-1094
@@ -9,1052 +9,116 @@
9
10
'use strict';
11
12
-var PropTypes;
12
var React;
13
var ReactNoop;
14
15
describe('ReactIncrementalErrorLogging', () => {
16
beforeEach(() => {
17
jest.resetModules();
19
- PropTypes = require('prop-types');
18
React = require('react');
19
ReactNoop = require('react-noop-renderer');
20
});
21
24
- afterEach(() => {
25
- jest.unmock('../ReactFiberErrorLogger');
26
- });
27
-
28
- function div(...children) {
29
- children = children.map(c => (typeof c === 'string' ? {text: c} : c));
30
- return {type: 'div', children, prop: undefined};
31
- }
32
-
33
- function span(prop) {
34
- return {type: 'span', children: [], prop};
22
+ function normalizeCodeLocInfo(str) {
23
+ return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
24
}
25
37
- it('catches render error in a boundary during full deferred mounting', () => {
38
- class ErrorBoundary extends React.Component {
39
- state = {error: null};
40
- componentDidCatch(error) {
41
- this.setState({error});
42
- }
43
- render() {
44
- if (this.state.error) {
45
- return (
46
- <span prop={`Caught an error: ${this.state.error.message}.`} />
47
- );
48
- }
49
- return this.props.children;
50
- }
51
- }
52
-
53
- function BrokenRender(props) {
54
- throw new Error('Hello');
55
- }
56
-
57
- ReactNoop.render(
58
- <ErrorBoundary>
59
- <BrokenRender />
60
- </ErrorBoundary>,
61
- );
62
- ReactNoop.flushDeferredPri();
63
- expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]);
64
- });
65
-
66
- it('catches render error in a boundary during partial deferred mounting', () => {
67
- var ops = [];
68
- class ErrorBoundary extends React.Component {
69
- state = {error: null};
70
- componentDidCatch(error) {
71
- ops.push('ErrorBoundary componentDidCatch');
72
- this.setState({error});
73
- }
74
- render() {
75
- if (this.state.error) {
76
- ops.push('ErrorBoundary render error');
77
- return (
78
- <span prop={`Caught an error: ${this.state.error.message}.`} />
79
- );
80
- }
81
- ops.push('ErrorBoundary render success');
82
- return this.props.children;
83
- }
84
- }
85
-
86
- function BrokenRender(props) {
87
- ops.push('BrokenRender');
88
- throw new Error('Hello');
89
- }
90
-
91
- ReactNoop.render(
92
- <ErrorBoundary>
93
- <BrokenRender />
94
- </ErrorBoundary>,
95
- );
96
-
97
- ReactNoop.flushDeferredPri(15);
98
- expect(ops).toEqual(['ErrorBoundary render success']);
99
- expect(ReactNoop.getChildren()).toEqual([]);
100
-
101
- ops.length = 0;
102
- ReactNoop.flushDeferredPri(30);
103
- expect(ops).toEqual([
104
- 'BrokenRender',
105
- 'ErrorBoundary componentDidCatch',
106
- 'ErrorBoundary render error',
107
- ]);
108
- expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]);
109
- });
110
-
111
- it('catches render error in a boundary during synchronous mounting', () => {
112
- var ops = [];
113
- class ErrorBoundary extends React.Component {
114
- state = {error: null};
115
- componentDidCatch(error) {
116
- ops.push('ErrorBoundary componentDidCatch');
117
- this.setState({error});
26
+ it('should log errors that occur during the begin phase', () => {
27
+ // Intentionally spy in production too.
28
+ spyOn(console, 'error');
29
+
30
+ class ErrorThrowingComponent extends React.Component {
31
+ componentWillMount() {
32
+ const error = new Error('componentWillMount error');
33
+ // Note: it's `true` on the Error prototype our test environment.
34
+ // That lets us avoid asserting on warnings for each expected error.
35
+ // Here we intentionally shadow it to test logging, like in real apps.
36
+ error.suppressReactErrorLogging = undefined;
37
+ throw error;
38
}
39
render() {
120
- if (this.state.error) {
121
- ops.push('ErrorBoundary render error');
122
- return (
123
- <span prop={`Caught an error: ${this.state.error.message}.`} />
124
- );
125
- }
126
- ops.push('ErrorBoundary render success');
127
- return this.props.children;
40
+ return <div />;
41
}
42
}
43
131
- function BrokenRender(props) {
132
- ops.push('BrokenRender');
133
- throw new Error('Hello');
134
- }
135
-
136
- ReactNoop.flushSync(() => {
44
+ try {
45
ReactNoop.render(
138
- <ErrorBoundary>
139
- <BrokenRender />
140
- </ErrorBoundary>,
46
+ <div>
47
+ <span>
48
+ <ErrorThrowingComponent />
49
+ </span>
50
+ </div>,
51
);
142
- });
143
-
144
- expect(ops).toEqual([
145
- 'ErrorBoundary render success',
146
- 'BrokenRender',
147
- 'ErrorBoundary componentDidCatch',
148
- 'ErrorBoundary render error',
149
- ]);
150
- expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]);
151
- });
152
-
153
- it('catches render error in a boundary during batched mounting', () => {
154
- var ops = [];
155
- class ErrorBoundary extends React.Component {
156
- state = {error: null};
157
- componentDidCatch(error) {
158
- ops.push('ErrorBoundary componentDidCatch');
159
- this.setState({error});
160
- }
161
- render() {
162
- if (this.state.error) {
163
- ops.push('ErrorBoundary render error');
164
- return (
165
- <span prop={`Caught an error: ${this.state.error.message}.`} />
166
- );
167
- }
168
- ops.push('ErrorBoundary render success');
169
- return this.props.children;
170
- }
171
- }
52
+ ReactNoop.flushDeferredPri();
53
+ } catch (error) {}
54
173
- function BrokenRender(props) {
174
- ops.push('BrokenRender');
175
- throw new Error('Hello');
176
- }
177
-
178
- ReactNoop.flushSync(() => {
179
- ReactNoop.render(<ErrorBoundary>Before the storm.</ErrorBoundary>);
180
- ReactNoop.render(
181
- <ErrorBoundary>
182
- <BrokenRender />
183
- </ErrorBoundary>,
55
+ expect(console.error.calls.count()).toBe(1);
56
+ const errorMessage = console.error.calls.argsFor(0)[0];
57
+ if (__DEV__) {
58
+ expect(normalizeCodeLocInfo(errorMessage)).toContain(
59
+ 'The above error occurred in the <ErrorThrowingComponent> component:\n' +
60
+ ' in ErrorThrowingComponent (at **)\n' +
61
+ ' in span (at **)\n' +
62
+ ' in div (at **)',
63
);
185
- });
186
-
187
- expect(ops).toEqual([
188
- 'ErrorBoundary render success',
189
- 'BrokenRender',
190
- 'ErrorBoundary componentDidCatch',
191
- 'ErrorBoundary render error',
192
- ]);
193
- expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]);
194
- });
195
-
196
- it('propagates an error from a noop error boundary during full deferred mounting', () => {
197
- var ops = [];
198
- class RethrowErrorBoundary extends React.Component {
199
- componentDidCatch(error) {
200
- ops.push('RethrowErrorBoundary componentDidCatch');
201
- throw error;
202
- }
203
- render() {
204
- ops.push('RethrowErrorBoundary render');
205
- return this.props.children;
206
- }
207
- }
208
-
209
- function BrokenRender() {
210
- ops.push('BrokenRender');
211
- throw new Error('Hello');
212
- }
213
-
214
- ReactNoop.render(
215
- <RethrowErrorBoundary>
216
- <BrokenRender />
217
- </RethrowErrorBoundary>,
218
- );
219
-
220
- expect(() => {
221
- ReactNoop.flush();
222
- }).toThrow('Hello');
223
- expect(ops).toEqual([
224
- 'RethrowErrorBoundary render',
225
- 'BrokenRender',
226
- 'RethrowErrorBoundary componentDidCatch',
227
- ]);
228
- expect(ReactNoop.getChildren()).toEqual([]);
229
- });
230
-
231
- it('propagates an error from a noop error boundary during partial deferred mounting', () => {
232
- var ops = [];
233
- class RethrowErrorBoundary extends React.Component {
234
- componentDidCatch(error) {
235
- ops.push('RethrowErrorBoundary componentDidCatch');
236
- throw error;
237
- }
238
- render() {
239
- ops.push('RethrowErrorBoundary render');
240
- return this.props.children;
241
- }
242
- }
243
-
244
- function BrokenRender() {
245
- ops.push('BrokenRender');
246
- throw new Error('Hello');
64
+ expect(errorMessage).toContain(
65
+ 'Consider adding an error boundary to your tree to customize error handling behavior.',
66
+ );
67
+ } else {
68
+ expect(errorMessage.message).toContain('componentWillMount error');
69
}
248
-
249
- ReactNoop.render(
250
- <RethrowErrorBoundary>
251
- <BrokenRender />
252
- </RethrowErrorBoundary>,
253
- );
254
-
255
- ReactNoop.flushDeferredPri(15);
256
- expect(ops).toEqual(['RethrowErrorBoundary render']);
257
-
258
- ops.length = 0;
259
- expect(() => {
260
- ReactNoop.flush();
261
- }).toThrow('Hello');
262
- expect(ops).toEqual([
263
- 'BrokenRender',
264
- 'RethrowErrorBoundary componentDidCatch',
265
- ]);
266
- expect(ReactNoop.getChildren()).toEqual([]);
70
});
71
269
- it('propagates an error from a noop error boundary during synchronous mounting', () => {
270
- var ops = [];
271
- class RethrowErrorBoundary extends React.Component {
272
- componentDidCatch(error) {
273
- ops.push('RethrowErrorBoundary componentDidCatch');
274
- throw error;
275
- }
276
- render() {
277
- ops.push('RethrowErrorBoundary render');
278
- return this.props.children;
279
- }
280
- }
281
-
282
- function BrokenRender() {
283
- ops.push('BrokenRender');
284
- throw new Error('Hello');
285
- }
72
+ it('should log errors that occur during the commit phase', () => {
73
+ // Intentionally spy in production too.
74
+ spyOn(console, 'error');
75
287
- expect(() => {
288
- ReactNoop.flushSync(() => {
289
- ReactNoop.render(
290
- <RethrowErrorBoundary>
291
- <BrokenRender />
292
- </RethrowErrorBoundary>,
293
- );
294
- });
295
- }).toThrow('Hello');
296
- expect(ops).toEqual([
297
- 'RethrowErrorBoundary render',
298
- 'BrokenRender',
299
- 'RethrowErrorBoundary componentDidCatch',
300
- ]);
301
- expect(ReactNoop.getChildren()).toEqual([]);
302
- });
303
-
304
- it('propagates an error from a noop error boundary during batched mounting', () => {
305
- var ops = [];
306
- class RethrowErrorBoundary extends React.Component {
307
- componentDidCatch(error) {
308
- ops.push('RethrowErrorBoundary componentDidCatch');
76
+ class ErrorThrowingComponent extends React.Component {
77
+ componentDidMount() {
78
+ const error = new Error('componentDidMount error');
79
+ // Note: it's `true` on the Error prototype our test environment.
80
+ // That lets us avoid asserting on warnings for each expected error.
81
+ // Here we intentionally shadow it to test logging, like in real apps.
82
+ error.suppressReactErrorLogging = undefined;
83
throw error;
84
}
311
- render() {
312
- ops.push('RethrowErrorBoundary render');
313
- return this.props.children;
314
- }
315
- }
316
-
317
- function BrokenRender() {
318
- ops.push('BrokenRender');
319
- throw new Error('Hello');
320
- }
321
-
322
- expect(() => {
323
- ReactNoop.flushSync(() => {
324
- ReactNoop.render(
325
- <RethrowErrorBoundary>Before the storm.</RethrowErrorBoundary>,
326
- );
327
- ReactNoop.render(
328
- <RethrowErrorBoundary>
329
- <BrokenRender />
330
- </RethrowErrorBoundary>,
331
- );
332
- });
333
- }).toThrow('Hello');
334
- expect(ops).toEqual([
335
- 'RethrowErrorBoundary render',
336
- 'BrokenRender',
337
- 'RethrowErrorBoundary componentDidCatch',
338
- ]);
339
- expect(ReactNoop.getChildren()).toEqual([]);
340
- });
341
-
342
- it('applies batched updates regardless despite errors in scheduling', () => {
343
- ReactNoop.render(<span prop="a:1" />);
344
- expect(() => {
345
- ReactNoop.batchedUpdates(() => {
346
- ReactNoop.render(<span prop="a:2" />);
347
- ReactNoop.render(<span prop="a:3" />);
348
- throw new Error('Hello');
349
- });
350
- }).toThrow('Hello');
351
- ReactNoop.flush();
352
- expect(ReactNoop.getChildren()).toEqual([span('a:3')]);
353
- });
354
-
355
- it('applies nested batched updates despite errors in scheduling', () => {
356
- ReactNoop.render(<span prop="a:1" />);
357
- expect(() => {
358
- ReactNoop.batchedUpdates(() => {
359
- ReactNoop.render(<span prop="a:2" />);
360
- ReactNoop.render(<span prop="a:3" />);
361
- ReactNoop.batchedUpdates(() => {
362
- ReactNoop.render(<span prop="a:4" />);
363
- ReactNoop.render(<span prop="a:5" />);
364
- throw new Error('Hello');
365
- });
366
- });
367
- }).toThrow('Hello');
368
- ReactNoop.flush();
369
- expect(ReactNoop.getChildren()).toEqual([span('a:5')]);
370
- });
371
-
372
- it('applies sync updates regardless despite errors in scheduling', () => {
373
- ReactNoop.render(<span prop="a:1" />);
374
- expect(() => {
375
- ReactNoop.flushSync(() => {
376
- ReactNoop.batchedUpdates(() => {
377
- ReactNoop.render(<span prop="a:2" />);
378
- ReactNoop.render(<span prop="a:3" />);
379
- throw new Error('Hello');
380
- });
381
- });
382
- }).toThrow('Hello');
383
- expect(ReactNoop.getChildren()).toEqual([span('a:3')]);
384
- });
385
-
386
- it('can schedule updates after uncaught error in render on mount', () => {
387
- var ops = [];
388
-
389
- function BrokenRender() {
390
- ops.push('BrokenRender');
391
- throw new Error('Hello');
392
- }
393
-
394
- function Foo() {
395
- ops.push('Foo');
396
- return null;
397
- }
398
-
399
- ReactNoop.render(<BrokenRender />);
400
- expect(() => {
401
- ReactNoop.flush();
402
- }).toThrow('Hello');
403
- expect(ops).toEqual(['BrokenRender']);
404
-
405
- ops = [];
406
- ReactNoop.render(<Foo />);
407
- ReactNoop.flush();
408
- expect(ops).toEqual(['Foo']);
409
- });
410
-
411
- it('can schedule updates after uncaught error in render on update', () => {
412
- var ops = [];
413
-
414
- function BrokenRender(props) {
415
- ops.push('BrokenRender');
416
- if (props.throw) {
417
- throw new Error('Hello');
418
- }
419
- return null;
420
- }
421
-
422
- function Foo() {
423
- ops.push('Foo');
424
- return null;
425
- }
426
-
427
- ReactNoop.render(<BrokenRender throw={false} />);
428
- ReactNoop.flush();
429
- ops = [];
430
-
431
- expect(() => {
432
- ReactNoop.render(<BrokenRender throw={true} />);
433
- ReactNoop.flush();
434
- }).toThrow('Hello');
435
- expect(ops).toEqual(['BrokenRender']);
436
-
437
- ops = [];
438
- ReactNoop.render(<Foo />);
439
- ReactNoop.flush();
440
- expect(ops).toEqual(['Foo']);
441
- });
442
-
443
- it('can schedule updates after uncaught error during umounting', () => {
444
- var ops = [];
445
-
446
- class BrokenComponentWillUnmount extends React.Component {
85
render() {
86
return <div />;
87
}
450
- componentWillUnmount() {
451
- throw new Error('Hello');
452
- }
453
- }
454
-
455
- function Foo() {
456
- ops.push('Foo');
457
- return null;
458
- }
459
-
460
- ReactNoop.render(<BrokenComponentWillUnmount />);
461
- ReactNoop.flush();
462
-
463
- expect(() => {
464
- ReactNoop.render(<div />);
465
- ReactNoop.flush();
466
- }).toThrow('Hello');
467
-
468
- ops = [];
469
- ReactNoop.render(<Foo />);
470
- ReactNoop.flush();
471
- expect(ops).toEqual(['Foo']);
472
- });
473
-
474
- it('should not attempt to recover an unmounting error boundary', () => {
475
- class Parent extends React.Component {
476
- componentWillUnmount() {
477
- ReactNoop.yield('Parent componentWillUnmount');
478
- }
479
- render() {
480
- return <Boundary />;
481
- }
482
- }
483
-
484
- class Boundary extends React.Component {
485
- componentDidCatch(e) {
486
- ReactNoop.yield(`Caught error: ${e.message}`);
487
- }
488
- render() {
489
- return <ThrowsOnUnmount />;
490
- }
491
- }
492
-
493
- class ThrowsOnUnmount extends React.Component {
494
- componentWillUnmount() {
495
- ReactNoop.yield('ThrowsOnUnmount componentWillUnmount');
496
- throw new Error('unmount error');
497
- }
498
- render() {
499
- return null;
500
- }
501
- }
502
-
503
- ReactNoop.render(<Parent />);
504
- ReactNoop.flush();
505
- ReactNoop.render(null);
506
- expect(ReactNoop.flush()).toEqual([
507
- // Parent unmounts before the error is thrown.
508
- 'Parent componentWillUnmount',
509
- 'ThrowsOnUnmount componentWillUnmount',
510
- ]);
511
- ReactNoop.render(<Parent />);
512
- });
513
-
514
- it('can unmount an error boundary before it is handled', () => {
515
- let parent;
516
-
517
- class Parent extends React.Component {
518
- state = {step: 0};
519
- render() {
520
- parent = this;
521
- return this.state.step === 0 ? <Boundary /> : null;
522
- }
523
- }
524
-
525
- class Boundary extends React.Component {
526
- componentDidCatch() {}
527
- render() {
528
- return <Child />;
529
- }
530
- }
531
-
532
- class Child extends React.Component {
533
- componentDidUpdate() {
534
- parent.setState({step: 1});
535
- throw new Error('update error');
536
- }
537
- render() {
538
- return null;
539
- }
540
- }
541
-
542
- ReactNoop.render(<Parent />);
543
- ReactNoop.flush();
544
-
545
- ReactNoop.flushSync(() => {
546
- ReactNoop.render(<Parent />);
547
- });
548
- });
549
-
550
- it('continues work on other roots despite caught errors', () => {
551
- class ErrorBoundary extends React.Component {
552
- state = {error: null};
553
- componentDidCatch(error) {
554
- this.setState({error});
555
- }
556
- render() {
557
- if (this.state.error) {
558
- return (
559
- <span prop={`Caught an error: ${this.state.error.message}.`} />
560
- );
561
- }
562
- return this.props.children;
563
- }
564
- }
565
-
566
- function BrokenRender(props) {
567
- throw new Error('Hello');
568
- }
569
-
570
- ReactNoop.renderToRootWithID(
571
- <ErrorBoundary>
572
- <BrokenRender />
573
- </ErrorBoundary>,
574
- 'a',
575
- );
576
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
577
- ReactNoop.flush();
578
- expect(ReactNoop.getChildren('a')).toEqual([
579
- span('Caught an error: Hello.'),
580
- ]);
581
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
582
- });
583
-
584
- it('continues work on other roots despite uncaught errors', () => {
585
- function BrokenRender(props) {
586
- throw new Error('Hello');
587
- }
588
-
589
- ReactNoop.renderToRootWithID(<BrokenRender />, 'a');
590
- expect(() => {
591
- ReactNoop.flush();
592
- }).toThrow('Hello');
593
- expect(ReactNoop.getChildren('a')).toEqual([]);
594
-
595
- ReactNoop.renderToRootWithID(<BrokenRender />, 'a');
596
- ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
597
- expect(() => {
598
- ReactNoop.flush();
599
- }).toThrow('Hello');
600
-
601
- expect(ReactNoop.getChildren('a')).toEqual([]);
602
- expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
603
-
604
- ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
605
- ReactNoop.renderToRootWithID(<BrokenRender />, 'b');
606
- expect(() => {
607
- ReactNoop.flush();
608
- }).toThrow('Hello');
609
- expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
610
- expect(ReactNoop.getChildren('b')).toEqual([]);
611
-
612
- ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
613
- ReactNoop.renderToRootWithID(<BrokenRender />, 'b');
614
- ReactNoop.renderToRootWithID(<span prop="c:4" />, 'c');
615
- expect(() => {
616
- ReactNoop.flush();
617
- }).toThrow('Hello');
618
- expect(ReactNoop.getChildren('a')).toEqual([span('a:4')]);
619
- expect(ReactNoop.getChildren('b')).toEqual([]);
620
- expect(ReactNoop.getChildren('c')).toEqual([span('c:4')]);
621
-
622
- ReactNoop.renderToRootWithID(<span prop="a:5" />, 'a');
623
- ReactNoop.renderToRootWithID(<span prop="b:5" />, 'b');
624
- ReactNoop.renderToRootWithID(<span prop="c:5" />, 'c');
625
- ReactNoop.renderToRootWithID(<span prop="d:5" />, 'd');
626
- ReactNoop.renderToRootWithID(<BrokenRender />, 'e');
627
- expect(() => {
628
- ReactNoop.flush();
629
- }).toThrow('Hello');
630
- expect(ReactNoop.getChildren('a')).toEqual([span('a:5')]);
631
- expect(ReactNoop.getChildren('b')).toEqual([span('b:5')]);
632
- expect(ReactNoop.getChildren('c')).toEqual([span('c:5')]);
633
- expect(ReactNoop.getChildren('d')).toEqual([span('d:5')]);
634
- expect(ReactNoop.getChildren('e')).toEqual([]);
635
-
636
- ReactNoop.renderToRootWithID(<BrokenRender />, 'a');
637
- ReactNoop.renderToRootWithID(<span prop="b:6" />, 'b');
638
- ReactNoop.renderToRootWithID(<BrokenRender />, 'c');
639
- ReactNoop.renderToRootWithID(<span prop="d:6" />, 'd');
640
- ReactNoop.renderToRootWithID(<BrokenRender />, 'e');
641
- ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
642
- expect(() => {
643
- ReactNoop.flush();
644
- }).toThrow('Hello');
645
- expect(ReactNoop.getChildren('a')).toEqual([]);
646
- expect(ReactNoop.getChildren('b')).toEqual([span('b:6')]);
647
- expect(ReactNoop.getChildren('c')).toEqual([]);
648
- expect(ReactNoop.getChildren('d')).toEqual([span('d:6')]);
649
- expect(ReactNoop.getChildren('e')).toEqual([]);
650
- expect(ReactNoop.getChildren('f')).toEqual([span('f:6')]);
651
-
652
- ReactNoop.unmountRootWithID('a');
653
- ReactNoop.unmountRootWithID('b');
654
- ReactNoop.unmountRootWithID('c');
655
- ReactNoop.unmountRootWithID('d');
656
- ReactNoop.unmountRootWithID('e');
657
- ReactNoop.unmountRootWithID('f');
658
- ReactNoop.flush();
659
- expect(ReactNoop.getChildren('a')).toEqual(null);
660
- expect(ReactNoop.getChildren('b')).toEqual(null);
661
- expect(ReactNoop.getChildren('c')).toEqual(null);
662
- expect(ReactNoop.getChildren('d')).toEqual(null);
663
- expect(ReactNoop.getChildren('e')).toEqual(null);
664
- expect(ReactNoop.getChildren('f')).toEqual(null);
665
- });
666
-
667
- it('unwinds the context stack correctly on error', () => {
668
- class Provider extends React.Component {
669
- static childContextTypes = {message: PropTypes.string};
670
- static contextTypes = {message: PropTypes.string};
671
- getChildContext() {
672
- return {
673
- message: (this.context.message || '') + this.props.message,
674
- };
675
- }
676
- render() {
677
- return this.props.children;
678
- }
679
- }
680
-
681
- function Connector(props, context) {
682
- return <span prop={context.message} />;
683
- }
684
-
685
- Connector.contextTypes = {
686
- message: PropTypes.string,
687
- };
688
-
689
- function BadRender() {
690
- throw new Error('render error');
691
- }
692
-
693
- class Boundary extends React.Component {
694
- state = {error: null};
695
- componentDidCatch(error) {
696
- this.setState({error});
697
- }
698
- render() {
699
- return (
700
- <Provider message="b">
701
- <Provider message="c">
702
- <Provider message="d">
703
- <Provider message="e">
704
- {!this.state.error && <BadRender />}
705
- </Provider>
706
- </Provider>
707
- </Provider>
708
- </Provider>
709
- );
710
- }
88
}
89
713
- ReactNoop.render(
714
- <Provider message="a">
715
- <Boundary />
716
- <Connector />
717
- </Provider>,
718
- );
719
- ReactNoop.flush();
720
-
721
- // If the context stack does not unwind, span will get 'abcde'
722
- expect(ReactNoop.getChildren()).toEqual([span('a')]);
723
- });
724
-
725
- it('catches reconciler errors in a boundary during mounting', () => {
726
- spyOnDev(console, 'error');
727
-
728
- class ErrorBoundary extends React.Component {
729
- state = {error: null};
730
- componentDidCatch(error) {
731
- this.setState({error});
732
- }
733
- render() {
734
- if (this.state.error) {
735
- return <span prop={this.state.error.message} />;
736
- }
737
- return this.props.children;
738
- }
739
- }
740
- const InvalidType = undefined;
741
- function BrokenRender(props) {
742
- return <InvalidType />;
743
- }
744
-
745
- ReactNoop.render(
746
- <ErrorBoundary>
747
- <BrokenRender />
748
- </ErrorBoundary>,
749
- );
750
- ReactNoop.flush();
751
- expect(ReactNoop.getChildren()).toEqual([
752
- span(
753
- 'Element type is invalid: expected a string (for built-in components) or ' +
754
- 'a class/function (for composite components) but got: undefined.' +
755
- (__DEV__
756
- ? " You likely forgot to export your component from the file it's " +
757
- 'defined in, or you might have mixed up default and named imports.' +
758
- '\n\nCheck the render method of `BrokenRender`.'
759
- : ''),
760
- ),
761
- ]);
762
- if (__DEV__) {
763
- expect(console.error.calls.count()).toBe(1);
764
- expect(console.error.calls.argsFor(0)[0]).toContain(
765
- 'Warning: React.createElement: type is invalid -- expected a string',
90
+ try {
91
+ ReactNoop.render(
92
+ <div>
93
+ <span>
94
+ <ErrorThrowingComponent />
95
+ </span>
96
+ </div>,
97
);
767
- }
768
- });
769
-
770
- it('catches reconciler errors in a boundary during update', () => {
771
- spyOnDev(console, 'error');
98
+ ReactNoop.flushDeferredPri();
99
+ } catch (error) {}
100
773
- class ErrorBoundary extends React.Component {
774
- state = {error: null};
775
- componentDidCatch(error) {
776
- this.setState({error});
777
- }
778
- render() {
779
- if (this.state.error) {
780
- return <span prop={this.state.error.message} />;
781
- }
782
- return this.props.children;
783
- }
784
- }
785
-
786
- const InvalidType = undefined;
787
- function BrokenRender(props) {
788
- return props.fail ? <InvalidType /> : <span />;
789
- }
790
-
791
- ReactNoop.render(
792
- <ErrorBoundary>
793
- <BrokenRender fail={false} />
794
- </ErrorBoundary>,
795
- );
796
- ReactNoop.flush();
797
-
798
- ReactNoop.render(
799
- <ErrorBoundary>
800
- <BrokenRender fail={true} />
801
- </ErrorBoundary>,
802
- );
803
- ReactNoop.flush();
804
- expect(ReactNoop.getChildren()).toEqual([
805
- span(
806
- 'Element type is invalid: expected a string (for built-in components) or ' +
807
- 'a class/function (for composite components) but got: undefined.' +
808
- (__DEV__
809
- ? " You likely forgot to export your component from the file it's " +
810
- 'defined in, or you might have mixed up default and named imports.' +
811
- '\n\nCheck the render method of `BrokenRender`.'
812
- : ''),
813
- ),
814
- ]);
101
+ expect(console.error.calls.count()).toBe(1);
102
+ const errorMessage = console.error.calls.argsFor(0)[0];
103
if (__DEV__) {
816
- expect(console.error.calls.count()).toBe(1);
817
- expect(console.error.calls.argsFor(0)[0]).toContain(
818
- 'Warning: React.createElement: type is invalid -- expected a string',
104
+ expect(normalizeCodeLocInfo(errorMessage)).toContain(
105
+ 'The above error occurred in the <ErrorThrowingComponent> component:\n' +
106
+ ' in ErrorThrowingComponent (at **)\n' +
107
+ ' in span (at **)\n' +
108
+ ' in div (at **)',
109
);
820
- }
821
- });
822
-
823
- it('recovers from uncaught reconciler errors', () => {
824
- spyOnDev(console, 'error');
825
- const InvalidType = undefined;
826
- ReactNoop.render(<InvalidType />);
827
- expect(() => {
828
- ReactNoop.flush();
829
- }).toThrowError(
830
- 'Element type is invalid: expected a string (for built-in components) or ' +
831
- 'a class/function (for composite components) but got: undefined.' +
832
- (__DEV__
833
- ? " You likely forgot to export your component from the file it's " +
834
- 'defined in, or you might have mixed up default and named imports.'
835
- : ''),
836
- );
837
-
838
- ReactNoop.render(<span prop="hi" />);
839
- ReactNoop.flush();
840
- expect(ReactNoop.getChildren()).toEqual([span('hi')]);
841
- if (__DEV__) {
842
- expect(console.error.calls.count()).toBe(1);
843
- expect(console.error.calls.argsFor(0)[0]).toContain(
844
- 'Warning: React.createElement: type is invalid -- expected a string',
110
+ expect(errorMessage).toContain(
111
+ 'Consider adding an error boundary to your tree to customize error handling behavior.',
112
);
113
+ } else {
114
+ expect(errorMessage.message).toBe('componentDidMount error');
115
}
116
});
117
849
- it('unmounts components with uncaught errors', () => {
850
- const ops = [];
851
- let inst;
852
-
853
- class BrokenRenderAndUnmount extends React.Component {
854
- state = {fail: false};
855
- componentWillUnmount() {
856
- ops.push('BrokenRenderAndUnmount componentWillUnmount');
857
- }
858
- render() {
859
- inst = this;
860
- if (this.state.fail) {
861
- throw new Error('Hello.');
862
- }
863
- return null;
864
- }
865
- }
866
-
867
- class Parent extends React.Component {
868
- componentWillUnmount() {
869
- ops.push('Parent componentWillUnmount [!]');
870
- throw new Error('One does not simply unmount me.');
871
- }
872
- render() {
873
- return this.props.children;
874
- }
875
- }
876
-
877
- ReactNoop.render(
878
- <Parent>
879
- <Parent>
880
- <BrokenRenderAndUnmount />
881
- </Parent>
882
- </Parent>,
883
- );
884
- ReactNoop.flush();
885
-
886
- inst.setState({fail: true});
887
- expect(() => {
888
- ReactNoop.flush();
889
- }).toThrowError('Hello.');
890
-
891
- expect(ops).toEqual([
892
- // Attempt to clean up.
893
- // Errors in parents shouldn't stop children from unmounting.
894
- 'Parent componentWillUnmount [!]',
895
- 'Parent componentWillUnmount [!]',
896
- 'BrokenRenderAndUnmount componentWillUnmount',
897
- ]);
898
- expect(ReactNoop.getChildren()).toEqual([]);
899
- });
900
-
901
- it('does not interrupt unmounting if detaching a ref throws', () => {
902
- var ops = [];
903
-
904
- class Bar extends React.Component {
905
- componentWillUnmount() {
906
- ops.push('Bar unmount');
907
- }
908
- render() {
909
- return <span prop="Bar" />;
910
- }
911
- }
912
-
913
- function barRef(inst) {
914
- if (inst === null) {
915
- ops.push('barRef detach');
916
- throw new Error('Detach error');
917
- }
918
- ops.push('barRef attach');
919
- }
920
-
921
- function Foo(props) {
922
- return <div>{props.hide ? null : <Bar ref={barRef} />}</div>;
923
- }
924
-
925
- ReactNoop.render(<Foo />);
926
- ReactNoop.flush();
927
- expect(ops).toEqual(['barRef attach']);
928
- expect(ReactNoop.getChildren()).toEqual([div(span('Bar'))]);
929
-
930
- ops = [];
931
-
932
- // Unmount
933
- ReactNoop.render(<Foo hide={true} />);
934
- expect(() => ReactNoop.flush()).toThrow('Detach error');
935
- expect(ops).toEqual([
936
- 'barRef detach',
937
- // Bar should unmount even though its ref threw an error while detaching
938
- 'Bar unmount',
939
- ]);
940
- // Because there was an error, entire tree should unmount
941
- expect(ReactNoop.getChildren()).toEqual([]);
942
- });
943
-
944
- it('handles error thrown by host config while working on failed root', () => {
945
- ReactNoop.simulateErrorInHostConfig(() => {
946
- ReactNoop.render(<span />);
947
- expect(() => ReactNoop.flush()).toThrow('Error in host config.');
948
- });
949
- });
950
-
951
- it('handles error thrown by top-level callback', () => {
952
- ReactNoop.render(<div />, () => {
953
- throw new Error('Error!');
954
- });
955
- expect(() => ReactNoop.flush()).toThrow('Error!');
956
- });
957
-
958
- describe('ReactFiberErrorLogger', () => {
959
- function normalizeCodeLocInfo(str) {
960
- return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
961
- }
962
-
963
- it('should log errors that occur during the begin phase', () => {
964
- // Intentionally spy in production too.
965
- spyOn(console, 'error');
966
-
967
- class ErrorThrowingComponent extends React.Component {
968
- componentWillMount() {
969
- const error = new Error('componentWillMount error');
970
- // Note: it's `true` on the Error prototype our test environment.
971
- // That lets us avoid asserting on warnings for each expected error.
972
- // Here we intentionally shadow it to test logging, like in real apps.
973
- error.suppressReactErrorLogging = undefined;
974
- throw error;
975
- }
976
- render() {
977
- return <div />;
978
- }
979
- }
980
-
981
- try {
982
- ReactNoop.render(
983
- <div>
984
- <span>
985
- <ErrorThrowingComponent />
986
- </span>
987
- </div>,
988
- );
989
- ReactNoop.flushDeferredPri();
990
- } catch (error) {}
991
-
992
- expect(console.error.calls.count()).toBe(1);
993
- const errorMessage = console.error.calls.argsFor(0)[0];
994
- if (__DEV__) {
995
- expect(normalizeCodeLocInfo(errorMessage)).toContain(
996
- 'The above error occurred in the <ErrorThrowingComponent> component:\n' +
997
- ' in ErrorThrowingComponent (at **)\n' +
998
- ' in span (at **)\n' +
999
- ' in div (at **)',
1000
- );
1001
- expect(errorMessage).toContain(
1002
- 'Consider adding an error boundary to your tree to customize error handling behavior.',
1003
- );
1004
- } else {
1005
- expect(errorMessage.message).toContain('componentWillMount error');
1006
- }
1007
- });
1008
-
1009
- it('should log errors that occur during the commit phase', () => {
1010
- // Intentionally spy in production too.
1011
- spyOn(console, 'error');
1012
-
1013
- class ErrorThrowingComponent extends React.Component {
1014
- componentDidMount() {
1015
- const error = new Error('componentDidMount error');
1016
- // Note: it's `true` on the Error prototype our test environment.
1017
- // That lets us avoid asserting on warnings for each expected error.
1018
- // Here we intentionally shadow it to test logging, like in real apps.
1019
- error.suppressReactErrorLogging = undefined;
1020
- throw error;
1021
- }
1022
- render() {
1023
- return <div />;
1024
- }
1025
- }
1026
-
1027
- try {
1028
- ReactNoop.render(
1029
- <div>
1030
- <span>
1031
- <ErrorThrowingComponent />
1032
- </span>
1033
- </div>,
1034
- );
1035
- ReactNoop.flushDeferredPri();
1036
- } catch (error) {}
1037
-
1038
- expect(console.error.calls.count()).toBe(1);
1039
- const errorMessage = console.error.calls.argsFor(0)[0];
1040
- if (__DEV__) {
1041
- expect(normalizeCodeLocInfo(errorMessage)).toContain(
1042
- 'The above error occurred in the <ErrorThrowingComponent> component:\n' +
1043
- ' in ErrorThrowingComponent (at **)\n' +
1044
- ' in span (at **)\n' +
1045
- ' in div (at **)',
1046
- );
1047
- expect(errorMessage).toContain(
1048
- 'Consider adding an error boundary to your tree to customize error handling behavior.',
1049
- );
1050
- } else {
1051
- expect(errorMessage.message).toBe('componentDidMount error');
1052
- }
1053
- });
1054
-
1055
- it('should ignore errors thrown in log method to prevent cycle', () => {
1056
- jest.resetModules();
1057
- jest.mock('../ReactFiberErrorLogger');
118
+ it('should ignore errors thrown in log method to prevent cycle', () => {
119
+ jest.resetModules();
120
+ jest.mock('../ReactFiberErrorLogger');
121
+ try {
122
React = require('react');
123
ReactNoop = require('react-noop-renderer');
124
// Intentionally spy in production too.
@@ -1099,109 +163,111 @@ describe('ReactIncrementalErrorLogging', () => {
163
expect(console.error.calls.argsFor(0)[0].message).toContain(
164
'logCapturedError error',
165
);
1102
- });
1103
-
1104
- it('should relay info about error boundary and retry attempts if applicable', () => {
1105
- // Intentionally spy in production too.
1106
- spyOn(console, 'error');
1107
-
1108
- class ParentComponent extends React.Component {
1109
- render() {
1110
- return <ErrorBoundaryComponent />;
1111
- }
1112
- }
1113
-
1114
- let handleErrorCalls = [];
1115
- let renderAttempts = 0;
166
+ } finally {
167
+ jest.unmock('../ReactFiberErrorLogger');
168
+ }
169
+ });
170
1117
- class ErrorBoundaryComponent extends React.Component {
1118
- componentDidCatch(error) {
1119
- handleErrorCalls.push(error);
1120
- this.setState({}); // Render again
1121
- }
1122
- render() {
1123
- return <ErrorThrowingComponent />;
1124
- }
1125
- }
171
+ it('should relay info about error boundary and retry attempts if applicable', () => {
172
+ // Intentionally spy in production too.
173
+ spyOn(console, 'error');
174
1127
- class ErrorThrowingComponent extends React.Component {
1128
- componentDidMount() {
1129
- const error = new Error('componentDidMount error');
1130
- // Note: it's `true` on the Error prototype our test environment.
1131
- // That lets us avoid asserting on warnings for each expected error.
1132
- // Here we intentionally shadow it to test logging, like in real apps.
1133
- error.suppressReactErrorLogging = undefined;
1134
- throw error;
1135
- }
1136
- render() {
1137
- renderAttempts++;
1138
- return <div />;
1139
- }
175
+ class ParentComponent extends React.Component {
176
+ render() {
177
+ return <ErrorBoundaryComponent />;
178
}
179
+ }
180
1142
- try {
1143
- ReactNoop.render(<ParentComponent />);
1144
- ReactNoop.flush();
1145
- } catch (error) {}
181
+ let handleErrorCalls = [];
182
+ let renderAttempts = 0;
183
1147
- expect(renderAttempts).toBe(2);
1148
- expect(handleErrorCalls.length).toBe(1);
1149
- expect(console.error.calls.count()).toBe(2);
1150
- if (__DEV__) {
1151
- expect(console.error.calls.argsFor(0)[0]).toContain(
1152
- 'The above error occurred in the <ErrorThrowingComponent> component:',
1153
- );
1154
- expect(console.error.calls.argsFor(0)[0]).toContain(
1155
- 'React will try to recreate this component tree from scratch ' +
1156
- 'using the error boundary you provided, ErrorBoundaryComponent.',
1157
- );
1158
- expect(console.error.calls.argsFor(1)[0]).toContain(
1159
- 'The above error occurred in the <ErrorThrowingComponent> component:',
1160
- );
1161
- expect(console.error.calls.argsFor(1)[0]).toContain(
1162
- 'This error was initially handled by the error boundary ErrorBoundaryComponent.\n' +
1163
- 'Recreating the tree from scratch failed so React will unmount the tree.',
1164
- );
1165
- }
1166
- });
1167
- });
1168
-
1169
- it('resets instance variables before unmounting failed node', () => {
1170
- class ErrorBoundary extends React.Component {
1171
- state = {error: null};
184
+ class ErrorBoundaryComponent extends React.Component {
185
componentDidCatch(error) {
1173
- this.setState({error});
186
+ handleErrorCalls.push(error);
187
+ this.setState({}); // Render again
188
}
189
render() {
1176
- return this.state.error ? null : this.props.children;
190
+ return <ErrorThrowingComponent />;
191
}
192
}
1179
- class Foo extends React.Component {
1180
- state = {step: 0};
193
+
194
+ class ErrorThrowingComponent extends React.Component {
195
componentDidMount() {
1182
- this.setState({step: 1});
1183
- }
1184
- componentWillUnmount() {
1185
- ReactNoop.yield('componentWillUnmount: ' + this.state.step);
196
+ const error = new Error('componentDidMount error');
197
+ // Note: it's `true` on the Error prototype our test environment.
198
+ // That lets us avoid asserting on warnings for each expected error.
199
+ // Here we intentionally shadow it to test logging, like in real apps.
200
+ error.suppressReactErrorLogging = undefined;
201
+ throw error;
202
}
203
render() {
1188
- ReactNoop.yield('render: ' + this.state.step);
1189
- if (this.state.step > 0) {
1190
- throw new Error('oops');
1191
- }
1192
- return null;
204
+ renderAttempts++;
205
+ return <div />;
206
}
207
}
208
1196
- ReactNoop.render(
1197
- <ErrorBoundary>
1198
- <Foo />
1199
- </ErrorBoundary>,
1200
- );
1201
- expect(ReactNoop.flush()).toEqual([
1202
- 'render: 0',
1203
- 'render: 1',
1204
- 'componentWillUnmount: 0',
1205
- ]);
209
+ try {
210
+ ReactNoop.render(<ParentComponent />);
211
+ ReactNoop.flush();
212
+ } catch (error) {}
213
+
214
+ expect(renderAttempts).toBe(2);
215
+ expect(handleErrorCalls.length).toBe(1);
216
+ expect(console.error.calls.count()).toBe(2);
217
+ if (__DEV__) {
218
+ expect(console.error.calls.argsFor(0)[0]).toContain(
219
+ 'The above error occurred in the <ErrorThrowingComponent> component:',
220
+ );
221
+ expect(console.error.calls.argsFor(0)[0]).toContain(
222
+ 'React will try to recreate this component tree from scratch ' +
223
+ 'using the error boundary you provided, ErrorBoundaryComponent.',
224
+ );
225
+ expect(console.error.calls.argsFor(1)[0]).toContain(
226
+ 'The above error occurred in the <ErrorThrowingComponent> component:',
227
+ );
228
+ expect(console.error.calls.argsFor(1)[0]).toContain(
229
+ 'This error was initially handled by the error boundary ErrorBoundaryComponent.\n' +
230
+ 'Recreating the tree from scratch failed so React will unmount the tree.',
231
+ );
232
+ }
233
});
234
});
235
+
236
+it('resets instance variables before unmounting failed node', () => {
237
+ class ErrorBoundary extends React.Component {
238
+ state = {error: null};
239
+ componentDidCatch(error) {
240
+ this.setState({error});
241
+ }
242
+ render() {
243
+ return this.state.error ? null : this.props.children;
244
+ }
245
+ }
246
+ class Foo extends React.Component {
247
+ state = {step: 0};
248
+ componentDidMount() {
249
+ this.setState({step: 1});
250
+ }
251
+ componentWillUnmount() {
252
+ ReactNoop.yield('componentWillUnmount: ' + this.state.step);
253
+ }
254
+ render() {
255
+ ReactNoop.yield('render: ' + this.state.step);
256
+ if (this.state.step > 0) {
257
+ throw new Error('oops');
258
+ }
259
+ return null;
260
+ }
261
+ }
262
+
263
+ ReactNoop.render(
264
+ <ErrorBoundary>
265
+ <Foo />
266
+ </ErrorBoundary>,
267
+ );
268
+ expect(ReactNoop.flush()).toEqual([
269
+ 'render: 0',
270
+ 'render: 1',
271
+ 'componentWillUnmount: 0',
272
+ ]);
273
+});