@samitouri / QOS-React / commits / 3d6fcf9582

Convert ReactErrorBoundaries-test.internal.js to createRoot (#28122)

Matt Carroll committed Feb 2, 2024 at 17:41 UTC 3d6fcf958233a004ff9bd0af8f1f65b8f22f1b3c
1 file changed +980 -602
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+980 -602
@@ -12,6 +12,7 @@
12 let PropTypes;
13 let React;
14 let ReactDOM;
15 +let ReactDOMClient;
16 let act;
17 let ReactFeatureFlags;
18 let Scheduler;
@@ -44,6 +45,7 @@ describe('ReactErrorBoundaries', () => {
45 ReactFeatureFlags = require('shared/ReactFeatureFlags');
46 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
47 ReactDOM = require('react-dom');
48 + ReactDOMClient = require('react-dom/client');
49 React = require('react');
50 act = require('internal-test-utils').act;
51 Scheduler = require('scheduler');
@@ -576,103 +578,139 @@ describe('ReactErrorBoundaries', () => {
578 };
579 });
580
579 - it('does not swallow exceptions on mounting without boundaries', () => {
581 + it('does not swallow exceptions on mounting without boundaries', async () => {
582 let container = document.createElement('div');
581 - expect(() => {
582 - ReactDOM.render(<BrokenRender />, container);
583 - }).toThrow('Hello');
583 + let root = ReactDOMClient.createRoot(container);
584 + await expect(async () => {
585 + await act(async () => {
586 + root.render(<BrokenRender />);
587 + });
588 + }).rejects.toThrow('Hello');
589
590 container = document.createElement('div');
586 - expect(() => {
587 - ReactDOM.render(<BrokenComponentWillMount />, container);
588 - }).toThrow('Hello');
591 + root = ReactDOMClient.createRoot(container);
592 + await expect(async () => {
593 + await act(async () => {
594 + root.render(<BrokenComponentWillMount />);
595 + });
596 + }).rejects.toThrow('Hello');
597
598 container = document.createElement('div');
591 - expect(() => {
592 - ReactDOM.render(<BrokenComponentDidMount />, container);
593 - }).toThrow('Hello');
599 + root = ReactDOMClient.createRoot(container);
600 + await expect(async () => {
601 + await act(async () => {
602 + root.render(<BrokenComponentDidMount />);
603 + });
604 + }).rejects.toThrow('Hello');
605 });
606
596 - it('does not swallow exceptions on updating without boundaries', () => {
607 + it('does not swallow exceptions on updating without boundaries', async () => {
608 let container = document.createElement('div');
598 - ReactDOM.render(<BrokenComponentWillUpdate />, container);
599 - expect(() => {
600 - ReactDOM.render(<BrokenComponentWillUpdate />, container);
601 - }).toThrow('Hello');
609 + let root = ReactDOMClient.createRoot(container);
610 + await act(async () => {
611 + root.render(<BrokenComponentWillUpdate />);
612 + });
613 + await expect(async () => {
614 + await act(async () => {
615 + root.render(<BrokenComponentWillUpdate />);
616 + });
617 + }).rejects.toThrow('Hello');
618
619 container = document.createElement('div');
604 - ReactDOM.render(<BrokenComponentWillReceiveProps />, container);
605 - expect(() => {
606 - ReactDOM.render(<BrokenComponentWillReceiveProps />, container);
607 - }).toThrow('Hello');
620 + root = ReactDOMClient.createRoot(container);
621 + await act(async () => {
622 + root.render(<BrokenComponentWillReceiveProps />);
623 + });
624 + await expect(async () => {
625 + await act(async () => {
626 + root.render(<BrokenComponentWillReceiveProps />);
627 + });
628 + }).rejects.toThrow('Hello');
629
630 container = document.createElement('div');
610 - ReactDOM.render(<BrokenComponentDidUpdate />, container);
611 - expect(() => {
612 - ReactDOM.render(<BrokenComponentDidUpdate />, container);
613 - }).toThrow('Hello');
631 + root = ReactDOMClient.createRoot(container);
632 + await act(async () => {
633 + root.render(<BrokenComponentDidUpdate />);
634 + });
635 + await expect(async () => {
636 + await act(async () => {
637 + root.render(<BrokenComponentDidUpdate />);
638 + });
639 + }).rejects.toThrow('Hello');
640 });
641
616 - it('does not swallow exceptions on unmounting without boundaries', () => {
642 + it('does not swallow exceptions on unmounting without boundaries', async () => {
643 const container = document.createElement('div');
618 - ReactDOM.render(<BrokenComponentWillUnmount />, container);
619 - expect(() => {
620 - ReactDOM.unmountComponentAtNode(container);
621 - }).toThrow('Hello');
644 + const root = ReactDOMClient.createRoot(container);
645 + await act(async () => {
646 + root.render(<BrokenComponentWillUnmount />);
647 + });
648 + await expect(async () => {
649 + root.unmount();
650 + }).rejects.toThrow('Hello');
651 });
652
624 - it('prevents errors from leaking into other roots', () => {
653 + it('prevents errors from leaking into other roots', async () => {
654 const container1 = document.createElement('div');
655 + const root1 = ReactDOMClient.createRoot(container1);
656 const container2 = document.createElement('div');
657 + const root2 = ReactDOMClient.createRoot(container2);
658 const container3 = document.createElement('div');
659 + const root3 = ReactDOMClient.createRoot(container3);
660
629 - ReactDOM.render(<span>Before 1</span>, container1);
630 - expect(() => {
631 - ReactDOM.render(<BrokenRender />, container2);
632 - }).toThrow('Hello');
633 - ReactDOM.render(
634 - <ErrorBoundary>
635 - <BrokenRender />
636 - </ErrorBoundary>,
637 - container3,
638 - );
661 + await act(async () => {
662 + root1.render(<span>Before 1</span>);
663 + });
664 + await expect(async () => {
665 + await act(async () => {
666 + root2.render(<BrokenRender />);
667 + });
668 + }).rejects.toThrow('Hello');
669 + await act(async () => {
670 + root3.render(
671 + <ErrorBoundary>
672 + <BrokenRender />
673 + </ErrorBoundary>,
674 + );
675 + });
676 expect(container1.firstChild.textContent).toBe('Before 1');
677 expect(container2.firstChild).toBe(null);
678 expect(container3.firstChild.textContent).toBe('Caught an error: Hello.');
679
643 - ReactDOM.render(<span>After 1</span>, container1);
644 - ReactDOM.render(<span>After 2</span>, container2);
645 - ReactDOM.render(
646 - <ErrorBoundary forceRetry={true}>After 3</ErrorBoundary>,
647 - container3,
648 - );
680 + await act(async () => {
681 + root1.render(<span>After 1</span>);
682 + });
683 + await act(async () => {
684 + root2.render(<span>After 2</span>);
685 + });
686 + await act(async () => {
687 + root3.render(<ErrorBoundary forceRetry={true}>After 3</ErrorBoundary>);
688 + });
689 expect(container1.firstChild.textContent).toBe('After 1');
690 expect(container2.firstChild.textContent).toBe('After 2');
691 expect(container3.firstChild.textContent).toBe('After 3');
652 -
653 - ReactDOM.unmountComponentAtNode(container1);
654 - ReactDOM.unmountComponentAtNode(container2);
655 - ReactDOM.unmountComponentAtNode(container3);
692 + root1.unmount();
693 + root2.unmount();
694 + root3.unmount();
695 expect(container1.firstChild).toBe(null);
696 expect(container2.firstChild).toBe(null);
697 expect(container3.firstChild).toBe(null);
698 });
699
661 - it('logs a single error when using error boundary', () => {
700 + it('logs a single error when using error boundary', async () => {
701 const container = document.createElement('div');
702 + const root = ReactDOMClient.createRoot(container);
703 spyOnDev(console, 'error');
664 - ReactDOM.render(
665 - <ErrorBoundary>
666 - <BrokenRender />
667 - </ErrorBoundary>,
668 - container,
669 - );
704 + await act(async () => {
705 + root.render(
706 + <ErrorBoundary>
707 + <BrokenRender />
708 + </ErrorBoundary>,
709 + );
710 + });
711 if (__DEV__) {
671 - expect(console.error).toHaveBeenCalledTimes(2);
712 + expect(console.error).toHaveBeenCalledTimes(1);
713 expect(console.error.mock.calls[0][0]).toContain(
673 - 'ReactDOM.render is no longer supported',
674 - );
675 - expect(console.error.mock.calls[1][0]).toContain(
714 'The above error occurred in the <BrokenRender> component:',
715 );
716 }
@@ -689,21 +727,33 @@ describe('ReactErrorBoundaries', () => {
727 'ErrorBoundary static getDerivedStateFromError',
728 'ErrorBoundary componentWillMount',
729 'ErrorBoundary render error',
730 + // logs for error retry
731 + 'ErrorBoundary constructor',
732 + 'ErrorBoundary componentWillMount',
733 + 'ErrorBoundary render success',
734 + 'BrokenRender constructor',
735 + 'BrokenRender componentWillMount',
736 + 'BrokenRender render [!]',
737 + 'ErrorBoundary static getDerivedStateFromError',
738 + 'ErrorBoundary componentWillMount',
739 + 'ErrorBoundary render error',
740 'ErrorBoundary componentDidMount',
741 ]);
742
695 - ReactDOM.unmountComponentAtNode(container);
743 + root.unmount();
744 assertLog(['ErrorBoundary componentWillUnmount']);
745 });
746
699 - it('renders an error state if child throws in render', () => {
747 + it('renders an error state if child throws in render', async () => {
748 const container = document.createElement('div');
701 - ReactDOM.render(
702 - <ErrorBoundary>
703 - <BrokenRender />
704 - </ErrorBoundary>,
705 - container,
706 - );
749 + const root = ReactDOMClient.createRoot(container);
750 + await act(async () => {
751 + root.render(
752 + <ErrorBoundary>
753 + <BrokenRender />
754 + </ErrorBoundary>,
755 + );
756 + });
757 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
758 assertLog([
759 'ErrorBoundary constructor',
@@ -716,21 +766,33 @@ describe('ReactErrorBoundaries', () => {
766 'ErrorBoundary static getDerivedStateFromError',
767 'ErrorBoundary componentWillMount',
768 'ErrorBoundary render error',
769 + // logs for error retry
770 + 'ErrorBoundary constructor',
771 + 'ErrorBoundary componentWillMount',
772 + 'ErrorBoundary render success',
773 + 'BrokenRender constructor',
774 + 'BrokenRender componentWillMount',
775 + 'BrokenRender render [!]',
776 + 'ErrorBoundary static getDerivedStateFromError',
777 + 'ErrorBoundary componentWillMount',
778 + 'ErrorBoundary render error',
779 'ErrorBoundary componentDidMount',
780 ]);
781
722 - ReactDOM.unmountComponentAtNode(container);
782 + root.unmount();
783 assertLog(['ErrorBoundary componentWillUnmount']);
784 });
785
726 - it('renders an error state if child throws in constructor', () => {
786 + it('renders an error state if child throws in constructor', async () => {
787 const container = document.createElement('div');
728 - ReactDOM.render(
729 - <ErrorBoundary>
730 - <BrokenConstructor />
731 - </ErrorBoundary>,
732 - container,
733 - );
788 + const root = ReactDOMClient.createRoot(container);
789 + await act(async () => {
790 + root.render(
791 + <ErrorBoundary>
792 + <BrokenConstructor />
793 + </ErrorBoundary>,
794 + );
795 + });
796 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
797 assertLog([
798 'ErrorBoundary constructor',
@@ -741,21 +803,31 @@ describe('ReactErrorBoundaries', () => {
803 'ErrorBoundary static getDerivedStateFromError',
804 'ErrorBoundary componentWillMount',
805 'ErrorBoundary render error',
806 + // logs for error retry
807 + 'ErrorBoundary constructor',
808 + 'ErrorBoundary componentWillMount',
809 + 'ErrorBoundary render success',
810 + 'BrokenConstructor constructor [!]',
811 + 'ErrorBoundary static getDerivedStateFromError',
812 + 'ErrorBoundary componentWillMount',
813 + 'ErrorBoundary render error',
814 'ErrorBoundary componentDidMount',
815 ]);
816
747 - ReactDOM.unmountComponentAtNode(container);
817 + root.unmount();
818 assertLog(['ErrorBoundary componentWillUnmount']);
819 });
820
751 - it('renders an error state if child throws in componentWillMount', () => {
821 + it('renders an error state if child throws in componentWillMount', async () => {
822 const container = document.createElement('div');
753 - ReactDOM.render(
754 - <ErrorBoundary>
755 - <BrokenComponentWillMount />
756 - </ErrorBoundary>,
757 - container,
758 - );
823 + const root = ReactDOMClient.createRoot(container);
824 + await act(async () => {
825 + root.render(
826 + <ErrorBoundary>
827 + <BrokenComponentWillMount />
828 + </ErrorBoundary>,
829 + );
830 + });
831 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
832 assertLog([
833 'ErrorBoundary constructor',
@@ -767,15 +839,24 @@ describe('ReactErrorBoundaries', () => {
839 'ErrorBoundary static getDerivedStateFromError',
840 'ErrorBoundary componentWillMount',
841 'ErrorBoundary render error',
842 + // logs for error retry
843 + 'ErrorBoundary constructor',
844 + 'ErrorBoundary componentWillMount',
845 + 'ErrorBoundary render success',
846 + 'BrokenComponentWillMount constructor',
847 + 'BrokenComponentWillMount componentWillMount [!]',
848 + 'ErrorBoundary static getDerivedStateFromError',
849 + 'ErrorBoundary componentWillMount',
850 + 'ErrorBoundary render error',
851 'ErrorBoundary componentDidMount',
852 ]);
853
773 - ReactDOM.unmountComponentAtNode(container);
854 + root.unmount();
855 assertLog(['ErrorBoundary componentWillUnmount']);
856 });
857
858 // @gate !disableLegacyContext || !__DEV__
778 - it('renders an error state if context provider throws in componentWillMount', () => {
859 + it('renders an error state if context provider throws in componentWillMount', async () => {
860 class BrokenComponentWillMountWithContext extends React.Component {
861 static childContextTypes = {foo: PropTypes.number};
862 getChildContext() {
@@ -790,18 +871,20 @@ describe('ReactErrorBoundaries', () => {
871 }
872
873 const container = document.createElement('div');
793 - ReactDOM.render(
794 - <ErrorBoundary>
795 - <BrokenComponentWillMountWithContext />
796 - </ErrorBoundary>,
797 - container,
798 - );
874 + const root = ReactDOMClient.createRoot(container);
875 + await act(async () => {
876 + root.render(
877 + <ErrorBoundary>
878 + <BrokenComponentWillMountWithContext />
879 + </ErrorBoundary>,
880 + );
881 + });
882 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
883 });
884
885 // @gate !disableModulePatternComponents
886 // @gate !disableLegacyContext
804 - it('renders an error state if module-style context provider throws in componentWillMount', () => {
887 + it('renders an error state if module-style context provider throws in componentWillMount', async () => {
888 function BrokenComponentWillMountWithContext() {
889 return {
890 getChildContext() {
@@ -820,13 +903,16 @@ describe('ReactErrorBoundaries', () => {
903 };
904
905 const container = document.createElement('div');
823 - expect(() =>
824 - ReactDOM.render(
825 - <ErrorBoundary>
826 - <BrokenComponentWillMountWithContext />
827 - </ErrorBoundary>,
828 - container,
829 - ),
906 + const root = ReactDOMClient.createRoot(container);
907 + await expect(
908 + async () =>
909 + await act(async () => {
910 + root.render(
911 + <ErrorBoundary>
912 + <BrokenComponentWillMountWithContext />
913 + </ErrorBoundary>,
914 + );
915 + }),
916 ).toErrorDev(
917 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
918 'returns a class instance. ' +
@@ -839,19 +925,34 @@ describe('ReactErrorBoundaries', () => {
925 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
926 });
927
842 - it('mounts the error message if mounting fails', () => {
928 + it('mounts the error message if mounting fails', async () => {
929 function renderError(error) {
930 return <ErrorMessage message={error.message} />;
931 }
932
933 const container = document.createElement('div');
848 - ReactDOM.render(
849 - <ErrorBoundary renderError={renderError}>
850 - <BrokenRender />
851 - </ErrorBoundary>,
852 - container,
853 - );
934 + const root = ReactDOMClient.createRoot(container);
935 + await act(async () => {
936 + root.render(
937 + <ErrorBoundary renderError={renderError}>
938 + <BrokenRender />
939 + </ErrorBoundary>,
940 + );
941 + });
942 assertLog([
943 + 'ErrorBoundary constructor',
944 + 'ErrorBoundary componentWillMount',
945 + 'ErrorBoundary render success',
946 + 'BrokenRender constructor',
947 + 'BrokenRender componentWillMount',
948 + 'BrokenRender render [!]',
949 + 'ErrorBoundary static getDerivedStateFromError',
950 + 'ErrorBoundary componentWillMount',
951 + 'ErrorBoundary render error',
952 + 'ErrorMessage constructor',
953 + 'ErrorMessage componentWillMount',
954 + 'ErrorMessage render',
955 + // logs for error retry
956 'ErrorBoundary constructor',
957 'ErrorBoundary componentWillMount',
958 'ErrorBoundary render success',
@@ -868,23 +969,25 @@ describe('ReactErrorBoundaries', () => {
969 'ErrorBoundary componentDidMount',
970 ]);
971
871 - ReactDOM.unmountComponentAtNode(container);
972 + root.unmount();
973 assertLog([
974 'ErrorBoundary componentWillUnmount',
975 'ErrorMessage componentWillUnmount',
976 ]);
977 });
978
878 - it('propagates errors on retry on mounting', () => {
979 + it('propagates errors on retry on mounting', async () => {
980 const container = document.createElement('div');
880 - ReactDOM.render(
881 - <ErrorBoundary>
882 - <RetryErrorBoundary>
883 - <BrokenRender />
884 - </RetryErrorBoundary>
885 - </ErrorBoundary>,
886 - container,
887 - );
981 + const root = ReactDOMClient.createRoot(container);
982 + await act(async () => {
983 + root.render(
984 + <ErrorBoundary>
985 + <RetryErrorBoundary>
986 + <BrokenRender />
987 + </RetryErrorBoundary>
988 + </ErrorBoundary>,
989 + );
990 + });
991 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
992 assertLog([
993 'ErrorBoundary constructor',
@@ -907,21 +1010,42 @@ describe('ReactErrorBoundaries', () => {
1010 'ErrorBoundary static getDerivedStateFromError',
1011 'ErrorBoundary componentWillMount',
1012 'ErrorBoundary render error',
1013 + // logs for error retry
1014 + 'ErrorBoundary constructor',
1015 + 'ErrorBoundary componentWillMount',
1016 + 'ErrorBoundary render success',
1017 + 'RetryErrorBoundary constructor',
1018 + 'RetryErrorBoundary componentWillMount',
1019 + 'RetryErrorBoundary render',
1020 + 'BrokenRender constructor',
1021 + 'BrokenRender componentWillMount',
1022 + 'BrokenRender render [!]',
1023 + 'RetryErrorBoundary static getDerivedStateFromError [!]',
1024 + 'RetryErrorBoundary componentWillMount',
1025 + 'RetryErrorBoundary render',
1026 + 'BrokenRender constructor',
1027 + 'BrokenRender componentWillMount',
1028 + 'BrokenRender render [!]',
1029 + 'ErrorBoundary static getDerivedStateFromError',
1030 + 'ErrorBoundary componentWillMount',
1031 + 'ErrorBoundary render error',
1032 'ErrorBoundary componentDidMount',
1033 ]);
1034
913 - ReactDOM.unmountComponentAtNode(container);
1035 + root.unmount();
1036 assertLog(['ErrorBoundary componentWillUnmount']);
1037 });
1038
917 - it('propagates errors inside boundary during componentWillMount', () => {
1039 + it('propagates errors inside boundary during componentWillMount', async () => {
1040 const container = document.createElement('div');
919 - ReactDOM.render(
920 - <ErrorBoundary>
921 - <BrokenComponentWillMountErrorBoundary />
922 - </ErrorBoundary>,
923 - container,
924 - );
1041 + const root = ReactDOMClient.createRoot(container);
1042 + await act(async () => {
1043 + root.render(
1044 + <ErrorBoundary>
1045 + <BrokenComponentWillMountErrorBoundary />
1046 + </ErrorBoundary>,
1047 + );
1048 + });
1049 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
1050 assertLog([
1051 'ErrorBoundary constructor',
@@ -933,23 +1057,34 @@ describe('ReactErrorBoundaries', () => {
1057 'ErrorBoundary static getDerivedStateFromError',
1058 'ErrorBoundary componentWillMount',
1059 'ErrorBoundary render error',
1060 + // logs for error retry
1061 + 'ErrorBoundary constructor',
1062 + 'ErrorBoundary componentWillMount',
1063 + 'ErrorBoundary render success',
1064 + 'BrokenComponentWillMountErrorBoundary constructor',
1065 + 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',
1066 + 'ErrorBoundary static getDerivedStateFromError',
1067 + 'ErrorBoundary componentWillMount',
1068 + 'ErrorBoundary render error',
1069 'ErrorBoundary componentDidMount',
1070 ]);
1071
939 - ReactDOM.unmountComponentAtNode(container);
1072 + root.unmount();
1073 assertLog(['ErrorBoundary componentWillUnmount']);
1074 });
1075
943 - it('propagates errors inside boundary while rendering error state', () => {
1076 + it('propagates errors inside boundary while rendering error state', async () => {
1077 const container = document.createElement('div');
945 - ReactDOM.render(
946 - <ErrorBoundary>
947 - <BrokenRenderErrorBoundary>
948 - <BrokenRender />
949 - </BrokenRenderErrorBoundary>
950 - </ErrorBoundary>,
951 - container,
952 - );
1078 + const root = ReactDOMClient.createRoot(container);
1079 + await act(async () => {
1080 + root.render(
1081 + <ErrorBoundary>
1082 + <BrokenRenderErrorBoundary>
1083 + <BrokenRender />
1084 + </BrokenRenderErrorBoundary>
1085 + </ErrorBoundary>,
1086 + );
1087 + });
1088 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
1089 assertLog([
1090 'ErrorBoundary constructor',
@@ -969,23 +1104,41 @@ describe('ReactErrorBoundaries', () => {
1104 'ErrorBoundary static getDerivedStateFromError',
1105 'ErrorBoundary componentWillMount',
1106 'ErrorBoundary render error',
1107 + // logs for error retry
1108 + 'ErrorBoundary constructor',
1109 + 'ErrorBoundary componentWillMount',
1110 + 'ErrorBoundary render success',
1111 + 'BrokenRenderErrorBoundary constructor',
1112 + 'BrokenRenderErrorBoundary componentWillMount',
1113 + 'BrokenRenderErrorBoundary render success',
1114 + 'BrokenRender constructor',
1115 + 'BrokenRender componentWillMount',
1116 + 'BrokenRender render [!]',
1117 + 'BrokenRenderErrorBoundary static getDerivedStateFromError',
1118 + 'BrokenRenderErrorBoundary componentWillMount',
1119 + 'BrokenRenderErrorBoundary render error [!]',
1120 + 'ErrorBoundary static getDerivedStateFromError',
1121 + 'ErrorBoundary componentWillMount',
1122 + 'ErrorBoundary render error',
1123 'ErrorBoundary componentDidMount',
1124 ]);
1125
975 - ReactDOM.unmountComponentAtNode(container);
1126 + root.unmount();
1127 assertLog(['ErrorBoundary componentWillUnmount']);
1128 });
1129
979 - it('does not call componentWillUnmount when aborting initial mount', () => {
1130 + it('does not call componentWillUnmount when aborting initial mount', async () => {
1131 const container = document.createElement('div');
981 - ReactDOM.render(
982 - <ErrorBoundary>
983 - <Normal />
984 - <BrokenRender />
985 - <Normal />
986 - </ErrorBoundary>,
987 - container,
988 - );
1132 + const root = ReactDOMClient.createRoot(container);
1133 + await act(async () => {
1134 + root.render(
1135 + <ErrorBoundary>
1136 + <Normal />
1137 + <BrokenRender />
1138 + <Normal />
1139 + </ErrorBoundary>,
1140 + );
1141 + });
1142 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
1143 assertLog([
1144 'ErrorBoundary constructor',
@@ -1004,14 +1157,27 @@ describe('ReactErrorBoundaries', () => {
1157 'ErrorBoundary static getDerivedStateFromError',
1158 'ErrorBoundary componentWillMount',
1159 'ErrorBoundary render error',
1160 + // logs for error retry
1161 + 'ErrorBoundary constructor',
1162 + 'ErrorBoundary componentWillMount',
1163 + 'ErrorBoundary render success',
1164 + 'Normal constructor',
1165 + 'Normal componentWillMount',
1166 + 'Normal render',
1167 + 'BrokenRender constructor',
1168 + 'BrokenRender componentWillMount',
1169 + 'BrokenRender render [!]',
1170 + 'ErrorBoundary static getDerivedStateFromError',
1171 + 'ErrorBoundary componentWillMount',
1172 + 'ErrorBoundary render error',
1173 'ErrorBoundary componentDidMount',
1174 ]);
1175
1010 - ReactDOM.unmountComponentAtNode(container);
1176 + root.unmount();
1177 assertLog(['ErrorBoundary componentWillUnmount']);
1178 });
1179
1014 - it('resets callback refs if mounting aborts', () => {
1180 + it('resets callback refs if mounting aborts', async () => {
1181 function childRef(x) {
1182 Scheduler.log('Child ref is set to ' + x);
1183 }
@@ -1020,13 +1186,15 @@ describe('ReactErrorBoundaries', () => {
1186 }
1187
1188 const container = document.createElement('div');
1023 - ReactDOM.render(
1024 - <ErrorBoundary errorMessageRef={errorMessageRef}>
1025 - <div ref={childRef} />
1026 - <BrokenRender />
1027 - </ErrorBoundary>,
1028 - container,
1029 - );
1189 + const root = ReactDOMClient.createRoot(container);
1190 + await act(async () => {
1191 + root.render(
1192 + <ErrorBoundary errorMessageRef={errorMessageRef}>
1193 + <div ref={childRef} />
1194 + <BrokenRender />
1195 + </ErrorBoundary>,
1196 + );
1197 + });
1198 expect(container.textContent).toBe('Caught an error: Hello.');
1199 assertLog([
1200 'ErrorBoundary constructor',
@@ -1039,29 +1207,41 @@ describe('ReactErrorBoundaries', () => {
1207 'ErrorBoundary static getDerivedStateFromError',
1208 'ErrorBoundary componentWillMount',
1209 'ErrorBoundary render error',
1210 + // logs for error retry
1211 + 'ErrorBoundary constructor',
1212 + 'ErrorBoundary componentWillMount',
1213 + 'ErrorBoundary render success',
1214 + 'BrokenRender constructor',
1215 + 'BrokenRender componentWillMount',
1216 + 'BrokenRender render [!]',
1217 + 'ErrorBoundary static getDerivedStateFromError',
1218 + 'ErrorBoundary componentWillMount',
1219 + 'ErrorBoundary render error',
1220 'Error message ref is set to [object HTMLDivElement]',
1221 'ErrorBoundary componentDidMount',
1222 ]);
1223
1046 - ReactDOM.unmountComponentAtNode(container);
1224 + root.unmount();
1225 assertLog([
1226 'ErrorBoundary componentWillUnmount',
1227 'Error message ref is set to null',
1228 ]);
1229 });
1230
1053 - it('resets object refs if mounting aborts', () => {
1231 + it('resets object refs if mounting aborts', async () => {
1232 const childRef = React.createRef();
1233 const errorMessageRef = React.createRef();
1234
1235 const container = document.createElement('div');
1058 - ReactDOM.render(
1059 - <ErrorBoundary errorMessageRef={errorMessageRef}>
1060 - <div ref={childRef} />
1061 - <BrokenRender />
1062 - </ErrorBoundary>,
1063 - container,
1064 - );
1236 + const root = ReactDOMClient.createRoot(container);
1237 + await act(async () => {
1238 + root.render(
1239 + <ErrorBoundary errorMessageRef={errorMessageRef}>
1240 + <div ref={childRef} />
1241 + <BrokenRender />
1242 + </ErrorBoundary>,
1243 + );
1244 + });
1245 expect(container.textContent).toBe('Caught an error: Hello.');
1246 assertLog([
1247 'ErrorBoundary constructor',
@@ -1074,25 +1254,37 @@ describe('ReactErrorBoundaries', () => {
1254 'ErrorBoundary static getDerivedStateFromError',
1255 'ErrorBoundary componentWillMount',
1256 'ErrorBoundary render error',
1257 + // logs for error retry
1258 + 'ErrorBoundary constructor',
1259 + 'ErrorBoundary componentWillMount',
1260 + 'ErrorBoundary render success',
1261 + 'BrokenRender constructor',
1262 + 'BrokenRender componentWillMount',
1263 + 'BrokenRender render [!]',
1264 + 'ErrorBoundary static getDerivedStateFromError',
1265 + 'ErrorBoundary componentWillMount',
1266 + 'ErrorBoundary render error',
1267 'ErrorBoundary componentDidMount',
1268 ]);
1269 expect(errorMessageRef.current.toString()).toEqual(
1270 '[object HTMLDivElement]',
1271 );
1272
1083 - ReactDOM.unmountComponentAtNode(container);
1273 + root.unmount();
1274 assertLog(['ErrorBoundary componentWillUnmount']);
1275 expect(errorMessageRef.current).toEqual(null);
1276 });
1277
1088 - it('successfully mounts if no error occurs', () => {
1278 + it('successfully mounts if no error occurs', async () => {
1279 const container = document.createElement('div');
1090 - ReactDOM.render(
1091 - <ErrorBoundary>
1092 - <div>Mounted successfully.</div>
1093 - </ErrorBoundary>,
1094 - container,
1095 - );
1280 + const root = ReactDOMClient.createRoot(container);
1281 + await act(async () => {
1282 + root.render(
1283 + <ErrorBoundary>
1284 + <div>Mounted successfully.</div>
1285 + </ErrorBoundary>,
1286 + );
1287 + });
1288 expect(container.firstChild.textContent).toBe('Mounted successfully.');
1289 assertLog([
1290 'ErrorBoundary constructor',
@@ -1101,27 +1293,30 @@ describe('ReactErrorBoundaries', () => {
1293 'ErrorBoundary componentDidMount',
1294 ]);
1295
1104 - ReactDOM.unmountComponentAtNode(container);
1296 + root.unmount();
1297 assertLog(['ErrorBoundary componentWillUnmount']);
1298 });
1299
1108 - it('catches if child throws in constructor during update', () => {
1300 + it('catches if child throws in constructor during update', async () => {
1301 const container = document.createElement('div');
1110 - ReactDOM.render(
1111 - <ErrorBoundary>
1112 - <Normal />
1113 - </ErrorBoundary>,
1114 - container,
1115 - );
1302 + const root = ReactDOMClient.createRoot(container);
1303 + await act(async () => {
1304 + root.render(
1305 + <ErrorBoundary>
1306 + <Normal />
1307 + </ErrorBoundary>,
1308 + );
1309 + });
1310 Scheduler.unstable_clearLog();
1117 - ReactDOM.render(
1118 - <ErrorBoundary>
1119 - <Normal />
1120 - <Normal logName="Normal2" />
1121 - <BrokenConstructor />
1122 - </ErrorBoundary>,
1123 - container,
1124 - );
1311 + await act(async () => {
1312 + root.render(
1313 + <ErrorBoundary>
1314 + <Normal />
1315 + <Normal logName="Normal2" />
1316 + <BrokenConstructor />
1317 + </ErrorBoundary>,
1318 + );
1319 + });
1320 expect(container.textContent).toBe('Caught an error: Hello.');
1321 assertLog([
1322 'ErrorBoundary componentWillReceiveProps',
@@ -1141,32 +1336,49 @@ describe('ReactErrorBoundaries', () => {
1336 // Render the error message
1337 'ErrorBoundary componentWillUpdate',
1338 'ErrorBoundary render error',
1339 + // logs for error retry
1340 + 'ErrorBoundary componentWillReceiveProps',
1341 + 'ErrorBoundary componentWillUpdate',
1342 + 'ErrorBoundary render success',
1343 + 'Normal componentWillReceiveProps',
1344 + 'Normal componentWillUpdate',
1345 + 'Normal render',
1346 + 'Normal2 constructor',
1347 + 'Normal2 componentWillMount',
1348 + 'Normal2 render',
1349 + 'BrokenConstructor constructor [!]',
1350 + 'ErrorBoundary static getDerivedStateFromError',
1351 + 'ErrorBoundary componentWillUpdate',
1352 + 'ErrorBoundary render error',
1353 'Normal componentWillUnmount',
1354 'ErrorBoundary componentDidUpdate',
1355 ]);
1356
1148 - ReactDOM.unmountComponentAtNode(container);
1357 + root.unmount();
1358 assertLog(['ErrorBoundary componentWillUnmount']);
1359 });
1360
1152 - it('catches if child throws in componentWillMount during update', () => {
1361 + it('catches if child throws in componentWillMount during update', async () => {
1362 const container = document.createElement('div');
1154 - ReactDOM.render(
1155 - <ErrorBoundary>
1156 - <Normal />
1157 - </ErrorBoundary>,
1158 - container,
1159 - );
1363 + const root = ReactDOMClient.createRoot(container);
1364 + await act(async () => {
1365 + root.render(
1366 + <ErrorBoundary>
1367 + <Normal />
1368 + </ErrorBoundary>,
1369 + );
1370 + });
1371
1372 Scheduler.unstable_clearLog();
1162 - ReactDOM.render(
1163 - <ErrorBoundary>
1164 - <Normal />
1165 - <Normal logName="Normal2" />
1166 - <BrokenComponentWillMount />
1167 - </ErrorBoundary>,
1168 - container,
1169 - );
1373 + await act(async () => {
1374 + root.render(
1375 + <ErrorBoundary>
1376 + <Normal />
1377 + <Normal logName="Normal2" />
1378 + <BrokenComponentWillMount />
1379 + </ErrorBoundary>,
1380 + );
1381 + });
1382 expect(container.textContent).toBe('Caught an error: Hello.');
1383 assertLog([
1384 'ErrorBoundary componentWillReceiveProps',
@@ -1187,32 +1399,50 @@ describe('ReactErrorBoundaries', () => {
1399 // Render the error message
1400 'ErrorBoundary componentWillUpdate',
1401 'ErrorBoundary render error',
1402 + // logs for error retry
1403 + 'ErrorBoundary componentWillReceiveProps',
1404 + 'ErrorBoundary componentWillUpdate',
1405 + 'ErrorBoundary render success',
1406 + 'Normal componentWillReceiveProps',
1407 + 'Normal componentWillUpdate',
1408 + 'Normal render',
1409 + 'Normal2 constructor',
1410 + 'Normal2 componentWillMount',
1411 + 'Normal2 render',
1412 + 'BrokenComponentWillMount constructor',
1413 + 'BrokenComponentWillMount componentWillMount [!]',
1414 + 'ErrorBoundary static getDerivedStateFromError',
1415 + 'ErrorBoundary componentWillUpdate',
1416 + 'ErrorBoundary render error',
1417 'Normal componentWillUnmount',
1418 'ErrorBoundary componentDidUpdate',
1419 ]);
1420
1194 - ReactDOM.unmountComponentAtNode(container);
1421 + root.unmount();
1422 assertLog(['ErrorBoundary componentWillUnmount']);
1423 });
1424
1198 - it('catches if child throws in componentWillReceiveProps during update', () => {
1425 + it('catches if child throws in componentWillReceiveProps during update', async () => {
1426 const container = document.createElement('div');
1200 - ReactDOM.render(
1201 - <ErrorBoundary>
1202 - <Normal />
1203 - <BrokenComponentWillReceiveProps />
1204 - </ErrorBoundary>,
1205 - container,
1206 - );
1427 + const root = ReactDOMClient.createRoot(container);
1428 + await act(async () => {
1429 + root.render(
1430 + <ErrorBoundary>
1431 + <Normal />
1432 + <BrokenComponentWillReceiveProps />
1433 + </ErrorBoundary>,
1434 + );
1435 + });
1436
1437 Scheduler.unstable_clearLog();
1209 - ReactDOM.render(
1210 - <ErrorBoundary>
1211 - <Normal />
1212 - <BrokenComponentWillReceiveProps />
1213 - </ErrorBoundary>,
1214 - container,
1215 - );
1438 + await act(async () => {
1439 + root.render(
1440 + <ErrorBoundary>
1441 + <Normal />
1442 + <BrokenComponentWillReceiveProps />
1443 + </ErrorBoundary>,
1444 + );
1445 + });
1446 expect(container.textContent).toBe('Caught an error: Hello.');
1447 assertLog([
1448 'ErrorBoundary componentWillReceiveProps',
@@ -1228,33 +1458,47 @@ describe('ReactErrorBoundaries', () => {
1458 // Render the error message
1459 'ErrorBoundary componentWillUpdate',
1460 'ErrorBoundary render error',
1461 + // logs for error retry
1462 + 'ErrorBoundary componentWillReceiveProps',
1463 + 'ErrorBoundary componentWillUpdate',
1464 + 'ErrorBoundary render success',
1465 + 'Normal componentWillReceiveProps',
1466 + 'Normal componentWillUpdate',
1467 + 'Normal render',
1468 + 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',
1469 + 'ErrorBoundary static getDerivedStateFromError',
1470 + 'ErrorBoundary componentWillUpdate',
1471 + 'ErrorBoundary render error',
1472 'Normal componentWillUnmount',
1473 'BrokenComponentWillReceiveProps componentWillUnmount',
1474 'ErrorBoundary componentDidUpdate',
1475 ]);
1476
1236 - ReactDOM.unmountComponentAtNode(container);
1477 + root.unmount();
1478 assertLog(['ErrorBoundary componentWillUnmount']);
1479 });
1480
1240 - it('catches if child throws in componentWillUpdate during update', () => {
1481 + it('catches if child throws in componentWillUpdate during update', async () => {
1482 const container = document.createElement('div');
1242 - ReactDOM.render(
1243 - <ErrorBoundary>
1244 - <Normal />
1245 - <BrokenComponentWillUpdate />
1246 - </ErrorBoundary>,
1247 - container,
1248 - );
1483 + const root = ReactDOMClient.createRoot(container);
1484 + await act(async () => {
1485 + root.render(
1486 + <ErrorBoundary>
1487 + <Normal />
1488 + <BrokenComponentWillUpdate />
1489 + </ErrorBoundary>,
1490 + );
1491 + });
1492
1493 Scheduler.unstable_clearLog();
1251 - ReactDOM.render(
1252 - <ErrorBoundary>
1253 - <Normal />
1254 - <BrokenComponentWillUpdate />
1255 - </ErrorBoundary>,
1256 - container,
1257 - );
1494 + await act(async () => {
1495 + root.render(
1496 + <ErrorBoundary>
1497 + <Normal />
1498 + <BrokenComponentWillUpdate />
1499 + </ErrorBoundary>,
1500 + );
1501 + });
1502 expect(container.textContent).toBe('Caught an error: Hello.');
1503 assertLog([
1504 'ErrorBoundary componentWillReceiveProps',
@@ -1263,10 +1507,22 @@ describe('ReactErrorBoundaries', () => {
1507 'Normal componentWillReceiveProps',
1508 'Normal componentWillUpdate',
1509 'Normal render',
1266 - // BrokenComponentWillUpdate will abort rendering:
1510 + // BrokenComponentWillUpdate will abort rendering:
1511 + 'BrokenComponentWillUpdate componentWillReceiveProps',
1512 + 'BrokenComponentWillUpdate componentWillUpdate [!]',
1513 + // Handle the error
1514 + 'ErrorBoundary static getDerivedStateFromError',
1515 + 'ErrorBoundary componentWillUpdate',
1516 + 'ErrorBoundary render error',
1517 + // logs for error retry
1518 + 'ErrorBoundary componentWillReceiveProps',
1519 + 'ErrorBoundary componentWillUpdate',
1520 + 'ErrorBoundary render success',
1521 + 'Normal componentWillReceiveProps',
1522 + 'Normal componentWillUpdate',
1523 + 'Normal render',
1524 'BrokenComponentWillUpdate componentWillReceiveProps',
1525 'BrokenComponentWillUpdate componentWillUpdate [!]',
1269 - // Handle the error
1526 'ErrorBoundary static getDerivedStateFromError',
1527 'ErrorBoundary componentWillUpdate',
1528 'ErrorBoundary render error',
@@ -1275,28 +1531,31 @@ describe('ReactErrorBoundaries', () => {
1531 'ErrorBoundary componentDidUpdate',
1532 ]);
1533
1278 - ReactDOM.unmountComponentAtNode(container);
1534 + root.unmount();
1535 assertLog(['ErrorBoundary componentWillUnmount']);
1536 });
1537
1282 - it('catches if child throws in render during update', () => {
1538 + it('catches if child throws in render during update', async () => {
1539 const container = document.createElement('div');
1284 - ReactDOM.render(
1285 - <ErrorBoundary>
1286 - <Normal />
1287 - </ErrorBoundary>,
1288 - container,
1289 - );
1540 + const root = ReactDOMClient.createRoot(container);
1541 + await act(async () => {
1542 + root.render(
1543 + <ErrorBoundary>
1544 + <Normal />
1545 + </ErrorBoundary>,
1546 + );
1547 + });
1548
1549 Scheduler.unstable_clearLog();
1292 - ReactDOM.render(
1293 - <ErrorBoundary>
1294 - <Normal />
1295 - <Normal logName="Normal2" />
1296 - <BrokenRender />
1297 - </ErrorBoundary>,
1298 - container,
1299 - );
1550 + await act(async () => {
1551 + root.render(
1552 + <ErrorBoundary>
1553 + <Normal />
1554 + <Normal logName="Normal2" />
1555 + <BrokenRender />
1556 + </ErrorBoundary>,
1557 + );
1558 + });
1559 expect(container.textContent).toBe('Caught an error: Hello.');
1560 assertLog([
1561 'ErrorBoundary componentWillReceiveProps',
@@ -1317,15 +1576,31 @@ describe('ReactErrorBoundaries', () => {
1576 'ErrorBoundary static getDerivedStateFromError',
1577 'ErrorBoundary componentWillUpdate',
1578 'ErrorBoundary render error',
1579 + // logs for error retry
1580 + 'ErrorBoundary componentWillReceiveProps',
1581 + 'ErrorBoundary componentWillUpdate',
1582 + 'ErrorBoundary render success',
1583 + 'Normal componentWillReceiveProps',
1584 + 'Normal componentWillUpdate',
1585 + 'Normal render',
1586 + 'Normal2 constructor',
1587 + 'Normal2 componentWillMount',
1588 + 'Normal2 render',
1589 + 'BrokenRender constructor',
1590 + 'BrokenRender componentWillMount',
1591 + 'BrokenRender render [!]',
1592 + 'ErrorBoundary static getDerivedStateFromError',
1593 + 'ErrorBoundary componentWillUpdate',
1594 + 'ErrorBoundary render error',
1595 'Normal componentWillUnmount',
1596 'ErrorBoundary componentDidUpdate',
1597 ]);
1598
1324 - ReactDOM.unmountComponentAtNode(container);
1599 + root.unmount();
1600 assertLog(['ErrorBoundary componentWillUnmount']);
1601 });
1602
1328 - it('keeps refs up-to-date during updates', () => {
1603 + it('keeps refs up-to-date during updates', async () => {
1604 function child1Ref(x) {
1605 Scheduler.log('Child1 ref is set to ' + x);
1606 }
@@ -1337,12 +1612,14 @@ describe('ReactErrorBoundaries', () => {
1612 }
1613
1614 const container = document.createElement('div');
1340 - ReactDOM.render(
1341 - <ErrorBoundary errorMessageRef={errorMessageRef}>
1342 - <div ref={child1Ref} />
1343 - </ErrorBoundary>,
1344 - container,
1345 - );
1615 + const root = ReactDOMClient.createRoot(container);
1616 + await act(async () => {
1617 + root.render(
1618 + <ErrorBoundary errorMessageRef={errorMessageRef}>
1619 + <div ref={child1Ref} />
1620 + </ErrorBoundary>,
1621 + );
1622 + });
1623 assertLog([
1624 'ErrorBoundary constructor',
1625 'ErrorBoundary componentWillMount',
@@ -1351,14 +1628,15 @@ describe('ReactErrorBoundaries', () => {
1628 'ErrorBoundary componentDidMount',
1629 ]);
1630
1354 - ReactDOM.render(
1355 - <ErrorBoundary errorMessageRef={errorMessageRef}>
1356 - <div ref={child1Ref} />
1357 - <div ref={child2Ref} />
1358 - <BrokenRender />
1359 - </ErrorBoundary>,
1360 - container,
1361 - );
1631 + await act(async () => {
1632 + root.render(
1633 + <ErrorBoundary errorMessageRef={errorMessageRef}>
1634 + <div ref={child1Ref} />
1635 + <div ref={child2Ref} />
1636 + <BrokenRender />
1637 + </ErrorBoundary>,
1638 + );
1639 + });
1640 expect(container.textContent).toBe('Caught an error: Hello.');
1641 assertLog([
1642 'ErrorBoundary componentWillReceiveProps',
@@ -1372,6 +1650,16 @@ describe('ReactErrorBoundaries', () => {
1650 'ErrorBoundary static getDerivedStateFromError',
1651 'ErrorBoundary componentWillUpdate',
1652 'ErrorBoundary render error',
1653 + // logs for error retry
1654 + 'ErrorBoundary componentWillReceiveProps',
1655 + 'ErrorBoundary componentWillUpdate',
1656 + 'ErrorBoundary render success',
1657 + 'BrokenRender constructor',
1658 + 'BrokenRender componentWillMount',
1659 + 'BrokenRender render [!]',
1660 + 'ErrorBoundary static getDerivedStateFromError',
1661 + 'ErrorBoundary componentWillUpdate',
1662 + 'ErrorBoundary render error',
1663 // Update Child1 ref since Child1 has been unmounted
1664 // Child2 ref is never set because its mounting aborted
1665 'Child1 ref is set to null',
@@ -1379,31 +1667,34 @@ describe('ReactErrorBoundaries', () => {
1667 'ErrorBoundary componentDidUpdate',
1668 ]);
1669
1382 - ReactDOM.unmountComponentAtNode(container);
1670 + root.unmount();
1671 assertLog([
1672 'ErrorBoundary componentWillUnmount',
1673 'Error message ref is set to null',
1674 ]);
1675 });
1676
1389 - it('recovers from componentWillUnmount errors on update', () => {
1677 + it('recovers from componentWillUnmount errors on update', async () => {
1678 const container = document.createElement('div');
1391 - ReactDOM.render(
1392 - <ErrorBoundary>
1393 - <BrokenComponentWillUnmount />
1394 - <BrokenComponentWillUnmount />
1395 - <Normal />
1396 - </ErrorBoundary>,
1397 - container,
1398 - );
1679 + const root = ReactDOMClient.createRoot(container);
1680 + await act(async () => {
1681 + root.render(
1682 + <ErrorBoundary>
1683 + <BrokenComponentWillUnmount />
1684 + <BrokenComponentWillUnmount />
1685 + <Normal />
1686 + </ErrorBoundary>,
1687 + );
1688 + });
1689
1690 Scheduler.unstable_clearLog();
1401 - ReactDOM.render(
1402 - <ErrorBoundary>
1403 - <BrokenComponentWillUnmount />
1404 - </ErrorBoundary>,
1405 - container,
1406 - );
1691 + await act(async () => {
1692 + root.render(
1693 + <ErrorBoundary>
1694 + <BrokenComponentWillUnmount />
1695 + </ErrorBoundary>,
1696 + );
1697 + });
1698 expect(container.textContent).toBe('Caught an error: Hello.');
1699 assertLog([
1700 'ErrorBoundary componentWillReceiveProps',
@@ -1437,31 +1728,34 @@ describe('ReactErrorBoundaries', () => {
1728 'ErrorBoundary componentDidUpdate',
1729 ]);
1730
1440 - ReactDOM.unmountComponentAtNode(container);
1731 + root.unmount();
1732 assertLog(['ErrorBoundary componentWillUnmount']);
1733 });
1734
1444 - it('recovers from nested componentWillUnmount errors on update', () => {
1735 + it('recovers from nested componentWillUnmount errors on update', async () => {
1736 const container = document.createElement('div');
1446 - ReactDOM.render(
1447 - <ErrorBoundary>
1448 - <Normal>
1737 + const root = ReactDOMClient.createRoot(container);
1738 + await act(async () => {
1739 + root.render(
1740 + <ErrorBoundary>
1741 + <Normal>
1742 + <BrokenComponentWillUnmount />
1743 + </Normal>
1744 <BrokenComponentWillUnmount />
1450 - </Normal>
1451 - <BrokenComponentWillUnmount />
1452 - </ErrorBoundary>,
1453 - container,
1454 - );
1745 + </ErrorBoundary>,
1746 + );
1747 + });
1748
1749 Scheduler.unstable_clearLog();
1457 - ReactDOM.render(
1458 - <ErrorBoundary>
1459 - <Normal>
1460 - <BrokenComponentWillUnmount />
1461 - </Normal>
1462 - </ErrorBoundary>,
1463 - container,
1464 - );
1750 + await act(async () => {
1751 + root.render(
1752 + <ErrorBoundary>
1753 + <Normal>
1754 + <BrokenComponentWillUnmount />
1755 + </Normal>
1756 + </ErrorBoundary>,
1757 + );
1758 + });
1759 expect(container.textContent).toBe('Caught an error: Hello.');
1760 assertLog([
1761 'ErrorBoundary componentWillReceiveProps',
@@ -1496,11 +1790,11 @@ describe('ReactErrorBoundaries', () => {
1790 'ErrorBoundary componentDidUpdate',
1791 ]);
1792
1499 - ReactDOM.unmountComponentAtNode(container);
1793 + root.unmount();
1794 assertLog(['ErrorBoundary componentWillUnmount']);
1795 });
1796
1503 - it('picks the right boundary when handling unmounting errors', () => {
1797 + it('picks the right boundary when handling unmounting errors', async () => {
1798 function renderInnerError(error) {
1799 return <div>Caught an inner error: {error.message}.</div>;
1800 }
@@ -1509,31 +1803,34 @@ describe('ReactErrorBoundaries', () => {
1803 }
1804
1805 const container = document.createElement('div');
1512 - ReactDOM.render(
1513 - <ErrorBoundary
1514 - logName="OuterErrorBoundary"
1515 - renderError={renderOuterError}>
1806 + const root = ReactDOMClient.createRoot(container);
1807 + await act(async () => {
1808 + root.render(
1809 <ErrorBoundary
1517 - logName="InnerErrorBoundary"
1518 - renderError={renderInnerError}>
1519 - <BrokenComponentWillUnmount />
1520 - </ErrorBoundary>
1521 - </ErrorBoundary>,
1522 - container,
1523 - );
1810 + logName="OuterErrorBoundary"
1811 + renderError={renderOuterError}>
1812 + <ErrorBoundary
1813 + logName="InnerErrorBoundary"
1814 + renderError={renderInnerError}>
1815 + <BrokenComponentWillUnmount />
1816 + </ErrorBoundary>
1817 + </ErrorBoundary>,
1818 + );
1819 + });
1820
1821 Scheduler.unstable_clearLog();
1526 - ReactDOM.render(
1527 - <ErrorBoundary
1528 - logName="OuterErrorBoundary"
1529 - renderError={renderOuterError}>
1822 + await act(async () => {
1823 + root.render(
1824 <ErrorBoundary
1531 - logName="InnerErrorBoundary"
1532 - renderError={renderInnerError}
1533 - />
1534 - </ErrorBoundary>,
1535 - container,
1536 - );
1825 + logName="OuterErrorBoundary"
1826 + renderError={renderOuterError}>
1827 + <ErrorBoundary
1828 + logName="InnerErrorBoundary"
1829 + renderError={renderInnerError}
1830 + />
1831 + </ErrorBoundary>,
1832 + );
1833 + });
1834 expect(container.textContent).toBe('Caught an inner error: Hello.');
1835 assertLog([
1836 // Update outer boundary
@@ -1559,39 +1856,43 @@ describe('ReactErrorBoundaries', () => {
1856 'InnerErrorBoundary componentDidUpdate',
1857 ]);
1858
1562 - ReactDOM.unmountComponentAtNode(container);
1859 + root.unmount();
1860 assertLog([
1861 'OuterErrorBoundary componentWillUnmount',
1862 'InnerErrorBoundary componentWillUnmount',
1863 ]);
1864 });
1865
1569 - it('can recover from error state', () => {
1866 + it('can recover from error state', async () => {
1867 const container = document.createElement('div');
1571 - ReactDOM.render(
1572 - <ErrorBoundary>
1573 - <BrokenRender />
1574 - </ErrorBoundary>,
1575 - container,
1576 - );
1868 + const root = ReactDOMClient.createRoot(container);
1869 + await act(async () => {
1870 + root.render(
1871 + <ErrorBoundary>
1872 + <BrokenRender />
1873 + </ErrorBoundary>,
1874 + );
1875 + });
1876
1578 - ReactDOM.render(
1579 - <ErrorBoundary>
1580 - <Normal />
1581 - </ErrorBoundary>,
1582 - container,
1583 - );
1877 + await act(async () => {
1878 + root.render(
1879 + <ErrorBoundary>
1880 + <Normal />
1881 + </ErrorBoundary>,
1882 + );
1883 + });
1884 // Error boundary doesn't retry by itself:
1885 expect(container.textContent).toBe('Caught an error: Hello.');
1886
1887 // Force the success path:
1888 Scheduler.unstable_clearLog();
1589 - ReactDOM.render(
1590 - <ErrorBoundary forceRetry={true}>
1591 - <Normal />
1592 - </ErrorBoundary>,
1593 - container,
1594 - );
1889 + await act(async () => {
1890 + root.render(
1891 + <ErrorBoundary forceRetry={true}>
1892 + <Normal />
1893 + </ErrorBoundary>,
1894 + );
1895 + });
1896 expect(container.textContent).not.toContain('Caught an error');
1897 assertLog([
1898 'ErrorBoundary componentWillReceiveProps',
@@ -1606,75 +1907,88 @@ describe('ReactErrorBoundaries', () => {
1907 'ErrorBoundary componentDidUpdate',
1908 ]);
1909
1609 - ReactDOM.unmountComponentAtNode(container);
1910 + root.unmount();
1911 assertLog([
1912 'ErrorBoundary componentWillUnmount',
1913 'Normal componentWillUnmount',
1914 ]);
1915 });
1916
1616 - it('can update multiple times in error state', () => {
1917 + it('can update multiple times in error state', async () => {
1918 const container = document.createElement('div');
1618 - ReactDOM.render(
1619 - <ErrorBoundary>
1620 - <BrokenRender />
1621 - </ErrorBoundary>,
1622 - container,
1623 - );
1919 + const root = ReactDOMClient.createRoot(container);
1920 + await act(async () => {
1921 + root.render(
1922 + <ErrorBoundary>
1923 + <BrokenRender />
1924 + </ErrorBoundary>,
1925 + );
1926 + });
1927 expect(container.textContent).toBe('Caught an error: Hello.');
1928
1626 - ReactDOM.render(
1627 - <ErrorBoundary>
1628 - <BrokenRender />
1629 - </ErrorBoundary>,
1630 - container,
1631 - );
1929 + await act(async () => {
1930 + root.render(
1931 + <ErrorBoundary>
1932 + <BrokenRender />
1933 + </ErrorBoundary>,
1934 + );
1935 + });
1936 expect(container.textContent).toBe('Caught an error: Hello.');
1937
1634 - ReactDOM.render(<div>Other screen</div>, container);
1938 + await act(async () => {
1939 + root.render(<div>Other screen</div>);
1940 + });
1941 expect(container.textContent).toBe('Other screen');
1942
1637 - ReactDOM.unmountComponentAtNode(container);
1943 + root.unmount();
1944 });
1945
1640 - it("doesn't get into inconsistent state during removals", () => {
1946 + it("doesn't get into inconsistent state during removals", async () => {
1947 const container = document.createElement('div');
1642 - ReactDOM.render(
1643 - <ErrorBoundary>
1644 - <Normal />
1645 - <BrokenComponentWillUnmount />
1646 - <Normal />
1647 - </ErrorBoundary>,
1648 - container,
1649 - );
1948 + const root = ReactDOMClient.createRoot(container);
1949 + await act(async () => {
1950 + root.render(
1951 + <ErrorBoundary>
1952 + <Normal />
1953 + <BrokenComponentWillUnmount />
1954 + <Normal />
1955 + </ErrorBoundary>,
1956 + );
1957 + });
1958
1651 - ReactDOM.render(<ErrorBoundary />, container);
1959 + await act(async () => {
1960 + root.render(<ErrorBoundary />);
1961 + });
1962 expect(container.textContent).toBe('Caught an error: Hello.');
1963
1964 Scheduler.unstable_clearLog();
1655 - ReactDOM.unmountComponentAtNode(container);
1965 + root.unmount();
1966 assertLog(['ErrorBoundary componentWillUnmount']);
1967 });
1968
1659 - it("doesn't get into inconsistent state during additions", () => {
1969 + it("doesn't get into inconsistent state during additions", async () => {
1970 const container = document.createElement('div');
1661 - ReactDOM.render(<ErrorBoundary />, container);
1662 - ReactDOM.render(
1663 - <ErrorBoundary>
1664 - <Normal />
1665 - <BrokenRender />
1666 - <Normal />
1667 - </ErrorBoundary>,
1668 - container,
1669 - );
1971 + const root = ReactDOMClient.createRoot(container);
1972 + await act(async () => {
1973 + root.render(<ErrorBoundary />);
1974 + });
1975 + await act(async () => {
1976 + root.render(
1977 + <ErrorBoundary>
1978 + <Normal />
1979 + <BrokenRender />
1980 + <Normal />
1981 + </ErrorBoundary>,
1982 + );
1983 + });
1984 expect(container.textContent).toBe('Caught an error: Hello.');
1985
1986 Scheduler.unstable_clearLog();
1673 - ReactDOM.unmountComponentAtNode(container);
1987 + root.unmount();
1988 assertLog(['ErrorBoundary componentWillUnmount']);
1989 });
1990
1677 - it("doesn't get into inconsistent state during reorders", () => {
1991 + it("doesn't get into inconsistent state during reorders", async () => {
1992 function getAMixOfNormalAndBrokenRenderElements() {
1993 const elements = [];
1994 for (let i = 0; i < 100; i++) {
@@ -1704,25 +2018,32 @@ describe('ReactErrorBoundaries', () => {
2018
2019 let fail = false;
2020 const container = document.createElement('div');
1707 - ReactDOM.render(
1708 - <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,
1709 - container,
1710 - );
2021 + const root = ReactDOMClient.createRoot(container);
2022 + await act(async () => {
2023 + root.render(
2024 + <ErrorBoundary>
2025 + {getAMixOfNormalAndBrokenRenderElements()}
2026 + </ErrorBoundary>,
2027 + );
2028 + });
2029 expect(container.textContent).not.toContain('Caught an error');
2030
2031 fail = true;
1714 - ReactDOM.render(
1715 - <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,
1716 - container,
1717 - );
2032 + await act(async () => {
2033 + root.render(
2034 + <ErrorBoundary>
2035 + {getAMixOfNormalAndBrokenRenderElements()}
2036 + </ErrorBoundary>,
2037 + );
2038 + });
2039 expect(container.textContent).toBe('Caught an error: Hello.');
2040
2041 Scheduler.unstable_clearLog();
1721 - ReactDOM.unmountComponentAtNode(container);
2042 + root.unmount();
2043 assertLog(['ErrorBoundary componentWillUnmount']);
2044 });
2045
1725 - it('catches errors originating downstream', () => {
2046 + it('catches errors originating downstream', async () => {
2047 let fail = false;
2048 class Stateful extends React.Component {
2049 state = {shouldThrow: false};
@@ -1738,43 +2059,38 @@ describe('ReactErrorBoundaries', () => {
2059
2060 let statefulInst;
2061 const container = document.createElement('div');
1741 - ReactDOM.render(
1742 - <ErrorBoundary>
1743 - <Stateful ref={inst => (statefulInst = inst)} />
1744 - </ErrorBoundary>,
1745 - container,
1746 - );
2062 + const root = ReactDOMClient.createRoot(container);
2063 + await act(async () => {
2064 + root.render(
2065 + <ErrorBoundary>
2066 + <Stateful ref={inst => (statefulInst = inst)} />
2067 + </ErrorBoundary>,
2068 + );
2069 + });
2070
2071 Scheduler.unstable_clearLog();
2072 expect(() => {
2073 fail = true;
2074 statefulInst.forceUpdate();
2075 }).not.toThrow();
1753 -
1754 - assertLog([
1755 - 'Stateful render [!]',
1756 - 'ErrorBoundary static getDerivedStateFromError',
1757 - 'ErrorBoundary componentWillUpdate',
1758 - 'ErrorBoundary render error',
1759 - 'ErrorBoundary componentDidUpdate',
1760 - ]);
1761 -
1762 - ReactDOM.unmountComponentAtNode(container);
2076 + root.unmount();
2077 assertLog(['ErrorBoundary componentWillUnmount']);
2078 });
2079
1766 - it('catches errors in componentDidMount', () => {
2080 + it('catches errors in componentDidMount', async () => {
2081 const container = document.createElement('div');
1768 - ReactDOM.render(
1769 - <ErrorBoundary>
1770 - <BrokenComponentWillUnmount>
1771 - <Normal />
1772 - </BrokenComponentWillUnmount>
1773 - <BrokenComponentDidMount />
1774 - <Normal logName="LastChild" />
1775 - </ErrorBoundary>,
1776 - container,
1777 - );
2082 + const root = ReactDOMClient.createRoot(container);
2083 + await act(async () => {
2084 + root.render(
2085 + <ErrorBoundary>
2086 + <BrokenComponentWillUnmount>
2087 + <Normal />
2088 + </BrokenComponentWillUnmount>
2089 + <BrokenComponentDidMount />
2090 + <Normal logName="LastChild" />
2091 + </ErrorBoundary>,
2092 + );
2093 + });
2094 assertLog([
2095 'ErrorBoundary constructor',
2096 'ErrorBoundary componentWillMount',
@@ -1817,26 +2133,29 @@ describe('ReactErrorBoundaries', () => {
2133 'ErrorBoundary componentDidUpdate',
2134 ]);
2135
1820 - ReactDOM.unmountComponentAtNode(container);
2136 + root.unmount();
2137 assertLog(['ErrorBoundary componentWillUnmount']);
2138 });
2139
1824 - it('catches errors in componentDidUpdate', () => {
2140 + it('catches errors in componentDidUpdate', async () => {
2141 const container = document.createElement('div');
1826 - ReactDOM.render(
1827 - <ErrorBoundary>
1828 - <BrokenComponentDidUpdate />
1829 - </ErrorBoundary>,
1830 - container,
1831 - );
2142 + const root = ReactDOMClient.createRoot(container);
2143 + await act(async () => {
2144 + root.render(
2145 + <ErrorBoundary>
2146 + <BrokenComponentDidUpdate />
2147 + </ErrorBoundary>,
2148 + );
2149 + });
2150
2151 Scheduler.unstable_clearLog();
1834 - ReactDOM.render(
1835 - <ErrorBoundary>
1836 - <BrokenComponentDidUpdate />
1837 - </ErrorBoundary>,
1838 - container,
1839 - );
2152 + await act(async () => {
2153 + root.render(
2154 + <ErrorBoundary>
2155 + <BrokenComponentDidUpdate />
2156 + </ErrorBoundary>,
2157 + );
2158 + });
2159 assertLog([
2160 'ErrorBoundary componentWillReceiveProps',
2161 'ErrorBoundary componentWillUpdate',
@@ -1855,33 +2174,29 @@ describe('ReactErrorBoundaries', () => {
2174 'ErrorBoundary componentDidUpdate',
2175 ]);
2176
1858 - ReactDOM.unmountComponentAtNode(container);
2177 + root.unmount();
2178 assertLog(['ErrorBoundary componentWillUnmount']);
2179 });
2180
2181 it('catches errors in useEffect', async () => {
2182 const container = document.createElement('div');
2183 + const root = ReactDOMClient.createRoot(container);
2184 await act(() => {
1865 - ReactDOM.render(
2185 + root.render(
2186 <ErrorBoundary>
2187 <BrokenUseEffect>Initial value</BrokenUseEffect>
2188 </ErrorBoundary>,
1869 - container,
2189 );
1871 - assertLog([
1872 - 'ErrorBoundary constructor',
1873 - 'ErrorBoundary componentWillMount',
1874 - 'ErrorBoundary render success',
1875 - 'BrokenUseEffect render',
1876 - 'ErrorBoundary componentDidMount',
1877 - ]);
1878 -
1879 - expect(container.firstChild.textContent).toBe('Initial value');
1880 - Scheduler.unstable_clearLog();
2190 });
2191
2192 // verify flushed passive effects and handle the error
2193 assertLog([
2194 + // logs for error retry
2195 + 'ErrorBoundary constructor',
2196 + 'ErrorBoundary componentWillMount',
2197 + 'ErrorBoundary render success',
2198 + 'BrokenUseEffect render',
2199 + 'ErrorBoundary componentDidMount',
2200 'BrokenUseEffect useEffect [!]',
2201 // Handle the error
2202 'ErrorBoundary static getDerivedStateFromError',
@@ -1889,18 +2204,19 @@ describe('ReactErrorBoundaries', () => {
2204 'ErrorBoundary render error',
2205 'ErrorBoundary componentDidUpdate',
2206 ]);
1892 -
2207 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
2208 });
2209
1896 - it('catches errors in useLayoutEffect', () => {
2210 + it('catches errors in useLayoutEffect', async () => {
2211 const container = document.createElement('div');
1898 - ReactDOM.render(
1899 - <ErrorBoundary>
1900 - <BrokenUseLayoutEffect>Initial value</BrokenUseLayoutEffect>
1901 - </ErrorBoundary>,
1902 - container,
1903 - );
2212 + const root = ReactDOMClient.createRoot(container);
2213 + await act(async () => {
2214 + root.render(
2215 + <ErrorBoundary>
2216 + <BrokenUseLayoutEffect>Initial value</BrokenUseLayoutEffect>
2217 + </ErrorBoundary>,
2218 + );
2219 + });
2220 assertLog([
2221 'ErrorBoundary constructor',
2222 'ErrorBoundary componentWillMount',
@@ -1920,18 +2236,20 @@ describe('ReactErrorBoundaries', () => {
2236 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
2237 });
2238
1923 - it('propagates errors inside boundary during componentDidMount', () => {
2239 + it('propagates errors inside boundary during componentDidMount', async () => {
2240 const container = document.createElement('div');
1925 - ReactDOM.render(
1926 - <ErrorBoundary>
1927 - <BrokenComponentDidMountErrorBoundary
1928 - renderError={error => (
1929 - <div>We should never catch our own error: {error.message}.</div>
1930 - )}
1931 - />
1932 - </ErrorBoundary>,
1933 - container,
1934 - );
2241 + const root = ReactDOMClient.createRoot(container);
2242 + await act(async () => {
2243 + root.render(
2244 + <ErrorBoundary>
2245 + <BrokenComponentDidMountErrorBoundary
2246 + renderError={error => (
2247 + <div>We should never catch our own error: {error.message}.</div>
2248 + )}
2249 + />
2250 + </ErrorBoundary>,
2251 + );
2252 + });
2253 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
2254 assertLog([
2255 'ErrorBoundary constructor',
@@ -1952,11 +2270,11 @@ describe('ReactErrorBoundaries', () => {
2270 'ErrorBoundary componentDidUpdate',
2271 ]);
2272
1955 - ReactDOM.unmountComponentAtNode(container);
2273 + root.unmount();
2274 assertLog(['ErrorBoundary componentWillUnmount']);
2275 });
2276
1959 - it('calls static getDerivedStateFromError for each error that is captured', () => {
2277 + it('calls static getDerivedStateFromError for each error that is captured', async () => {
2278 function renderUnmountError(error) {
2279 return <div>Caught an unmounting error: {error.message}.</div>;
2280 }
@@ -1965,40 +2283,43 @@ describe('ReactErrorBoundaries', () => {
2283 }
2284
2285 const container = document.createElement('div');
1968 - ReactDOM.render(
1969 - <ErrorBoundary logName="OuterErrorBoundary">
1970 - <ErrorBoundary
1971 - logName="InnerUnmountBoundary"
1972 - renderError={renderUnmountError}>
1973 - <BrokenComponentWillUnmount errorText="E1" />
1974 - <BrokenComponentWillUnmount errorText="E2" />
1975 - </ErrorBoundary>
1976 - <ErrorBoundary
1977 - logName="InnerUpdateBoundary"
1978 - renderError={renderUpdateError}>
1979 - <BrokenComponentDidUpdate errorText="E3" />
1980 - <BrokenComponentDidUpdate errorText="E4" />
1981 - </ErrorBoundary>
1982 - </ErrorBoundary>,
1983 - container,
1984 - );
2286 + const root = ReactDOMClient.createRoot(container);
2287 + await act(async () => {
2288 + root.render(
2289 + <ErrorBoundary logName="OuterErrorBoundary">
2290 + <ErrorBoundary
2291 + logName="InnerUnmountBoundary"
2292 + renderError={renderUnmountError}>
2293 + <BrokenComponentWillUnmount errorText="E1" />
2294 + <BrokenComponentWillUnmount errorText="E2" />
2295 + </ErrorBoundary>
2296 + <ErrorBoundary
2297 + logName="InnerUpdateBoundary"
2298 + renderError={renderUpdateError}>
2299 + <BrokenComponentDidUpdate errorText="E3" />
2300 + <BrokenComponentDidUpdate errorText="E4" />
2301 + </ErrorBoundary>
2302 + </ErrorBoundary>,
2303 + );
2304 + });
2305
2306 Scheduler.unstable_clearLog();
1987 - ReactDOM.render(
1988 - <ErrorBoundary logName="OuterErrorBoundary">
1989 - <ErrorBoundary
1990 - logName="InnerUnmountBoundary"
1991 - renderError={renderUnmountError}
1992 - />
1993 - <ErrorBoundary
1994 - logName="InnerUpdateBoundary"
1995 - renderError={renderUpdateError}>
1996 - <BrokenComponentDidUpdate errorText="E3" />
1997 - <BrokenComponentDidUpdate errorText="E4" />
1998 - </ErrorBoundary>
1999 - </ErrorBoundary>,
2000 - container,
2001 - );
2307 + await act(async () => {
2308 + root.render(
2309 + <ErrorBoundary logName="OuterErrorBoundary">
2310 + <ErrorBoundary
2311 + logName="InnerUnmountBoundary"
2312 + renderError={renderUnmountError}
2313 + />
2314 + <ErrorBoundary
2315 + logName="InnerUpdateBoundary"
2316 + renderError={renderUpdateError}>
2317 + <BrokenComponentDidUpdate errorText="E3" />
2318 + <BrokenComponentDidUpdate errorText="E4" />
2319 + </ErrorBoundary>
2320 + </ErrorBoundary>,
2321 + );
2322 + });
2323
2324 expect(container.firstChild.textContent).toBe(
2325 'Caught an unmounting error: E2.' + 'Caught an updating error: E4.',
@@ -2048,7 +2369,7 @@ describe('ReactErrorBoundaries', () => {
2369 'InnerUpdateBoundary componentDidUpdate',
2370 ]);
2371
2051 - ReactDOM.unmountComponentAtNode(container);
2372 + root.unmount();
2373 assertLog([
2374 'OuterErrorBoundary componentWillUnmount',
2375 'InnerUnmountBoundary componentWillUnmount',
@@ -2056,7 +2377,7 @@ describe('ReactErrorBoundaries', () => {
2377 ]);
2378 });
2379
2059 - it('discards a bad root if the root component fails', () => {
2380 + it('discards a bad root if the root component fails', async () => {
2381 const X = null;
2382 const Y = undefined;
2383 let err1;
@@ -2064,7 +2385,13 @@ describe('ReactErrorBoundaries', () => {
2385
2386 try {
2387 const container = document.createElement('div');
2067 - expect(() => ReactDOM.render(<X />, container)).toErrorDev(
2388 + const root = ReactDOMClient.createRoot(container);
2389 + await expect(
2390 + async () =>
2391 + await act(async () => {
2392 + root.render(<X />, container);
2393 + }),
2394 + ).toErrorDev(
2395 'React.createElement: type is invalid -- expected a string ' +
2396 '(for built-in components) or a class/function ' +
2397 '(for composite components) but got: null.',
@@ -2074,7 +2401,13 @@ describe('ReactErrorBoundaries', () => {
2401 }
2402 try {
2403 const container = document.createElement('div');
2077 - expect(() => ReactDOM.render(<Y />, container)).toErrorDev(
2404 + const root = ReactDOMClient.createRoot(container);
2405 + await expect(
2406 + async () =>
2407 + await act(async () => {
2408 + root.render(<Y />, container);
2409 + }),
2410 + ).toErrorDev(
2411 'React.createElement: type is invalid -- expected a string ' +
2412 '(for built-in components) or a class/function ' +
2413 '(for composite components) but got: undefined.',
@@ -2087,19 +2420,23 @@ describe('ReactErrorBoundaries', () => {
2420 expect(err2.message).toMatch(/got: undefined/);
2421 });
2422
2090 - it('renders empty output if error boundary does not handle the error', () => {
2423 + it('renders empty output if error boundary does not handle the error', async () => {
2424 const container = document.createElement('div');
2092 - expect(() =>
2093 - ReactDOM.render(
2094 - <div>
2095 - Sibling
2096 - <NoopErrorBoundary>
2097 - <BrokenRender />
2098 - </NoopErrorBoundary>
2099 - </div>,
2100 - container,
2101 - ),
2102 - ).toThrow('Hello');
2425 + const root = ReactDOMClient.createRoot(container);
2426 +
2427 + await expect(async () => {
2428 + await act(async () => {
2429 + root.render(
2430 + <div>
2431 + Sibling
2432 + <NoopErrorBoundary>
2433 + <BrokenRender />
2434 + </NoopErrorBoundary>
2435 + </div>,
2436 + );
2437 + });
2438 + }).rejects.toThrow('Hello');
2439 +
2440 expect(container.innerHTML).toBe('');
2441 assertLog([
2442 'NoopErrorBoundary constructor',
@@ -2114,10 +2451,22 @@ describe('ReactErrorBoundaries', () => {
2451 'BrokenRender constructor',
2452 'BrokenRender componentWillMount',
2453 'BrokenRender render [!]',
2454 + // logs for error retry
2455 + 'NoopErrorBoundary constructor',
2456 + 'NoopErrorBoundary componentWillMount',
2457 + 'NoopErrorBoundary render',
2458 + 'BrokenRender constructor',
2459 + 'BrokenRender componentWillMount',
2460 + 'BrokenRender render [!]',
2461 + 'NoopErrorBoundary static getDerivedStateFromError',
2462 + 'NoopErrorBoundary render',
2463 + 'BrokenRender constructor',
2464 + 'BrokenRender componentWillMount',
2465 + 'BrokenRender render [!]',
2466 ]);
2467 });
2468
2120 - it('passes first error when two errors happen in commit', () => {
2469 + it('passes first error when two errors happen in commit', async () => {
2470 const errors = [];
2471 let caughtError;
2472 class Parent extends React.Component {
@@ -2140,10 +2489,13 @@ describe('ReactErrorBoundaries', () => {
2489 }
2490
2491 const container = document.createElement('div');
2492 + const root = ReactDOMClient.createRoot(container);
2493 try {
2494 // Here, we test the behavior where there is no error boundary and we
2495 // delegate to the host root.
2146 - ReactDOM.render(<Parent />, container);
2496 + await act(async () => {
2497 + root.render(<Parent />);
2498 + });
2499 } catch (e) {
2500 if (e.message !== 'parent sad' && e.message !== 'child sad') {
2501 throw e;
@@ -2156,19 +2508,22 @@ describe('ReactErrorBoundaries', () => {
2508 expect(caughtError.message).toBe('child sad');
2509 });
2510
2159 - it('propagates uncaught error inside unbatched initial mount', () => {
2511 + it('propagates uncaught error inside unbatched initial mount', async () => {
2512 function Foo() {
2513 throw new Error('foo error');
2514 }
2515 const container = document.createElement('div');
2164 - expect(() => {
2165 - ReactDOM.unstable_batchedUpdates(() => {
2166 - ReactDOM.render(<Foo />, container);
2516 + const root = ReactDOMClient.createRoot(container);
2517 + await expect(async () => {
2518 + await ReactDOM.unstable_batchedUpdates(async () => {
2519 + await act(async () => {
2520 + root.render(<Foo />);
2521 + });
2522 });
2168 - }).toThrow('foo error');
2523 + }).rejects.toThrow('foo error');
2524 });
2525
2171 - it('handles errors that occur in before-mutation commit hook', () => {
2526 + it('handles errors that occur in before-mutation commit hook', async () => {
2527 const errors = [];
2528 let caughtError;
2529 class Parent extends React.Component {
@@ -2193,9 +2548,14 @@ describe('ReactErrorBoundaries', () => {
2548 }
2549
2550 const container = document.createElement('div');
2196 - ReactDOM.render(<Parent value={1} />, container);
2551 + const root = ReactDOMClient.createRoot(container);
2552 + await act(async () => {
2553 + root.render(<Parent value={1} />);
2554 + });
2555 try {
2198 - ReactDOM.render(<Parent value={2} />, container);
2556 + await act(async () => {
2557 + root.render(<Parent value={2} />);
2558 + });
2559 } catch (e) {
2560 if (e.message !== 'parent sad' && e.message !== 'child sad') {
2561 throw e;
@@ -2208,7 +2568,7 @@ describe('ReactErrorBoundaries', () => {
2568 expect(caughtError.message).toBe('child sad');
2569 });
2570
2211 - it('should warn if an error boundary with only componentDidCatch does not update state', () => {
2571 + it('should warn if an error boundary with only componentDidCatch does not update state', async () => {
2572 class InvalidErrorBoundary extends React.Component {
2573 componentDidCatch(error, info) {
2574 // This component does not define getDerivedStateFromError().
@@ -2225,13 +2585,15 @@ describe('ReactErrorBoundaries', () => {
2585 };
2586
2587 const container = document.createElement('div');
2228 - expect(() => {
2229 - ReactDOM.render(
2230 - <InvalidErrorBoundary>
2231 - <Throws />
2232 - </InvalidErrorBoundary>,
2233 - container,
2234 - );
2588 + const root = ReactDOMClient.createRoot(container);
2589 + await expect(async () => {
2590 + await act(async () => {
2591 + root.render(
2592 + <InvalidErrorBoundary>
2593 + <Throws />
2594 + </InvalidErrorBoundary>,
2595 + );
2596 + });
2597 }).toErrorDev(
2598 'InvalidErrorBoundary: Error boundaries should implement getDerivedStateFromError(). ' +
2599 'In that method, return a state update to display an error message or fallback UI.',
@@ -2239,7 +2601,7 @@ describe('ReactErrorBoundaries', () => {
2601 expect(container.textContent).toBe('');
2602 });
2603
2242 - it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', () => {
2604 + it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', async () => {
2605 let componentDidCatchError, getDerivedStateFromErrorError;
2606 class ErrorBoundaryWithBothMethods extends React.Component {
2607 state = {error: null};
@@ -2261,34 +2623,39 @@ describe('ReactErrorBoundaries', () => {
2623 };
2624
2625 const container = document.createElement('div');
2264 - ReactDOM.render(
2265 - <ErrorBoundaryWithBothMethods>
2266 - <Throws />
2267 - </ErrorBoundaryWithBothMethods>,
2268 - container,
2269 - );
2626 + const root = ReactDOMClient.createRoot(container);
2627 + await act(async () => {
2628 + root.render(
2629 + <ErrorBoundaryWithBothMethods>
2630 + <Throws />
2631 + </ErrorBoundaryWithBothMethods>,
2632 + );
2633 + });
2634 expect(container.textContent).toBe('ErrorBoundary');
2635 expect(componentDidCatchError).toBe(thrownError);
2636 expect(getDerivedStateFromErrorError).toBe(thrownError);
2637 });
2638
2275 - it('should catch errors from invariants in completion phase', () => {
2639 + it('should catch errors from invariants in completion phase', async () => {
2640 const container = document.createElement('div');
2277 - ReactDOM.render(
2278 - <ErrorBoundary>
2279 - <input>
2280 - <div />
2281 - </input>
2282 - </ErrorBoundary>,
2283 - container,
2284 - );
2641 + const root = ReactDOMClient.createRoot(container);
2642 + await act(async () => {
2643 + root.render(
2644 + <ErrorBoundary>
2645 + <input>
2646 + <div />
2647 + </input>
2648 + </ErrorBoundary>,
2649 + );
2650 + });
2651 expect(container.textContent).toContain(
2652 'Caught an error: input is a void element tag',
2653 );
2654 });
2655
2290 - it('should catch errors from errors in the throw phase from boundaries', () => {
2656 + it('should catch errors from errors in the throw phase from boundaries', async () => {
2657 const container = document.createElement('div');
2658 + const root = ReactDOMClient.createRoot(container);
2659
2660 const thrownError = new Error('original error');
2661 const Throws = () => {
@@ -2304,22 +2671,24 @@ describe('ReactErrorBoundaries', () => {
2671 }
2672 }
2673
2307 - ReactDOM.render(
2308 - <ErrorBoundary>
2309 - <EvilErrorBoundary>
2310 - <Throws />
2311 - </EvilErrorBoundary>
2312 - </ErrorBoundary>,
2313 - container,
2314 - );
2674 + await act(async () => {
2675 + root.render(
2676 + <ErrorBoundary>
2677 + <EvilErrorBoundary>
2678 + <Throws />
2679 + </EvilErrorBoundary>
2680 + </ErrorBoundary>,
2681 + );
2682 + });
2683
2684 expect(container.textContent).toContain(
2685 'Caught an error: gotta catch em all',
2686 );
2687 });
2688
2321 - it('should protect errors from errors in the stack generation', () => {
2689 + it('should protect errors from errors in the stack generation', async () => {
2690 const container = document.createElement('div');
2691 + const root = ReactDOMClient.createRoot(container);
2692
2693 const evilError = {
2694 message: 'gotta catch em all',
@@ -2340,19 +2709,22 @@ describe('ReactErrorBoundaries', () => {
2709 return <Throws />;
2710 }
2711
2343 - ReactDOM.render(
2344 - <ErrorBoundary>
2345 - <Wrapper />
2346 - </ErrorBoundary>,
2347 - container,
2348 - );
2712 + await expect(async () => {
2713 + await act(async () => {
2714 + root.render(
2715 + <ErrorBoundary>
2716 + <Wrapper />
2717 + </ErrorBoundary>,
2718 + );
2719 + });
2720 + }).rejects.toThrow('gotta catch em all');
2721
2722 expect(container.textContent).toContain(
2723 'Caught an error: gotta catch em all.',
2724 );
2725 });
2726
2355 - it('catches errors thrown in componentWillUnmount', () => {
2727 + it('catches errors thrown in componentWillUnmount', async () => {
2728 class LocalErrorBoundary extends React.Component {
2729 state = {error: null};
2730 static getDerivedStateFromError(error) {
@@ -2392,16 +2764,18 @@ describe('ReactErrorBoundaries', () => {
2764 }
2765
2766 const container = document.createElement('div');
2395 -
2396 - ReactDOM.render(
2397 - <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2398 - <Component id="sibling" />
2399 - <LocalErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
2400 - <LocalBrokenComponentWillUnmount />
2401 - </LocalErrorBoundary>
2402 - </LocalErrorBoundary>,
2403 - container,
2404 - );
2767 + const root = ReactDOMClient.createRoot(container);
2768 +
2769 + await act(async () => {
2770 + root.render(
2771 + <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2772 + <Component id="sibling" />
2773 + <LocalErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
2774 + <LocalBrokenComponentWillUnmount />
2775 + </LocalErrorBoundary>
2776 + </LocalErrorBoundary>,
2777 + );
2778 + });
2779
2780 expect(container.firstChild.textContent).toBe('sibling');
2781 expect(container.lastChild.textContent).toBe('broken');
@@ -2412,12 +2786,13 @@ describe('ReactErrorBoundaries', () => {
2786 'BrokenComponentWillUnmount render',
2787 ]);
2788
2415 - ReactDOM.render(
2416 - <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2417 - <Component id="sibling" />
2418 - </LocalErrorBoundary>,
2419 - container,
2420 - );
2789 + await act(async () => {
2790 + root.render(
2791 + <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2792 + <Component id="sibling" />
2793 + </LocalErrorBoundary>,
2794 + );
2795 + });
2796
2797 // React should skip over the unmounting boundary and find the nearest still-mounted boundary.
2798 expect(container.firstChild.textContent).toBe('OuterFallback');
@@ -2432,7 +2807,7 @@ describe('ReactErrorBoundaries', () => {
2807 ]);
2808 });
2809
2435 - it('catches errors thrown while detaching refs', () => {
2810 + it('catches errors thrown while detaching refs', async () => {
2811 class LocalErrorBoundary extends React.Component {
2812 state = {error: null};
2813 static getDerivedStateFromError(error) {
@@ -2474,16 +2849,18 @@ describe('ReactErrorBoundaries', () => {
2849 }
2850
2851 const container = document.createElement('div');
2477 -
2478 - ReactDOM.render(
2479 - <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2480 - <Component id="sibling" />
2481 - <LocalErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
2482 - <LocalBrokenCallbackRef />
2483 - </LocalErrorBoundary>
2484 - </LocalErrorBoundary>,
2485 - container,
2486 - );
2852 + const root = ReactDOMClient.createRoot(container);
2853 +
2854 + await act(async () => {
2855 + root.render(
2856 + <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2857 + <Component id="sibling" />
2858 + <LocalErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
2859 + <LocalBrokenCallbackRef />
2860 + </LocalErrorBoundary>
2861 + </LocalErrorBoundary>,
2862 + );
2863 + });
2864
2865 expect(container.firstChild.textContent).toBe('sibling');
2866 expect(container.lastChild.textContent).toBe('ref');
@@ -2495,12 +2872,13 @@ describe('ReactErrorBoundaries', () => {
2872 'LocalBrokenCallbackRef ref true',
2873 ]);
2874
2498 - ReactDOM.render(
2499 - <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2500 - <Component id="sibling" />
2501 - </LocalErrorBoundary>,
2502 - container,
2503 - );
2875 + await act(async () => {
2876 + root.render(
2877 + <LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
2878 + <Component id="sibling" />
2879 + </LocalErrorBoundary>,
2880 + );
2881 + });
2882
2883 // React should skip over the unmounting boundary and find the nearest still-mounted boundary.
2884 expect(container.firstChild.textContent).toBe('OuterFallback');