@samitouri / QOS-React-2 / commits / e577bfb1ce

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 +});