@samitouri / QOS-React / commits / 878feebe34

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