Add tests for invokeGuardedCallback (#21734)
* Add tests for invokeGuardedCallback * Add skipped failing tests * Check next render works * Mirror tests for createRoot * Move comments around
Dan Abramov committed
Jun 23, 2021 at 21:47 UTC
e577bfb1cedc56f93d8e5967088932e7100d4829
1 file changed
+1120
packages/react-dom/src/__tests__/ReactDOMConsoleErrorReporting-test.js
new
+1120
@@ -0,0 +1,1120 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+'use strict';
9
+
10
+describe('ReactDOMConsoleErrorReporting', () => {
11
+ let act;
12
+ let React;
13
+ let ReactDOM;
14
+
15
+ let ErrorBoundary;
16
+ let NoError;
17
+ let container;
18
+ let windowOnError;
19
+
20
+ beforeEach(() => {
21
+ jest.resetModules();
22
+ act = require('jest-react').act;
23
+ React = require('react');
24
+ ReactDOM = require('react-dom');
25
+
26
+ ErrorBoundary = class extends React.Component {
27
+ state = {error: null};
28
+ static getDerivedStateFromError(error) {
29
+ return {error};
30
+ }
31
+ render() {
32
+ if (this.state.error) {
33
+ return <h1>Caught: {this.state.error.message}</h1>;
34
+ }
35
+ return this.props.children;
36
+ }
37
+ };
38
+ NoError = function() {
39
+ return <h1>OK</h1>;
40
+ };
41
+ container = document.createElement('div');
42
+ document.body.appendChild(container);
43
+ windowOnError = jest.fn();
44
+ window.addEventListener('error', windowOnError);
45
+ });
46
+
47
+ afterEach(() => {
48
+ document.body.removeChild(container);
49
+ window.removeEventListener('error', windowOnError);
50
+ });
51
+
52
+ describe('ReactDOM.createRoot', () => {
53
+ it('logs errors during event handlers', () => {
54
+ spyOnDevAndProd(console, 'error');
55
+
56
+ function Foo() {
57
+ return (
58
+ <button
59
+ onClick={() => {
60
+ throw Error('Boom');
61
+ }}>
62
+ click me
63
+ </button>
64
+ );
65
+ }
66
+
67
+ const root = ReactDOM.createRoot(container);
68
+ act(() => {
69
+ root.render(<Foo />);
70
+ });
71
+
72
+ act(() => {
73
+ container.firstChild.dispatchEvent(
74
+ new MouseEvent('click', {
75
+ bubbles: true,
76
+ }),
77
+ );
78
+ });
79
+
80
+ if (__DEV__) {
81
+ expect(windowOnError.mock.calls).toEqual([
82
+ [
83
+ // Reported because we're in a browser click event:
84
+ expect.objectContaining({
85
+ message: 'Boom',
86
+ }),
87
+ ],
88
+ [
89
+ // This one is jsdom-only. Real browser deduplicates it.
90
+ // (In DEV, we have a nested event due to guarded callback.)
91
+ expect.objectContaining({
92
+ message: 'Boom',
93
+ }),
94
+ ],
95
+ ]);
96
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
97
+ [
98
+ // Reported because we're in a browser click event:
99
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
100
+ expect.objectContaining({
101
+ message: 'Boom',
102
+ }),
103
+ ],
104
+ [
105
+ // This one is jsdom-only. Real browser deduplicates it.
106
+ // (In DEV, we have a nested event due to guarded callback.)
107
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
108
+ expect.objectContaining({
109
+ message: 'Boom',
110
+ }),
111
+ ],
112
+ ]);
113
+ } else {
114
+ expect(windowOnError.mock.calls).toEqual([
115
+ [
116
+ // Reported because we're in a browser click event:
117
+ expect.objectContaining({
118
+ message: 'Boom',
119
+ }),
120
+ ],
121
+ ]);
122
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
123
+ [
124
+ // Reported because we're in a browser click event:
125
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
126
+ expect.objectContaining({
127
+ message: 'Boom',
128
+ }),
129
+ ],
130
+ ]);
131
+ }
132
+
133
+ // Check next render doesn't throw.
134
+ windowOnError.mockReset();
135
+ console.error.calls.reset();
136
+ act(() => {
137
+ root.render(<NoError />);
138
+ });
139
+ expect(container.textContent).toBe('OK');
140
+ expect(windowOnError.mock.calls).toEqual([]);
141
+ if (__DEV__) {
142
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
143
+ }
144
+ });
145
+
146
+ it('logs render errors without an error boundary', () => {
147
+ spyOnDevAndProd(console, 'error');
148
+
149
+ function Foo() {
150
+ throw Error('Boom');
151
+ }
152
+
153
+ const root = ReactDOM.createRoot(container);
154
+ expect(() => {
155
+ act(() => {
156
+ root.render(<Foo />);
157
+ });
158
+ }).toThrow('Boom');
159
+
160
+ if (__DEV__) {
161
+ expect(windowOnError.mock.calls).toEqual([
162
+ [
163
+ // Reported due to guarded callback:
164
+ expect.objectContaining({
165
+ message: 'Boom',
166
+ }),
167
+ ],
168
+ [
169
+ // TODO: This is duplicated only with createRoot. Why?
170
+ expect.objectContaining({
171
+ message: 'Boom',
172
+ }),
173
+ ],
174
+ ]);
175
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
176
+ [
177
+ // Reported due to the guarded callback:
178
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
179
+ expect.objectContaining({
180
+ message: 'Boom',
181
+ }),
182
+ ],
183
+ [
184
+ // TODO: This is duplicated only with createRoot. Why?
185
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
186
+ expect.objectContaining({
187
+ message: 'Boom',
188
+ }),
189
+ ],
190
+ [
191
+ // Addendum by React:
192
+ expect.stringContaining(
193
+ 'The above error occurred in the <Foo> component',
194
+ ),
195
+ ],
196
+ ]);
197
+ } else {
198
+ // The top-level error was caught with try/catch, and there's no guarded callback,
199
+ // so in production we don't see an error event.
200
+ expect(windowOnError.mock.calls).toEqual([]);
201
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
202
+ [
203
+ // Reported by React with no extra message:
204
+ expect.objectContaining({
205
+ message: 'Boom',
206
+ }),
207
+ ],
208
+ ]);
209
+ }
210
+
211
+ // Check next render doesn't throw.
212
+ windowOnError.mockReset();
213
+ console.error.calls.reset();
214
+ act(() => {
215
+ root.render(<NoError />);
216
+ });
217
+ expect(container.textContent).toBe('OK');
218
+ expect(windowOnError.mock.calls).toEqual([]);
219
+ if (__DEV__) {
220
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
221
+ }
222
+ });
223
+
224
+ it('logs render errors with an error boundary', () => {
225
+ spyOnDevAndProd(console, 'error');
226
+
227
+ function Foo() {
228
+ throw Error('Boom');
229
+ }
230
+
231
+ const root = ReactDOM.createRoot(container);
232
+ act(() => {
233
+ root.render(
234
+ <ErrorBoundary>
235
+ <Foo />
236
+ </ErrorBoundary>,
237
+ );
238
+ });
239
+
240
+ if (__DEV__) {
241
+ expect(windowOnError.mock.calls).toEqual([
242
+ [
243
+ // Reported due to guarded callback:
244
+ expect.objectContaining({
245
+ message: 'Boom',
246
+ }),
247
+ ],
248
+ [
249
+ // TODO: This is duplicated only with createRoot. Why?
250
+ expect.objectContaining({
251
+ message: 'Boom',
252
+ }),
253
+ ],
254
+ ]);
255
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
256
+ [
257
+ // Reported by jsdom due to the guarded callback:
258
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
259
+ expect.objectContaining({
260
+ message: 'Boom',
261
+ }),
262
+ ],
263
+ [
264
+ // Addendum by React:
265
+ expect.stringContaining(
266
+ 'The above error occurred in the <Foo> component',
267
+ ),
268
+ ],
269
+ [
270
+ // TODO: This is duplicated only with createRoot. Why?
271
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
272
+ expect.objectContaining({
273
+ message: 'Boom',
274
+ }),
275
+ ],
276
+ [
277
+ // TODO: This is duplicated only with createRoot. Why?
278
+ expect.stringContaining(
279
+ 'The above error occurred in the <Foo> component',
280
+ ),
281
+ ],
282
+ ]);
283
+ } else {
284
+ // The top-level error was caught with try/catch, and there's no guarded callback,
285
+ // so in production we don't see an error event.
286
+ expect(windowOnError.mock.calls).toEqual([]);
287
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
288
+ [
289
+ // Reported by React with no extra message:
290
+ expect.objectContaining({
291
+ message: 'Boom',
292
+ }),
293
+ ],
294
+ [
295
+ // TODO: This is duplicated only with createRoot. Why?
296
+ expect.objectContaining({
297
+ message: 'Boom',
298
+ }),
299
+ ],
300
+ ]);
301
+ }
302
+
303
+ // Check next render doesn't throw.
304
+ windowOnError.mockReset();
305
+ console.error.calls.reset();
306
+ act(() => {
307
+ root.render(<NoError />);
308
+ });
309
+ expect(container.textContent).toBe('OK');
310
+ expect(windowOnError.mock.calls).toEqual([]);
311
+ if (__DEV__) {
312
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
313
+ }
314
+ });
315
+
316
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
317
+ xit('logs layout effect errors without an error boundary', () => {
318
+ spyOnDevAndProd(console, 'error');
319
+
320
+ function Foo() {
321
+ React.useLayoutEffect(() => {
322
+ throw Error('Boom');
323
+ }, []);
324
+ return null;
325
+ }
326
+
327
+ const root = ReactDOM.createRoot(container);
328
+ expect(() => {
329
+ act(() => {
330
+ root.render(<Foo />);
331
+ });
332
+ }).toThrow('Boom');
333
+
334
+ if (__DEV__) {
335
+ expect(windowOnError.mock.calls).toEqual([
336
+ [
337
+ // Reported due to guarded callback:
338
+ expect.objectContaining({
339
+ message: 'Boom',
340
+ }),
341
+ ],
342
+ ]);
343
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
344
+ [
345
+ // Reported due to the guarded callback:
346
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
347
+ expect.objectContaining({
348
+ message: 'Boom',
349
+ }),
350
+ ],
351
+ [
352
+ // Addendum by React:
353
+ expect.stringContaining(
354
+ 'The above error occurred in the <Foo> component',
355
+ ),
356
+ ],
357
+ ]);
358
+ } else {
359
+ // The top-level error was caught with try/catch, and there's no guarded callback,
360
+ // so in production we don't see an error event.
361
+ expect(windowOnError.mock.calls).toEqual([]);
362
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
363
+ [
364
+ // Reported by React with no extra message:
365
+ expect.objectContaining({
366
+ message: 'Boom',
367
+ }),
368
+ ],
369
+ ]);
370
+ }
371
+
372
+ // Check next render doesn't throw.
373
+ windowOnError.mockReset();
374
+ console.error.calls.reset();
375
+ act(() => {
376
+ root.render(<NoError />);
377
+ });
378
+ expect(container.textContent).toBe('OK');
379
+ expect(windowOnError.mock.calls).toEqual([]);
380
+ if (__DEV__) {
381
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
382
+ }
383
+ });
384
+
385
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
386
+ xit('logs layout effect errors with an error boundary', () => {
387
+ spyOnDevAndProd(console, 'error');
388
+
389
+ function Foo() {
390
+ React.useLayoutEffect(() => {
391
+ throw Error('Boom');
392
+ }, []);
393
+ return null;
394
+ }
395
+
396
+ const root = ReactDOM.createRoot(container);
397
+ act(() => {
398
+ root.render(
399
+ <ErrorBoundary>
400
+ <Foo />
401
+ </ErrorBoundary>,
402
+ );
403
+ });
404
+
405
+ if (__DEV__) {
406
+ expect(windowOnError.mock.calls).toEqual([
407
+ [
408
+ // Reported due to guarded callback:
409
+ expect.objectContaining({
410
+ message: 'Boom',
411
+ }),
412
+ ],
413
+ ]);
414
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
415
+ [
416
+ // Reported by jsdom due to the guarded callback:
417
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
418
+ expect.objectContaining({
419
+ message: 'Boom',
420
+ }),
421
+ ],
422
+ [
423
+ // Addendum by React:
424
+ expect.stringContaining(
425
+ 'The above error occurred in the <Foo> component',
426
+ ),
427
+ ],
428
+ ]);
429
+ } else {
430
+ // The top-level error was caught with try/catch, and there's no guarded callback,
431
+ // so in production we don't see an error event.
432
+ expect(windowOnError.mock.calls).toEqual([]);
433
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
434
+ [
435
+ // Reported by React with no extra message:
436
+ expect.objectContaining({
437
+ message: 'Boom',
438
+ }),
439
+ ],
440
+ ]);
441
+ }
442
+
443
+ // Check next render doesn't throw.
444
+ windowOnError.mockReset();
445
+ console.error.calls.reset();
446
+ act(() => {
447
+ root.render(<NoError />);
448
+ });
449
+ expect(container.textContent).toBe('OK');
450
+ expect(windowOnError.mock.calls).toEqual([]);
451
+ if (__DEV__) {
452
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
453
+ }
454
+ });
455
+
456
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
457
+ xit('logs passive effect errors without an error boundary', () => {
458
+ spyOnDevAndProd(console, 'error');
459
+
460
+ function Foo() {
461
+ React.useEffect(() => {
462
+ throw Error('Boom');
463
+ }, []);
464
+ return null;
465
+ }
466
+
467
+ const root = ReactDOM.createRoot(container);
468
+ expect(() => {
469
+ act(() => {
470
+ root.render(<Foo />);
471
+ });
472
+ }).toThrow('Boom');
473
+
474
+ if (__DEV__) {
475
+ expect(windowOnError.mock.calls).toEqual([
476
+ [
477
+ // Reported due to guarded callback:
478
+ expect.objectContaining({
479
+ message: 'Boom',
480
+ }),
481
+ ],
482
+ ]);
483
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
484
+ [
485
+ // Reported due to the guarded callback:
486
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
487
+ expect.objectContaining({
488
+ message: 'Boom',
489
+ }),
490
+ ],
491
+ [
492
+ // Addendum by React:
493
+ expect.stringContaining(
494
+ 'The above error occurred in the <Foo> component',
495
+ ),
496
+ ],
497
+ ]);
498
+ } else {
499
+ // The top-level error was caught with try/catch, and there's no guarded callback,
500
+ // so in production we don't see an error event.
501
+ expect(windowOnError.mock.calls).toEqual([]);
502
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
503
+ [
504
+ // Reported by React with no extra message:
505
+ expect.objectContaining({
506
+ message: 'Boom',
507
+ }),
508
+ ],
509
+ ]);
510
+ }
511
+
512
+ // Check next render doesn't throw.
513
+ windowOnError.mockReset();
514
+ console.error.calls.reset();
515
+ act(() => {
516
+ root.render(<NoError />);
517
+ });
518
+ expect(container.textContent).toBe('OK');
519
+ expect(windowOnError.mock.calls).toEqual([]);
520
+ if (__DEV__) {
521
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
522
+ }
523
+ });
524
+
525
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
526
+ xit('logs passive effect errors with an error boundary', () => {
527
+ spyOnDevAndProd(console, 'error');
528
+
529
+ function Foo() {
530
+ React.useEffect(() => {
531
+ throw Error('Boom');
532
+ }, []);
533
+ return null;
534
+ }
535
+
536
+ const root = ReactDOM.createRoot(container);
537
+ act(() => {
538
+ root.render(
539
+ <ErrorBoundary>
540
+ <Foo />
541
+ </ErrorBoundary>,
542
+ );
543
+ });
544
+
545
+ if (__DEV__) {
546
+ expect(windowOnError.mock.calls).toEqual([
547
+ [
548
+ // Reported due to guarded callback:
549
+ expect.objectContaining({
550
+ message: 'Boom',
551
+ }),
552
+ ],
553
+ ]);
554
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
555
+ [
556
+ // Reported by jsdom due to the guarded callback:
557
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
558
+ expect.objectContaining({
559
+ message: 'Boom',
560
+ }),
561
+ ],
562
+ [
563
+ // Addendum by React:
564
+ expect.stringContaining(
565
+ 'The above error occurred in the <Foo> component',
566
+ ),
567
+ ],
568
+ ]);
569
+ } else {
570
+ // The top-level error was caught with try/catch, and there's no guarded callback,
571
+ // so in production we don't see an error event.
572
+ expect(windowOnError.mock.calls).toEqual([]);
573
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
574
+ [
575
+ // Reported by React with no extra message:
576
+ expect.objectContaining({
577
+ message: 'Boom',
578
+ }),
579
+ ],
580
+ ]);
581
+ }
582
+
583
+ // Check next render doesn't throw.
584
+ windowOnError.mockReset();
585
+ console.error.calls.reset();
586
+ act(() => {
587
+ root.render(<NoError />);
588
+ });
589
+ expect(container.textContent).toBe('OK');
590
+ expect(windowOnError.mock.calls).toEqual([]);
591
+ if (__DEV__) {
592
+ expect(console.error.calls.all().map(c => c.args)).toEqual([]);
593
+ }
594
+ });
595
+ });
596
+
597
+ describe('ReactDOM.render', () => {
598
+ it('logs errors during event handlers', () => {
599
+ spyOnDevAndProd(console, 'error');
600
+
601
+ function Foo() {
602
+ return (
603
+ <button
604
+ onClick={() => {
605
+ throw Error('Boom');
606
+ }}>
607
+ click me
608
+ </button>
609
+ );
610
+ }
611
+
612
+ act(() => {
613
+ ReactDOM.render(<Foo />, container);
614
+ });
615
+
616
+ act(() => {
617
+ container.firstChild.dispatchEvent(
618
+ new MouseEvent('click', {
619
+ bubbles: true,
620
+ }),
621
+ );
622
+ });
623
+
624
+ if (__DEV__) {
625
+ expect(windowOnError.mock.calls).toEqual([
626
+ [
627
+ // Reported because we're in a browser click event:
628
+ expect.objectContaining({
629
+ message: 'Boom',
630
+ }),
631
+ ],
632
+ [
633
+ // This one is jsdom-only. Real browser deduplicates it.
634
+ // (In DEV, we have a nested event due to guarded callback.)
635
+ expect.objectContaining({
636
+ message: 'Boom',
637
+ }),
638
+ ],
639
+ ]);
640
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
641
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
642
+ [
643
+ // Reported because we're in a browser click event:
644
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
645
+ expect.objectContaining({
646
+ message: 'Boom',
647
+ }),
648
+ ],
649
+ [
650
+ // This one is jsdom-only. Real browser deduplicates it.
651
+ // (In DEV, we have a nested event due to guarded callback.)
652
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
653
+ expect.objectContaining({
654
+ message: 'Boom',
655
+ }),
656
+ ],
657
+ ]);
658
+ } else {
659
+ expect(windowOnError.mock.calls).toEqual([
660
+ [
661
+ // Reported because we're in a browser click event:
662
+ expect.objectContaining({
663
+ message: 'Boom',
664
+ }),
665
+ ],
666
+ ]);
667
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
668
+ [
669
+ // Reported because we're in a browser click event:
670
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
671
+ expect.objectContaining({
672
+ message: 'Boom',
673
+ }),
674
+ ],
675
+ ]);
676
+ }
677
+
678
+ // Check next render doesn't throw.
679
+ windowOnError.mockReset();
680
+ console.error.calls.reset();
681
+ act(() => {
682
+ ReactDOM.render(<NoError />, container);
683
+ });
684
+ expect(container.textContent).toBe('OK');
685
+ expect(windowOnError.mock.calls).toEqual([]);
686
+ if (__DEV__) {
687
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
688
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
689
+ ]);
690
+ }
691
+ });
692
+
693
+ it('logs render errors without an error boundary', () => {
694
+ spyOnDevAndProd(console, 'error');
695
+
696
+ function Foo() {
697
+ throw Error('Boom');
698
+ }
699
+
700
+ expect(() => {
701
+ act(() => {
702
+ ReactDOM.render(<Foo />, container);
703
+ });
704
+ }).toThrow('Boom');
705
+
706
+ if (__DEV__) {
707
+ expect(windowOnError.mock.calls).toEqual([
708
+ [
709
+ // Reported due to guarded callback:
710
+ expect.objectContaining({
711
+ message: 'Boom',
712
+ }),
713
+ ],
714
+ ]);
715
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
716
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
717
+ [
718
+ // Reported due to the guarded callback:
719
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
720
+ expect.objectContaining({
721
+ message: 'Boom',
722
+ }),
723
+ ],
724
+ [
725
+ // Addendum by React:
726
+ expect.stringContaining(
727
+ 'The above error occurred in the <Foo> component',
728
+ ),
729
+ ],
730
+ ]);
731
+ } else {
732
+ // The top-level error was caught with try/catch, and there's no guarded callback,
733
+ // so in production we don't see an error event.
734
+ expect(windowOnError.mock.calls).toEqual([]);
735
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
736
+ [
737
+ // Reported by React with no extra message:
738
+ expect.objectContaining({
739
+ message: 'Boom',
740
+ }),
741
+ ],
742
+ ]);
743
+ }
744
+
745
+ // Check next render doesn't throw.
746
+ windowOnError.mockReset();
747
+ console.error.calls.reset();
748
+ act(() => {
749
+ ReactDOM.render(<NoError />, container);
750
+ });
751
+ expect(container.textContent).toBe('OK');
752
+ expect(windowOnError.mock.calls).toEqual([]);
753
+ if (__DEV__) {
754
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
755
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
756
+ ]);
757
+ }
758
+ });
759
+
760
+ it('logs render errors with an error boundary', () => {
761
+ spyOnDevAndProd(console, 'error');
762
+
763
+ function Foo() {
764
+ throw Error('Boom');
765
+ }
766
+
767
+ act(() => {
768
+ ReactDOM.render(
769
+ <ErrorBoundary>
770
+ <Foo />
771
+ </ErrorBoundary>,
772
+ container,
773
+ );
774
+ });
775
+
776
+ if (__DEV__) {
777
+ expect(windowOnError.mock.calls).toEqual([
778
+ [
779
+ // Reported due to guarded callback:
780
+ expect.objectContaining({
781
+ message: 'Boom',
782
+ }),
783
+ ],
784
+ ]);
785
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
786
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
787
+ [
788
+ // Reported by jsdom due to the guarded callback:
789
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
790
+ expect.objectContaining({
791
+ message: 'Boom',
792
+ }),
793
+ ],
794
+ [
795
+ // Addendum by React:
796
+ expect.stringContaining(
797
+ 'The above error occurred in the <Foo> component',
798
+ ),
799
+ ],
800
+ ]);
801
+ } else {
802
+ // The top-level error was caught with try/catch, and there's no guarded callback,
803
+ // so in production we don't see an error event.
804
+ expect(windowOnError.mock.calls).toEqual([]);
805
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
806
+ [
807
+ // Reported by React with no extra message:
808
+ expect.objectContaining({
809
+ message: 'Boom',
810
+ }),
811
+ ],
812
+ ]);
813
+ }
814
+
815
+ // Check next render doesn't throw.
816
+ windowOnError.mockReset();
817
+ console.error.calls.reset();
818
+ act(() => {
819
+ ReactDOM.render(<NoError />, container);
820
+ });
821
+ expect(container.textContent).toBe('OK');
822
+ expect(windowOnError.mock.calls).toEqual([]);
823
+ if (__DEV__) {
824
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
825
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
826
+ ]);
827
+ }
828
+ });
829
+
830
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
831
+ xit('logs layout effect errors without an error boundary', () => {
832
+ spyOnDevAndProd(console, 'error');
833
+
834
+ function Foo() {
835
+ React.useLayoutEffect(() => {
836
+ throw Error('Boom');
837
+ }, []);
838
+ return null;
839
+ }
840
+
841
+ expect(() => {
842
+ act(() => {
843
+ ReactDOM.render(<Foo />, container);
844
+ });
845
+ }).toThrow('Boom');
846
+
847
+ if (__DEV__) {
848
+ expect(windowOnError.mock.calls).toEqual([
849
+ [
850
+ // Reported due to guarded callback:
851
+ expect.objectContaining({
852
+ message: 'Boom',
853
+ }),
854
+ ],
855
+ ]);
856
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
857
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
858
+ [
859
+ // Reported due to the guarded callback:
860
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
861
+ expect.objectContaining({
862
+ message: 'Boom',
863
+ }),
864
+ ],
865
+ [
866
+ // Addendum by React:
867
+ expect.stringContaining(
868
+ 'The above error occurred in the <Foo> component',
869
+ ),
870
+ ],
871
+ ]);
872
+ } else {
873
+ // The top-level error was caught with try/catch, and there's no guarded callback,
874
+ // so in production we don't see an error event.
875
+ expect(windowOnError.mock.calls).toEqual([]);
876
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
877
+ [
878
+ // Reported by React with no extra message:
879
+ expect.objectContaining({
880
+ message: 'Boom',
881
+ }),
882
+ ],
883
+ ]);
884
+ }
885
+
886
+ // Check next render doesn't throw.
887
+ windowOnError.mockReset();
888
+ console.error.calls.reset();
889
+ act(() => {
890
+ ReactDOM.render(<NoError />, container);
891
+ });
892
+ expect(container.textContent).toBe('OK');
893
+ expect(windowOnError.mock.calls).toEqual([]);
894
+ if (__DEV__) {
895
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
896
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
897
+ ]);
898
+ }
899
+ });
900
+
901
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
902
+ xit('logs layout effect errors with an error boundary', () => {
903
+ spyOnDevAndProd(console, 'error');
904
+
905
+ function Foo() {
906
+ React.useLayoutEffect(() => {
907
+ throw Error('Boom');
908
+ }, []);
909
+ return null;
910
+ }
911
+
912
+ act(() => {
913
+ ReactDOM.render(
914
+ <ErrorBoundary>
915
+ <Foo />
916
+ </ErrorBoundary>,
917
+ container,
918
+ );
919
+ });
920
+
921
+ if (__DEV__) {
922
+ expect(windowOnError.mock.calls).toEqual([
923
+ [
924
+ // Reported due to guarded callback:
925
+ expect.objectContaining({
926
+ message: 'Boom',
927
+ }),
928
+ ],
929
+ ]);
930
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
931
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
932
+ [
933
+ // Reported by jsdom due to the guarded callback:
934
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
935
+ expect.objectContaining({
936
+ message: 'Boom',
937
+ }),
938
+ ],
939
+ [
940
+ // Addendum by React:
941
+ expect.stringContaining(
942
+ 'The above error occurred in the <Foo> component',
943
+ ),
944
+ ],
945
+ ]);
946
+ } else {
947
+ // The top-level error was caught with try/catch, and there's no guarded callback,
948
+ // so in production we don't see an error event.
949
+ expect(windowOnError.mock.calls).toEqual([]);
950
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
951
+ [
952
+ // Reported by React with no extra message:
953
+ expect.objectContaining({
954
+ message: 'Boom',
955
+ }),
956
+ ],
957
+ ]);
958
+ }
959
+
960
+ // Check next render doesn't throw.
961
+ windowOnError.mockReset();
962
+ console.error.calls.reset();
963
+ act(() => {
964
+ ReactDOM.render(<NoError />, container);
965
+ });
966
+ expect(container.textContent).toBe('OK');
967
+ expect(windowOnError.mock.calls).toEqual([]);
968
+ if (__DEV__) {
969
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
970
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
971
+ ]);
972
+ }
973
+ });
974
+
975
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
976
+ xit('logs passive effect errors without an error boundary', () => {
977
+ spyOnDevAndProd(console, 'error');
978
+
979
+ function Foo() {
980
+ React.useEffect(() => {
981
+ throw Error('Boom');
982
+ }, []);
983
+ return null;
984
+ }
985
+
986
+ expect(() => {
987
+ act(() => {
988
+ ReactDOM.render(<Foo />, container);
989
+ });
990
+ }).toThrow('Boom');
991
+
992
+ if (__DEV__) {
993
+ expect(windowOnError.mock.calls).toEqual([
994
+ [
995
+ // Reported due to guarded callback:
996
+ expect.objectContaining({
997
+ message: 'Boom',
998
+ }),
999
+ ],
1000
+ ]);
1001
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
1002
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
1003
+ [
1004
+ // Reported due to the guarded callback:
1005
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
1006
+ expect.objectContaining({
1007
+ message: 'Boom',
1008
+ }),
1009
+ ],
1010
+ [
1011
+ // Addendum by React:
1012
+ expect.stringContaining(
1013
+ 'The above error occurred in the <Foo> component',
1014
+ ),
1015
+ ],
1016
+ ]);
1017
+ } else {
1018
+ // The top-level error was caught with try/catch, and there's no guarded callback,
1019
+ // so in production we don't see an error event.
1020
+ expect(windowOnError.mock.calls).toEqual([]);
1021
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
1022
+ [
1023
+ // Reported by React with no extra message:
1024
+ expect.objectContaining({
1025
+ message: 'Boom',
1026
+ }),
1027
+ ],
1028
+ ]);
1029
+ }
1030
+
1031
+ // Check next render doesn't throw.
1032
+ windowOnError.mockReset();
1033
+ console.error.calls.reset();
1034
+ act(() => {
1035
+ ReactDOM.render(<NoError />, container);
1036
+ });
1037
+ expect(container.textContent).toBe('OK');
1038
+ expect(windowOnError.mock.calls).toEqual([]);
1039
+ if (__DEV__) {
1040
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
1041
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
1042
+ ]);
1043
+ }
1044
+ });
1045
+
1046
+ // TODO: this is broken due to https://github.com/facebook/react/issues/21712.
1047
+ xit('logs passive effect errors with an error boundary', () => {
1048
+ spyOnDevAndProd(console, 'error');
1049
+
1050
+ function Foo() {
1051
+ React.useEffect(() => {
1052
+ throw Error('Boom');
1053
+ }, []);
1054
+ return null;
1055
+ }
1056
+
1057
+ act(() => {
1058
+ ReactDOM.render(
1059
+ <ErrorBoundary>
1060
+ <Foo />
1061
+ </ErrorBoundary>,
1062
+ container,
1063
+ );
1064
+ });
1065
+
1066
+ if (__DEV__) {
1067
+ // Reported due to guarded callback:
1068
+ expect(windowOnError.mock.calls).toEqual([
1069
+ [
1070
+ expect.objectContaining({
1071
+ message: 'Boom',
1072
+ }),
1073
+ ],
1074
+ ]);
1075
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
1076
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
1077
+ [
1078
+ // Reported by jsdom due to the guarded callback:
1079
+ expect.stringContaining('Error: Uncaught [Error: Boom]'),
1080
+ expect.objectContaining({
1081
+ message: 'Boom',
1082
+ }),
1083
+ ],
1084
+ [
1085
+ // Addendum by React:
1086
+ expect.stringContaining(
1087
+ 'The above error occurred in the <Foo> component',
1088
+ ),
1089
+ ],
1090
+ ]);
1091
+ } else {
1092
+ // The top-level error was caught with try/catch, and there's no guarded callback,
1093
+ // so in production we don't see an error event.
1094
+ expect(windowOnError.mock.calls).toEqual([]);
1095
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
1096
+ [
1097
+ // Reported by React with no extra message:
1098
+ expect.objectContaining({
1099
+ message: 'Boom',
1100
+ }),
1101
+ ],
1102
+ ]);
1103
+ }
1104
+
1105
+ // Check next render doesn't throw.
1106
+ windowOnError.mockReset();
1107
+ console.error.calls.reset();
1108
+ act(() => {
1109
+ ReactDOM.render(<NoError />, container);
1110
+ });
1111
+ expect(container.textContent).toBe('OK');
1112
+ expect(windowOnError.mock.calls).toEqual([]);
1113
+ if (__DEV__) {
1114
+ expect(console.error.calls.all().map(c => c.args)).toEqual([
1115
+ [expect.stringContaining('ReactDOM.render is no longer supported')],
1116
+ ]);
1117
+ }
1118
+ });
1119
+ });
1120
+});