[act] reset scope depth on synchronous errors (#15937)
* reset scope depth on synchronous errors we weren't resetting the acting scope depth on sync errors thrown in the callback. this fixes that. * typos * add a test to make sure sync error propagate
Sunil Pai committed
Jun 20, 2019 at 13:50 UTC
ff91bfa58ce817c8c8c1cbe8fcdd7584c75c36ee
4 files changed
+133
-17
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+106
-14
@@ -404,20 +404,6 @@ function runActTests(label, render, unmount) {
404
expect(container.innerHTML).toBe('1');
405
});
406
407
- it('propagates errors', async () => {
408
- let err;
409
- try {
410
- await act(async () => {
411
- await sleep(100);
412
- throw new Error('some error');
413
- });
414
- } catch (_err) {
415
- err = _err;
416
- } finally {
417
- expect(err instanceof Error).toBe(true);
418
- expect(err.message).toBe('some error');
419
- }
420
- });
407
it('can handle cascading promises', async () => {
408
// this component triggers an effect, that waits a tick,
409
// then sets state. repeats this 5 times.
@@ -524,5 +510,111 @@ function runActTests(label, render, unmount) {
510
});
511
}
512
});
513
+ describe('error propagation', () => {
514
+ it('propagates errors - sync', () => {
515
+ let err;
516
+ try {
517
+ act(() => {
518
+ throw new Error('some error');
519
+ });
520
+ } catch (_err) {
521
+ err = _err;
522
+ } finally {
523
+ expect(err instanceof Error).toBe(true);
524
+ expect(err.message).toBe('some error');
525
+ }
526
+ });
527
+
528
+ it('should propagate errors from effects - sync', () => {
529
+ function App() {
530
+ React.useEffect(() => {
531
+ throw new Error('oh no');
532
+ });
533
+ return null;
534
+ }
535
+ let error;
536
+
537
+ try {
538
+ act(() => {
539
+ render(<App />, container);
540
+ });
541
+ } catch (_error) {
542
+ error = _error;
543
+ } finally {
544
+ expect(error instanceof Error).toBe(true);
545
+ expect(error.message).toBe('oh no');
546
+ }
547
+ });
548
+
549
+ it('propagates errors - async', async () => {
550
+ let err;
551
+ try {
552
+ await act(async () => {
553
+ await sleep(100);
554
+ throw new Error('some error');
555
+ });
556
+ } catch (_err) {
557
+ err = _err;
558
+ } finally {
559
+ expect(err instanceof Error).toBe(true);
560
+ expect(err.message).toBe('some error');
561
+ }
562
+ });
563
+
564
+ it('should cleanup after errors - sync', () => {
565
+ function App() {
566
+ React.useEffect(() => {
567
+ Scheduler.yieldValue('oh yes');
568
+ });
569
+ return null;
570
+ }
571
+ let error;
572
+ try {
573
+ act(() => {
574
+ throw new Error('oh no');
575
+ });
576
+ } catch (_error) {
577
+ error = _error;
578
+ } finally {
579
+ expect(error instanceof Error).toBe(true);
580
+ expect(error.message).toBe('oh no');
581
+ // should be able to render components after this tho
582
+ act(() => {
583
+ render(<App />, container);
584
+ });
585
+ expect(Scheduler).toHaveYielded(['oh yes']);
586
+ }
587
+ });
588
+
589
+ it('should cleanup after errors - async', async () => {
590
+ function App() {
591
+ async function somethingAsync() {
592
+ await null;
593
+ Scheduler.yieldValue('oh yes');
594
+ }
595
+ React.useEffect(() => {
596
+ somethingAsync();
597
+ });
598
+ return null;
599
+ }
600
+ let error;
601
+ try {
602
+ await act(async () => {
603
+ await sleep(100);
604
+ throw new Error('oh no');
605
+ });
606
+ } catch (_error) {
607
+ error = _error;
608
+ } finally {
609
+ expect(error instanceof Error).toBe(true);
610
+ expect(error.message).toBe('oh no');
611
+ // should be able to render components after this tho
612
+ await act(async () => {
613
+ render(<App />, container);
614
+ });
615
+ expect(Scheduler).toHaveYielded(['oh yes']);
616
+ }
617
+ });
618
+ });
619
});
620
}
packages/react-dom/src/test-utils/ReactTestUtilsAct.js
+9
-1
@@ -108,7 +108,15 @@ function act(callback: () => Thenable) {
108
}
109
}
110
111
- const result = batchedUpdates(callback);
111
+ let result;
112
+ try {
113
+ result = batchedUpdates(callback);
114
+ } catch (error) {
115
+ // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
116
+ onDone();
117
+ throw error;
118
+ }
119
+
120
if (
121
result !== null &&
122
typeof result === 'object' &&
packages/react-noop-renderer/src/createReactNoop.js
+9
-1
@@ -724,7 +724,15 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
724
}
725
}
726
727
- const result = batchedUpdates(callback);
727
+ let result;
728
+ try {
729
+ result = batchedUpdates(callback);
730
+ } catch (error) {
731
+ // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
732
+ onDone();
733
+ throw error;
734
+ }
735
+
736
if (
737
result !== null &&
738
typeof result === 'object' &&
packages/react-test-renderer/src/ReactTestRendererAct.js
+9
-1
@@ -89,7 +89,15 @@ function act(callback: () => Thenable) {
89
}
90
}
91
92
- const result = batchedUpdates(callback);
92
+ let result;
93
+ try {
94
+ result = batchedUpdates(callback);
95
+ } catch (error) {
96
+ // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
97
+ onDone();
98
+ throw error;
99
+ }
100
+
101
if (
102
result !== null &&
103
typeof result === 'object' &&