@samitouri / QOS-React / commits / e02f87f139

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