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

[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' &&