Use RTR with concurrent root in ReactHooks-test.internal (#28578)
Continued cleanup of legacy root usage from RTR
Jack Pope committed
Mar 18, 2024 at 15:49 UTC
e02f87f139fae4b17234c0cc592ff4e0825c18db
1 file changed
+459
-254
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+459
-254
@@ -25,7 +25,6 @@ let waitForThrow;
25
describe('ReactHooks', () => {
26
beforeEach(() => {
27
jest.resetModules();
28
-
28
ReactFeatureFlags = require('shared/ReactFeatureFlags');
29
30
React = require('react');
@@ -42,16 +41,20 @@ describe('ReactHooks', () => {
41
42
if (__DEV__) {
43
// useDebugValue is a DEV-only hook
45
- it('useDebugValue throws when used in a class component', () => {
44
+ it('useDebugValue throws when used in a class component', async () => {
45
class Example extends React.Component {
46
render() {
47
React.useDebugValue('abc');
48
return null;
49
}
50
}
52
- expect(() => {
53
- ReactTestRenderer.create(<Example />);
54
- }).toThrow(
51
+ await expect(async () => {
52
+ await act(() => {
53
+ ReactTestRenderer.create(<Example />, {
54
+ unstable_isConcurrent: true,
55
+ });
56
+ });
57
+ }).rejects.toThrow(
58
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen' +
59
' for one of the following reasons:\n' +
60
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
@@ -569,7 +572,7 @@ describe('ReactHooks', () => {
572
expect(root).toMatchRenderedOutput('105');
573
});
574
572
- it('warns about variable number of dependencies', () => {
575
+ it('warns about variable number of dependencies', async () => {
576
const {useLayoutEffect} = React;
577
function App(props) {
578
useLayoutEffect(() => {
@@ -577,10 +580,17 @@ describe('ReactHooks', () => {
580
}, props.dependencies);
581
return props.dependencies;
582
}
580
- const root = ReactTestRenderer.create(<App dependencies={['A']} />);
583
+ let root;
584
+ await act(() => {
585
+ root = ReactTestRenderer.create(<App dependencies={['A']} />, {
586
+ unstable_isConcurrent: true,
587
+ });
588
+ });
589
assertLog(['Did commit: A']);
582
- expect(() => {
583
- root.update(<App dependencies={['A', 'B']} />);
590
+ await expect(async () => {
591
+ await act(() => {
592
+ root.update(<App dependencies={['A', 'B']} />);
593
+ });
594
}).toErrorDev([
595
'Warning: The final argument passed to useLayoutEffect changed size ' +
596
'between renders. The order and size of this array must remain ' +
@@ -590,7 +600,7 @@ describe('ReactHooks', () => {
600
]);
601
});
602
593
- it('warns if switching from dependencies to no dependencies', () => {
603
+ it('warns if switching from dependencies to no dependencies', async () => {
604
const {useMemo} = React;
605
function App({text, hasDeps}) {
606
const resolvedText = useMemo(
@@ -603,13 +613,20 @@ describe('ReactHooks', () => {
613
return resolvedText;
614
}
615
606
- const root = ReactTestRenderer.create(null);
607
- root.update(<App text="Hello" hasDeps={true} />);
616
+ let root;
617
+ await act(() => {
618
+ root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
619
+ });
620
+ await act(() => {
621
+ root.update(<App text="Hello" hasDeps={true} />);
622
+ });
623
assertLog(['Compute']);
624
expect(root).toMatchRenderedOutput('HELLO');
625
611
- expect(() => {
612
- root.update(<App text="Hello" hasDeps={false} />);
626
+ await expect(async () => {
627
+ await act(() => {
628
+ root.update(<App text="Hello" hasDeps={false} />);
629
+ });
630
}).toErrorDev([
631
'Warning: useMemo received a final argument during this render, but ' +
632
'not during the previous render. Even though the final argument is ' +
@@ -630,7 +647,9 @@ describe('ReactHooks', () => {
647
648
await expect(async () => {
649
await act(() => {
633
- ReactTestRenderer.create(<App deps={'hello'} />);
650
+ ReactTestRenderer.create(<App deps={'hello'} />, {
651
+ unstable_isConcurrent: true,
652
+ });
653
});
654
}).toErrorDev([
655
'Warning: useEffect received a final argument that is not an array (instead, received `string`). ' +
@@ -644,7 +663,9 @@ describe('ReactHooks', () => {
663
]);
664
await expect(async () => {
665
await act(() => {
647
- ReactTestRenderer.create(<App deps={100500} />);
666
+ ReactTestRenderer.create(<App deps={100500} />, {
667
+ unstable_isConcurrent: true,
668
+ });
669
});
670
}).toErrorDev([
671
'Warning: useEffect received a final argument that is not an array (instead, received `number`). ' +
@@ -658,7 +679,9 @@ describe('ReactHooks', () => {
679
]);
680
await expect(async () => {
681
await act(() => {
661
- ReactTestRenderer.create(<App deps={{}} />);
682
+ ReactTestRenderer.create(<App deps={{}} />, {
683
+ unstable_isConcurrent: true,
684
+ });
685
});
686
}).toErrorDev([
687
'Warning: useEffect received a final argument that is not an array (instead, received `object`). ' +
@@ -672,13 +695,19 @@ describe('ReactHooks', () => {
695
]);
696
697
await act(() => {
675
- ReactTestRenderer.create(<App deps={[]} />);
676
- ReactTestRenderer.create(<App deps={null} />);
677
- ReactTestRenderer.create(<App deps={undefined} />);
698
+ ReactTestRenderer.create(<App deps={[]} />, {
699
+ unstable_isConcurrent: true,
700
+ });
701
+ ReactTestRenderer.create(<App deps={null} />, {
702
+ unstable_isConcurrent: true,
703
+ });
704
+ ReactTestRenderer.create(<App deps={undefined} />, {
705
+ unstable_isConcurrent: true,
706
+ });
707
});
708
});
709
681
- it('warns if deps is not an array for useImperativeHandle', () => {
710
+ it('warns if deps is not an array for useImperativeHandle', async () => {
711
const {useImperativeHandle} = React;
712
713
const App = React.forwardRef((props, ref) => {
@@ -686,15 +715,31 @@ describe('ReactHooks', () => {
715
return null;
716
});
717
689
- expect(() => {
690
- ReactTestRenderer.create(<App deps={'hello'} />);
718
+ await expect(async () => {
719
+ await act(() => {
720
+ ReactTestRenderer.create(<App deps={'hello'} />, {
721
+ unstable_isConcurrent: true,
722
+ });
723
+ });
724
}).toErrorDev([
725
'Warning: useImperativeHandle received a final argument that is not an array (instead, received `string`). ' +
726
'When specified, the final argument must be an array.',
727
]);
695
- ReactTestRenderer.create(<App deps={[]} />);
696
- ReactTestRenderer.create(<App deps={null} />);
697
- ReactTestRenderer.create(<App deps={undefined} />);
728
+ await act(() => {
729
+ ReactTestRenderer.create(<App deps={null} />, {
730
+ unstable_isConcurrent: true,
731
+ });
732
+ });
733
+ await act(() => {
734
+ ReactTestRenderer.create(<App deps={[]} />, {
735
+ unstable_isConcurrent: true,
736
+ });
737
+ });
738
+ await act(() => {
739
+ ReactTestRenderer.create(<App deps={undefined} />, {
740
+ unstable_isConcurrent: true,
741
+ });
742
+ });
743
});
744
745
it('does not forget render phase useState updates inside an effect', async () => {
@@ -713,7 +758,7 @@ describe('ReactHooks', () => {
758
return counter;
759
}
760
716
- const root = ReactTestRenderer.create(null);
761
+ const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
762
await act(() => {
763
root.update(<Counter />);
764
});
@@ -737,7 +782,7 @@ describe('ReactHooks', () => {
782
return counter;
783
}
784
740
- const root = ReactTestRenderer.create(null);
785
+ const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
786
await act(() => {
787
root.update(<Counter />);
788
});
@@ -760,14 +805,14 @@ describe('ReactHooks', () => {
805
return counter;
806
}
807
763
- const root = ReactTestRenderer.create(null);
808
+ const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
809
await act(() => {
810
root.update(<Counter />);
811
});
812
expect(root).toMatchRenderedOutput('4');
813
});
814
770
- it('warns for bad useImperativeHandle first arg', () => {
815
+ it('warns for bad useImperativeHandle first arg', async () => {
816
const {useImperativeHandle} = React;
817
function App() {
818
useImperativeHandle({
@@ -776,10 +821,12 @@ describe('ReactHooks', () => {
821
return null;
822
}
823
779
- expect(() => {
780
- expect(() => {
781
- ReactTestRenderer.create(<App />);
782
- }).toThrow('create is not a function');
824
+ await expect(async () => {
825
+ await expect(async () => {
826
+ await act(() => {
827
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
828
+ });
829
+ }).rejects.toThrow('create is not a function');
830
}).toErrorDev([
831
'Expected useImperativeHandle() first argument to either be a ' +
832
'ref callback or React.createRef() object. ' +
@@ -789,7 +836,7 @@ describe('ReactHooks', () => {
836
]);
837
});
838
792
- it('warns for bad useImperativeHandle second arg', () => {
839
+ it('warns for bad useImperativeHandle second arg', async () => {
840
const {useImperativeHandle} = React;
841
const App = React.forwardRef((props, ref) => {
842
useImperativeHandle(ref, {
@@ -798,8 +845,10 @@ describe('ReactHooks', () => {
845
return null;
846
});
847
801
- expect(() => {
802
- ReactTestRenderer.create(<App />);
848
+ await expect(async () => {
849
+ await act(() => {
850
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
851
+ });
852
}).toErrorDev([
853
'Expected useImperativeHandle() second argument to be a function ' +
854
'that creates a handle. Instead received: object.',
@@ -807,7 +856,7 @@ describe('ReactHooks', () => {
856
});
857
858
// https://github.com/facebook/react/issues/14022
810
- it('works with ReactDOMServer calls inside a component', () => {
859
+ it('works with ReactDOMServer calls inside a component', async () => {
860
const {useState} = React;
861
function App(props) {
862
const markup1 = ReactDOMServer.renderToString(<p>hello</p>);
@@ -815,11 +864,14 @@ describe('ReactHooks', () => {
864
const [counter] = useState(0);
865
return markup1 + counter + markup2;
866
}
818
- const root = ReactTestRenderer.create(<App />);
867
+ let root;
868
+ await act(() => {
869
+ root = ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
870
+ });
871
expect(root.toJSON()).toMatchSnapshot();
872
});
873
822
- it("throws when calling hooks inside .memo's compare function", () => {
874
+ it("throws when calling hooks inside .memo's compare function", async () => {
875
const {useState} = React;
876
function App() {
877
useState(0);
@@ -830,9 +882,18 @@ describe('ReactHooks', () => {
882
return false;
883
});
884
833
- const root = ReactTestRenderer.create(<MemoApp />);
885
+ let root;
886
+ await act(() => {
887
+ root = ReactTestRenderer.create(<MemoApp />, {
888
+ unstable_isConcurrent: true,
889
+ });
890
+ });
891
// trying to render again should trigger comparison and throw
835
- expect(() => root.update(<MemoApp />)).toThrow(
892
+ await expect(
893
+ act(() => {
894
+ root.update(<MemoApp />);
895
+ }),
896
+ ).rejects.toThrow(
897
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
898
' one of the following reasons:\n' +
899
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
@@ -841,7 +902,11 @@ describe('ReactHooks', () => {
902
'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
903
);
904
// the next round, it does a fresh mount, so should render
844
- expect(() => root.update(<MemoApp />)).not.toThrow(
905
+ await expect(
906
+ act(() => {
907
+ root.update(<MemoApp />);
908
+ }),
909
+ ).resolves.not.toThrow(
910
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
911
' one of the following reasons:\n' +
912
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
@@ -850,7 +915,11 @@ describe('ReactHooks', () => {
915
'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
916
);
917
// and then again, fail
853
- expect(() => root.update(<MemoApp />)).toThrow(
918
+ await expect(
919
+ act(() => {
920
+ root.update(<MemoApp />);
921
+ }),
922
+ ).rejects.toThrow(
923
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
924
' one of the following reasons:\n' +
925
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
@@ -860,7 +929,7 @@ describe('ReactHooks', () => {
929
);
930
});
931
863
- it('warns when calling hooks inside useMemo', () => {
932
+ it('warns when calling hooks inside useMemo', async () => {
933
const {useMemo, useState} = React;
934
function App() {
935
useMemo(() => {
@@ -868,12 +937,16 @@ describe('ReactHooks', () => {
937
});
938
return null;
939
}
871
- expect(() => ReactTestRenderer.create(<App />)).toErrorDev(
940
+ await expect(async () => {
941
+ await act(() => {
942
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
943
+ });
944
+ }).toErrorDev(
945
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
946
);
947
});
948
876
- it('warns when reading context inside useMemo', () => {
949
+ it('warns when reading context inside useMemo', async () => {
950
const {useMemo, createContext} = React;
951
const ReactCurrentDispatcher =
952
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -886,12 +959,14 @@ describe('ReactHooks', () => {
959
}, []);
960
}
961
889
- expect(() => ReactTestRenderer.create(<App />)).toErrorDev(
890
- 'Context can only be read while React is rendering',
891
- );
962
+ await expect(async () => {
963
+ await act(() => {
964
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
965
+ });
966
+ }).toErrorDev('Context can only be read while React is rendering');
967
});
968
894
- it('warns when reading context inside useMemo after reading outside it', () => {
969
+ it('warns when reading context inside useMemo after reading outside it', async () => {
970
const {useMemo, createContext} = React;
971
const ReactCurrentDispatcher =
972
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -908,9 +983,11 @@ describe('ReactHooks', () => {
983
}, []);
984
}
985
911
- expect(() => ReactTestRenderer.create(<App />)).toErrorDev(
912
- 'Context can only be read while React is rendering',
913
- );
986
+ await expect(async () => {
987
+ await act(() => {
988
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
989
+ });
990
+ }).toErrorDev('Context can only be read while React is rendering');
991
expect(firstRead).toBe('light');
992
expect(secondRead).toBe('light');
993
});
@@ -931,14 +1008,14 @@ describe('ReactHooks', () => {
1008
}
1009
1010
await act(async () => {
934
- ReactTestRenderer.create(<App />);
1011
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1012
// The exact message doesn't matter, just make sure we don't allow this
1013
await waitForThrow('Context can only be read while React is rendering');
1014
});
1015
});
1016
1017
// Throws because there's no runtime cost for being strict here.
941
- it('throws when reading context inside useLayoutEffect', () => {
1018
+ it('throws when reading context inside useLayoutEffect', async () => {
1019
const {useLayoutEffect, createContext} = React;
1020
const ReactCurrentDispatcher =
1021
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -952,13 +1029,17 @@ describe('ReactHooks', () => {
1029
return null;
1030
}
1031
955
- expect(() => ReactTestRenderer.create(<App />)).toThrow(
1032
+ await expect(
1033
+ act(() => {
1034
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1035
+ }),
1036
+ ).rejects.toThrow(
1037
// The exact message doesn't matter, just make sure we don't allow this
1038
'Context can only be read while React is rendering',
1039
);
1040
});
1041
961
- it('warns when reading context inside useReducer', () => {
1042
+ it('warns when reading context inside useReducer', async () => {
1043
const {useReducer, createContext} = React;
1044
const ReactCurrentDispatcher =
1045
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -976,13 +1057,15 @@ describe('ReactHooks', () => {
1057
return null;
1058
}
1059
979
- expect(() => ReactTestRenderer.create(<App />)).toErrorDev([
980
- 'Context can only be read while React is rendering',
981
- ]);
1060
+ await expect(async () => {
1061
+ await act(() => {
1062
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1063
+ });
1064
+ }).toErrorDev(['Context can only be read while React is rendering']);
1065
});
1066
1067
// Edge case.
985
- it('warns when reading context inside eager useReducer', () => {
1068
+ it('warns when reading context inside eager useReducer', async () => {
1069
const {useState, createContext} = React;
1070
const ThemeContext = createContext('light');
1071
@@ -1007,20 +1090,23 @@ describe('ReactHooks', () => {
1090
}
1091
}
1092
1010
- expect(() =>
1011
- ReactTestRenderer.create(
1012
- <>
1013
- <Fn />
1014
- <Cls />
1015
- </>,
1016
- ),
1017
- ).toErrorDev([
1093
+ await expect(async () => {
1094
+ await act(() => {
1095
+ ReactTestRenderer.create(
1096
+ <>
1097
+ <Fn />
1098
+ <Cls />
1099
+ </>,
1100
+ {unstable_isConcurrent: true},
1101
+ );
1102
+ });
1103
+ }).toErrorDev([
1104
'Context can only be read while React is rendering',
1105
'Cannot update a component (`Fn`) while rendering a different component (`Cls`).',
1106
]);
1107
});
1108
1023
- it('warns when calling hooks inside useReducer', () => {
1109
+ it('warns when calling hooks inside useReducer', async () => {
1110
const {useReducer, useState, useRef} = React;
1111
1112
function App() {
@@ -1035,10 +1121,12 @@ describe('ReactHooks', () => {
1121
return value;
1122
}
1123
1038
- expect(() => {
1039
- expect(() => {
1040
- ReactTestRenderer.create(<App />);
1041
- }).toThrow(
1124
+ await expect(async () => {
1125
+ await expect(async () => {
1126
+ await act(() => {
1127
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1128
+ });
1129
+ }).rejects.toThrow(
1130
'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
1131
);
1132
}).toErrorDev([
@@ -1051,10 +1139,11 @@ describe('ReactHooks', () => {
1139
'1. useReducer useReducer\n' +
1140
'2. useState useRef\n' +
1141
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
1142
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1143
]);
1144
});
1145
1057
- it("warns when calling hooks inside useState's initialize function", () => {
1146
+ it("warns when calling hooks inside useState's initialize function", async () => {
1147
const {useState, useRef} = React;
1148
function App() {
1149
useState(() => {
@@ -1063,7 +1152,11 @@ describe('ReactHooks', () => {
1152
});
1153
return null;
1154
}
1066
- expect(() => ReactTestRenderer.create(<App />)).toErrorDev(
1155
+ await expect(async () => {
1156
+ await act(() => {
1157
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1158
+ });
1159
+ }).toErrorDev(
1160
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
1161
);
1162
});
@@ -1097,15 +1190,21 @@ describe('ReactHooks', () => {
1190
}
1191
}
1192
1100
- expect(() => {
1101
- ReactTestRenderer.create(
1102
- <Boundary>
1103
- <App />
1104
- </Boundary>,
1105
- );
1193
+ await expect(async () => {
1194
+ await act(() => {
1195
+ ReactTestRenderer.create(
1196
+ <Boundary>
1197
+ <App />
1198
+ </Boundary>,
1199
+ {unstable_isConcurrent: true},
1200
+ );
1201
+ });
1202
}).toErrorDev([
1203
'Context can only be read while React is rendering',
1204
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1205
+
1206
+ 'Context can only be read while React is rendering',
1207
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1208
]);
1209
1210
function Valid() {
@@ -1128,23 +1227,29 @@ describe('ReactHooks', () => {
1227
// Verify it doesn't think we're still inside a Hook.
1228
// Should have no warnings.
1229
await act(() => {
1131
- ReactTestRenderer.create(<Valid />);
1230
+ ReactTestRenderer.create(<Valid />, {unstable_isConcurrent: true});
1231
});
1232
1233
// Verify warnings don't get permanently disabled.
1135
- expect(() => {
1136
- ReactTestRenderer.create(
1137
- <Boundary>
1138
- <App />
1139
- </Boundary>,
1140
- );
1234
+ await expect(async () => {
1235
+ await act(() => {
1236
+ ReactTestRenderer.create(
1237
+ <Boundary>
1238
+ <App />
1239
+ </Boundary>,
1240
+ {unstable_isConcurrent: true},
1241
+ );
1242
+ });
1243
}).toErrorDev([
1244
'Context can only be read while React is rendering',
1245
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1246
+
1247
+ 'Context can only be read while React is rendering',
1248
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1249
]);
1250
});
1251
1147
- it('warns when reading context inside useMemo', () => {
1252
+ it('warns when reading context inside useMemo', async () => {
1253
const {useMemo, createContext} = React;
1254
const ReactCurrentDispatcher =
1255
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -1157,12 +1262,14 @@ describe('ReactHooks', () => {
1262
}, []);
1263
}
1264
1160
- expect(() => ReactTestRenderer.create(<App />)).toErrorDev(
1161
- 'Context can only be read while React is rendering',
1162
- );
1265
+ await expect(async () => {
1266
+ await act(() => {
1267
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1268
+ });
1269
+ }).toErrorDev('Context can only be read while React is rendering');
1270
});
1271
1165
- it('double-invokes components with Hooks in Strict Mode', () => {
1272
+ it('double-invokes components with Hooks in Strict Mode', async () => {
1273
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
1274
1275
const {useState, StrictMode} = React;
@@ -1211,74 +1318,105 @@ describe('ReactHooks', () => {
1318
};
1319
}
1320
1214
- const renderer = ReactTestRenderer.create(null);
1321
+ let renderer;
1322
+ await act(() => {
1323
+ renderer = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
1324
+ });
1325
1326
renderCount = 0;
1217
- renderer.update(<NoHooks />);
1327
+ await act(() => {
1328
+ renderer.update(<NoHooks />);
1329
+ });
1330
expect(renderCount).toBe(1);
1331
renderCount = 0;
1220
- renderer.update(<NoHooks />);
1332
+ await act(() => {
1333
+ renderer.update(<NoHooks />);
1334
+ });
1335
expect(renderCount).toBe(1);
1336
renderCount = 0;
1223
- renderer.update(
1224
- <StrictMode>
1225
- <NoHooks />
1226
- </StrictMode>,
1227
- );
1337
+ await act(() => {
1338
+ renderer.update(
1339
+ <StrictMode>
1340
+ <NoHooks />
1341
+ </StrictMode>,
1342
+ );
1343
+ });
1344
expect(renderCount).toBe(__DEV__ ? 2 : 1);
1345
renderCount = 0;
1230
- renderer.update(
1231
- <StrictMode>
1232
- <NoHooks />
1233
- </StrictMode>,
1234
- );
1346
+ await act(() => {
1347
+ renderer.update(
1348
+ <StrictMode>
1349
+ <NoHooks />
1350
+ </StrictMode>,
1351
+ );
1352
+ });
1353
expect(renderCount).toBe(__DEV__ ? 2 : 1);
1354
1355
renderCount = 0;
1238
- renderer.update(<FwdRef />);
1356
+ await act(() => {
1357
+ renderer.update(<FwdRef />);
1358
+ });
1359
expect(renderCount).toBe(1);
1360
renderCount = 0;
1241
- renderer.update(<FwdRef />);
1361
+ await act(() => {
1362
+ renderer.update(<FwdRef />);
1363
+ });
1364
expect(renderCount).toBe(1);
1365
renderCount = 0;
1244
- renderer.update(
1245
- <StrictMode>
1246
- <FwdRef />
1247
- </StrictMode>,
1248
- );
1366
+ await act(() => {
1367
+ renderer.update(
1368
+ <StrictMode>
1369
+ <FwdRef />
1370
+ </StrictMode>,
1371
+ );
1372
+ });
1373
expect(renderCount).toBe(__DEV__ ? 2 : 1);
1374
renderCount = 0;
1251
- renderer.update(
1252
- <StrictMode>
1253
- <FwdRef />
1254
- </StrictMode>,
1255
- );
1375
+ await act(() => {
1376
+ renderer.update(
1377
+ <StrictMode>
1378
+ <FwdRef />
1379
+ </StrictMode>,
1380
+ );
1381
+ });
1382
expect(renderCount).toBe(__DEV__ ? 2 : 1);
1383
1384
renderCount = 0;
1259
- renderer.update(<Memo arg={1} />);
1385
+ await act(() => {
1386
+ renderer.update(<Memo arg={1} />);
1387
+ });
1388
expect(renderCount).toBe(1);
1389
renderCount = 0;
1262
- renderer.update(<Memo arg={2} />);
1390
+ await act(() => {
1391
+ renderer.update(<Memo arg={2} />);
1392
+ });
1393
expect(renderCount).toBe(1);
1394
renderCount = 0;
1265
- renderer.update(
1266
- <StrictMode>
1267
- <Memo arg={1} />
1268
- </StrictMode>,
1269
- );
1395
+ await act(() => {
1396
+ renderer.update(
1397
+ <StrictMode>
1398
+ <Memo arg={1} />
1399
+ </StrictMode>,
1400
+ );
1401
+ });
1402
expect(renderCount).toBe(__DEV__ ? 2 : 1);
1403
renderCount = 0;
1272
- renderer.update(
1273
- <StrictMode>
1274
- <Memo arg={2} />
1275
- </StrictMode>,
1276
- );
1404
+ await act(() => {
1405
+ renderer.update(
1406
+ <StrictMode>
1407
+ <Memo arg={2} />
1408
+ </StrictMode>,
1409
+ );
1410
+ });
1411
expect(renderCount).toBe(__DEV__ ? 2 : 1);
1412
1413
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
1414
renderCount = 0;
1281
- expect(() => renderer.update(<Factory />)).toErrorDev(
1415
+ await expect(async () => {
1416
+ await act(() => {
1417
+ renderer.update(<Factory />);
1418
+ });
1419
+ }).toErrorDev(
1420
'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
1421
'Change Factory to a class that extends React.Component instead. ' +
1422
"If you can't use a class try assigning the prototype on the function as a workaround. " +
@@ -1287,90 +1425,120 @@ describe('ReactHooks', () => {
1425
);
1426
expect(renderCount).toBe(1);
1427
renderCount = 0;
1290
- renderer.update(<Factory />);
1428
+ await act(() => {
1429
+ renderer.update(<Factory />);
1430
+ });
1431
expect(renderCount).toBe(1);
1432
1433
renderCount = 0;
1294
- renderer.update(
1295
- <StrictMode>
1296
- <Factory />
1297
- </StrictMode>,
1298
- );
1434
+ await act(() => {
1435
+ renderer.update(
1436
+ <StrictMode>
1437
+ <Factory />
1438
+ </StrictMode>,
1439
+ );
1440
+ });
1441
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
1442
renderCount = 0;
1301
- renderer.update(
1302
- <StrictMode>
1303
- <Factory />
1304
- </StrictMode>,
1305
- );
1443
+ await act(() => {
1444
+ renderer.update(
1445
+ <StrictMode>
1446
+ <Factory />
1447
+ </StrictMode>,
1448
+ );
1449
+ });
1450
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
1451
}
1452
1453
renderCount = 0;
1310
- renderer.update(<HasHooks />);
1454
+ await act(() => {
1455
+ renderer.update(<HasHooks />);
1456
+ });
1457
expect(renderCount).toBe(1);
1458
renderCount = 0;
1313
- renderer.update(<HasHooks />);
1459
+ await act(() => {
1460
+ renderer.update(<HasHooks />);
1461
+ });
1462
expect(renderCount).toBe(1);
1463
renderCount = 0;
1316
- renderer.update(
1317
- <StrictMode>
1318
- <HasHooks />
1319
- </StrictMode>,
1320
- );
1464
+ await act(() => {
1465
+ renderer.update(
1466
+ <StrictMode>
1467
+ <HasHooks />
1468
+ </StrictMode>,
1469
+ );
1470
+ });
1471
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1472
renderCount = 0;
1323
- renderer.update(
1324
- <StrictMode>
1325
- <HasHooks />
1326
- </StrictMode>,
1327
- );
1473
+ await act(() => {
1474
+ renderer.update(
1475
+ <StrictMode>
1476
+ <HasHooks />
1477
+ </StrictMode>,
1478
+ );
1479
+ });
1480
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1481
1482
renderCount = 0;
1331
- renderer.update(<FwdRefHasHooks />);
1483
+ await act(() => {
1484
+ renderer.update(<FwdRefHasHooks />);
1485
+ });
1486
expect(renderCount).toBe(1);
1487
renderCount = 0;
1334
- renderer.update(<FwdRefHasHooks />);
1488
+ await act(() => {
1489
+ renderer.update(<FwdRefHasHooks />);
1490
+ });
1491
expect(renderCount).toBe(1);
1492
renderCount = 0;
1337
- renderer.update(
1338
- <StrictMode>
1339
- <FwdRefHasHooks />
1340
- </StrictMode>,
1341
- );
1493
+ await act(() => {
1494
+ renderer.update(
1495
+ <StrictMode>
1496
+ <FwdRefHasHooks />
1497
+ </StrictMode>,
1498
+ );
1499
+ });
1500
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1501
renderCount = 0;
1344
- renderer.update(
1345
- <StrictMode>
1346
- <FwdRefHasHooks />
1347
- </StrictMode>,
1348
- );
1502
+ await act(() => {
1503
+ renderer.update(
1504
+ <StrictMode>
1505
+ <FwdRefHasHooks />
1506
+ </StrictMode>,
1507
+ );
1508
+ });
1509
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1510
1511
renderCount = 0;
1352
- renderer.update(<MemoHasHooks arg={1} />);
1512
+ await act(() => {
1513
+ renderer.update(<MemoHasHooks arg={1} />);
1514
+ });
1515
expect(renderCount).toBe(1);
1516
renderCount = 0;
1355
- renderer.update(<MemoHasHooks arg={2} />);
1517
+ await act(() => {
1518
+ renderer.update(<MemoHasHooks arg={2} />);
1519
+ });
1520
expect(renderCount).toBe(1);
1521
renderCount = 0;
1358
- renderer.update(
1359
- <StrictMode>
1360
- <MemoHasHooks arg={1} />
1361
- </StrictMode>,
1362
- );
1522
+ await act(() => {
1523
+ renderer.update(
1524
+ <StrictMode>
1525
+ <MemoHasHooks arg={1} />
1526
+ </StrictMode>,
1527
+ );
1528
+ });
1529
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1530
renderCount = 0;
1365
- renderer.update(
1366
- <StrictMode>
1367
- <MemoHasHooks arg={2} />
1368
- </StrictMode>,
1369
- );
1531
+ await act(() => {
1532
+ renderer.update(
1533
+ <StrictMode>
1534
+ <MemoHasHooks arg={2} />
1535
+ </StrictMode>,
1536
+ );
1537
+ });
1538
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1539
});
1540
1373
- it('double-invokes useMemo in DEV StrictMode despite []', () => {
1541
+ it('double-invokes useMemo in DEV StrictMode despite []', async () => {
1542
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
1543
const {useMemo, StrictMode} = React;
1544
@@ -1383,11 +1551,14 @@ describe('ReactHooks', () => {
1551
}
1552
1553
useMemoCount = 0;
1386
- ReactTestRenderer.create(
1387
- <StrictMode>
1388
- <BadUseMemo />
1389
- </StrictMode>,
1390
- );
1554
+ await act(() => {
1555
+ ReactTestRenderer.create(
1556
+ <StrictMode>
1557
+ <BadUseMemo />
1558
+ </StrictMode>,
1559
+ {unstable_isConcurrent: true},
1560
+ );
1561
+ });
1562
expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1563
});
1564
@@ -1482,7 +1653,9 @@ describe('ReactHooks', () => {
1653
}
1654
let root;
1655
await act(() => {
1485
- root = ReactTestRenderer.create(<App update={false} />);
1656
+ root = ReactTestRenderer.create(<App update={false} />, {
1657
+ unstable_isConcurrent: true,
1658
+ });
1659
});
1660
await expect(async () => {
1661
try {
@@ -1531,7 +1704,9 @@ describe('ReactHooks', () => {
1704
}
1705
let root;
1706
await act(() => {
1534
- root = ReactTestRenderer.create(<App update={false} />);
1707
+ root = ReactTestRenderer.create(<App update={false} />, {
1708
+ unstable_isConcurrent: true,
1709
+ });
1710
});
1711
1712
await expect(async () => {
@@ -1585,21 +1760,23 @@ describe('ReactHooks', () => {
1760
}
1761
let root;
1762
await act(() => {
1588
- root = ReactTestRenderer.create(<App update={false} />);
1763
+ root = ReactTestRenderer.create(<App update={false} />, {
1764
+ unstable_isConcurrent: true,
1765
+ });
1766
});
1767
1591
- await act(() => {
1592
- expect(() => {
1768
+ await expect(async () => {
1769
+ await act(() => {
1770
root.update(<App update={true} />);
1594
- }).toThrow('Rendered fewer hooks than expected. ');
1595
- });
1771
+ });
1772
+ }).rejects.toThrow('Rendered fewer hooks than expected. ');
1773
});
1774
});
1775
1776
it(
1777
'warns on using differently ordered hooks ' +
1778
'(useImperativeHandleHelper, useMemoHelper) on subsequent renders',
1602
- () => {
1779
+ async () => {
1780
function App(props) {
1781
/* eslint-disable no-unused-vars */
1782
if (props.update) {
@@ -1614,15 +1791,19 @@ describe('ReactHooks', () => {
1791
return null;
1792
/* eslint-enable no-unused-vars */
1793
}
1617
- const root = ReactTestRenderer.create(<App update={false} />);
1618
- expect(() => {
1619
- try {
1794
+ let root;
1795
+ await act(() => {
1796
+ root = ReactTestRenderer.create(<App update={false} />, {
1797
+ unstable_isConcurrent: true,
1798
+ });
1799
+ });
1800
+ await expect(async () => {
1801
+ await act(() => {
1802
root.update(<App update={true} />);
1621
- } catch (error) {
1622
- // Swapping certain types of hooks will cause runtime errors.
1623
- // This is okay as far as this test is concerned.
1624
- // We just want to verify that warnings are always logged.
1625
- }
1803
+ }).catch(e => {});
1804
+ // Swapping certain types of hooks will cause runtime errors.
1805
+ // This is okay as far as this test is concerned.
1806
+ // We just want to verify that warnings are always logged.
1807
}).toErrorDev([
1808
'Warning: React has detected a change in the order of Hooks called by App. ' +
1809
'This will lead to bugs and errors if not fixed. For more information, ' +
@@ -1638,11 +1819,13 @@ describe('ReactHooks', () => {
1819
]);
1820
1821
// further warnings for this component are silenced
1641
- root.update(<App update={false} />);
1822
+ await act(() => {
1823
+ root.update(<App update={false} />);
1824
+ });
1825
},
1826
);
1827
1645
- it('detects a bad hook order even if the component throws', () => {
1828
+ it('detects a bad hook order even if the component throws', async () => {
1829
const {useState, useReducer} = React;
1830
function useCustomHook() {
1831
useState(0);
@@ -1660,11 +1843,18 @@ describe('ReactHooks', () => {
1843
return null;
1844
/* eslint-enable no-unused-vars */
1845
}
1663
- const root = ReactTestRenderer.create(<App update={false} />);
1664
- expect(() => {
1665
- expect(() => root.update(<App update={true} />)).toThrow(
1666
- 'custom error',
1667
- );
1846
+ let root;
1847
+ await act(() => {
1848
+ root = ReactTestRenderer.create(<App update={false} />, {
1849
+ unstable_isConcurrent: true,
1850
+ });
1851
+ });
1852
+ await expect(async () => {
1853
+ await expect(async () => {
1854
+ await act(() => {
1855
+ root.update(<App update={true} />);
1856
+ });
1857
+ }).rejects.toThrow('custom error');
1858
}).toErrorDev([
1859
'Warning: React has detected a change in the order of Hooks called by App. ' +
1860
'This will lead to bugs and errors if not fixed. For more information, ' +
@@ -1696,14 +1886,17 @@ describe('ReactHooks', () => {
1886
return null;
1887
}
1888
1699
- expect(() => {
1700
- ReactTestRenderer.create(
1701
- <>
1702
- <A />
1703
- <B />
1704
- </>,
1705
- );
1706
- }).toThrow('Hello');
1889
+ await expect(async () => {
1890
+ await act(() => {
1891
+ ReactTestRenderer.create(
1892
+ <>
1893
+ <A />
1894
+ <B />
1895
+ </>,
1896
+ {unstable_isConcurrent: true},
1897
+ );
1898
+ });
1899
+ }).rejects.toThrow('Hello');
1900
1901
if (__DEV__) {
1902
expect(console.error).toHaveBeenCalledTimes(2);
@@ -1752,18 +1945,17 @@ describe('ReactHooks', () => {
1945
}
1946
1947
await act(() => {
1755
- ReactTestRenderer.create(<A />);
1948
+ ReactTestRenderer.create(<A />, {unstable_isConcurrent: true});
1949
});
1950
1758
- expect(() => {
1759
- globalListener();
1760
- globalListener();
1761
- }).toErrorDev([
1762
- 'An update to C inside a test was not wrapped in act',
1763
- 'An update to C inside a test was not wrapped in act',
1764
- // Note: should *not* warn about updates on unmounted component.
1765
- // Because there's no way for component to know it got unmounted.
1766
- ]);
1951
+ // Note: should *not* warn about updates on unmounted component.
1952
+ // Because there's no way for component to know it got unmounted.
1953
+ await expect(
1954
+ act(() => {
1955
+ globalListener();
1956
+ globalListener();
1957
+ }),
1958
+ ).resolves.not.toThrow();
1959
});
1960
1961
// Regression test for https://github.com/facebook/react/issues/14790
@@ -1771,11 +1963,12 @@ describe('ReactHooks', () => {
1963
const {Suspense, useState} = React;
1964
1965
let wasSuspended = false;
1966
+ let resolve;
1967
function trySuspend() {
1968
if (!wasSuspended) {
1776
- throw new Promise(resolve => {
1969
+ throw new Promise(r => {
1970
wasSuspended = true;
1778
- resolve();
1971
+ resolve = r;
1972
});
1973
}
1974
}
@@ -1787,14 +1980,17 @@ describe('ReactHooks', () => {
1980
}
1981
1982
const Wrapper = React.memo(Child);
1790
- const root = ReactTestRenderer.create(
1791
- <Suspense fallback="loading">
1792
- <Wrapper />
1793
- </Suspense>,
1794
- );
1983
+ let root;
1984
+ await act(() => {
1985
+ root = ReactTestRenderer.create(
1986
+ <Suspense fallback="loading">
1987
+ <Wrapper />
1988
+ </Suspense>,
1989
+ {unstable_isConcurrent: true},
1990
+ );
1991
+ });
1992
expect(root).toMatchRenderedOutput('loading');
1796
- await Promise.resolve();
1797
- await waitForAll([]);
1993
+ await act(resolve);
1994
expect(root).toMatchRenderedOutput('hello');
1995
});
1996
@@ -1803,11 +1999,12 @@ describe('ReactHooks', () => {
1999
const {Suspense, useState} = React;
2000
2001
let wasSuspended = false;
2002
+ let resolve;
2003
function trySuspend() {
2004
if (!wasSuspended) {
1808
- throw new Promise(resolve => {
2005
+ throw new Promise(r => {
2006
wasSuspended = true;
1810
- resolve();
2007
+ resolve = r;
2008
});
2009
}
2010
}
@@ -1819,14 +2016,17 @@ describe('ReactHooks', () => {
2016
}
2017
2018
const Wrapper = React.forwardRef(render);
1822
- const root = ReactTestRenderer.create(
1823
- <Suspense fallback="loading">
1824
- <Wrapper />
1825
- </Suspense>,
1826
- );
2019
+ let root;
2020
+ await act(() => {
2021
+ root = ReactTestRenderer.create(
2022
+ <Suspense fallback="loading">
2023
+ <Wrapper />
2024
+ </Suspense>,
2025
+ {unstable_isConcurrent: true},
2026
+ );
2027
+ });
2028
expect(root).toMatchRenderedOutput('loading');
1828
- await Promise.resolve();
1829
- await waitForAll([]);
2029
+ await act(resolve);
2030
expect(root).toMatchRenderedOutput('hello');
2031
});
2032
@@ -1835,11 +2035,12 @@ describe('ReactHooks', () => {
2035
const {Suspense, useState} = React;
2036
2037
let wasSuspended = false;
2038
+ let resolve;
2039
function trySuspend() {
2040
if (!wasSuspended) {
1840
- throw new Promise(resolve => {
2041
+ throw new Promise(r => {
2042
wasSuspended = true;
1842
- resolve();
2043
+ resolve = r;
2044
});
2045
}
2046
}
@@ -1851,14 +2052,17 @@ describe('ReactHooks', () => {
2052
}
2053
2054
const Wrapper = React.memo(React.forwardRef(render));
1854
- const root = ReactTestRenderer.create(
1855
- <Suspense fallback="loading">
1856
- <Wrapper />
1857
- </Suspense>,
1858
- );
2055
+ let root;
2056
+ await act(() => {
2057
+ root = ReactTestRenderer.create(
2058
+ <Suspense fallback="loading">
2059
+ <Wrapper />
2060
+ </Suspense>,
2061
+ {unstable_isConcurrent: true},
2062
+ );
2063
+ });
2064
expect(root).toMatchRenderedOutput('loading');
1860
- await Promise.resolve();
1861
- await waitForAll([]);
2065
+ await act(resolve);
2066
expect(root).toMatchRenderedOutput('hello');
2067
});
2068
@@ -1906,6 +2110,7 @@ describe('ReactHooks', () => {
2110
<ErrorBoundary>
2111
<Thrower />
2112
</ErrorBoundary>,
2113
+ {unstable_isConcurrent: true},
2114
);
2115
});
2116