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');