Convert ReactFresh to createRoot (#28170)
Sebastian Silbermann committed
Feb 4, 2024 at 11:09 UTC
93a5f40cbf05a6efb63a78748d238605dfe35e0c
1 file changed
+372
-326
packages/react-refresh/src/__tests__/ReactFresh-test.js
+372
-326
@@ -17,13 +17,13 @@ let ReactDOMClient;
17
let ReactFreshRuntime;
18
let Scheduler;
19
let act;
20
-let internalAct;
20
let createReactClass;
21
let waitFor;
22
let assertLog;
23
24
describe('ReactFresh', () => {
25
let container;
26
+ let root;
27
28
beforeEach(() => {
29
if (__DEV__) {
@@ -34,8 +34,7 @@ describe('ReactFresh', () => {
34
ReactDOM = require('react-dom');
35
ReactDOMClient = require('react-dom/client');
36
Scheduler = require('scheduler');
37
- act = React.act;
38
- internalAct = require('internal-test-utils').act;
37
+ act = require('internal-test-utils').act;
38
39
const InternalTestUtils = require('internal-test-utils');
40
waitFor = InternalTestUtils.waitFor;
@@ -47,6 +46,7 @@ describe('ReactFresh', () => {
46
new React.Component().updater,
47
);
48
container = document.createElement('div');
49
+ root = ReactDOMClient.createRoot(container);
50
document.body.appendChild(container);
51
}
52
});
@@ -63,10 +63,10 @@ describe('ReactFresh', () => {
63
return Component;
64
}
65
66
- function render(version, props) {
66
+ async function render(version, props) {
67
const Component = version();
68
- act(() => {
69
- ReactDOM.render(<Component {...props} />, container);
68
+ await act(() => {
69
+ root.render(<Component {...props} />);
70
});
71
return Component;
72
}
@@ -99,9 +99,9 @@ describe('ReactFresh', () => {
99
);
100
}
101
102
- it('can preserve state for compatible types', () => {
102
+ it('can preserve state for compatible types', async () => {
103
if (__DEV__) {
104
- const HelloV1 = render(() => {
104
+ const HelloV1 = await render(() => {
105
function Hello() {
106
const [val, setVal] = React.useState(0);
107
return (
@@ -118,7 +118,7 @@ describe('ReactFresh', () => {
118
const el = container.firstChild;
119
expect(el.textContent).toBe('0');
120
expect(el.style.color).toBe('blue');
121
- act(() => {
121
+ await act(() => {
122
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
123
});
124
expect(el.textContent).toBe('1');
@@ -143,7 +143,7 @@ describe('ReactFresh', () => {
143
expect(el.style.color).toBe('red');
144
145
// Bump the state again.
146
- act(() => {
146
+ await act(() => {
147
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
148
});
149
expect(container.firstChild).toBe(el);
@@ -153,15 +153,15 @@ describe('ReactFresh', () => {
153
// Perform top-down renders with both fresh and stale types.
154
// Neither should change the state or color.
155
// They should always resolve to the latest version.
156
- render(() => HelloV1);
157
- render(() => HelloV2);
158
- render(() => HelloV1);
156
+ await render(() => HelloV1);
157
+ await render(() => HelloV2);
158
+ await render(() => HelloV1);
159
expect(container.firstChild).toBe(el);
160
expect(el.textContent).toBe('2');
161
expect(el.style.color).toBe('red');
162
163
// Bump the state again.
164
- act(() => {
164
+ await act(() => {
165
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
166
});
167
expect(container.firstChild).toBe(el);
@@ -169,7 +169,7 @@ describe('ReactFresh', () => {
169
expect(el.style.color).toBe('red');
170
171
// Finally, a render with incompatible type should reset it.
172
- render(() => {
172
+ await render(() => {
173
function Hello() {
174
const [val, setVal] = React.useState(0);
175
return (
@@ -189,9 +189,9 @@ describe('ReactFresh', () => {
189
}
190
});
191
192
- it('can preserve state for forwardRef', () => {
192
+ it('can preserve state for forwardRef', async () => {
193
if (__DEV__) {
194
- const OuterV1 = render(() => {
194
+ const OuterV1 = await render(() => {
195
function Hello() {
196
const [val, setVal] = React.useState(0);
197
return (
@@ -211,7 +211,7 @@ describe('ReactFresh', () => {
211
const el = container.firstChild;
212
expect(el.textContent).toBe('0');
213
expect(el.style.color).toBe('blue');
214
- act(() => {
214
+ await act(() => {
215
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
216
});
217
expect(el.textContent).toBe('1');
@@ -239,7 +239,7 @@ describe('ReactFresh', () => {
239
expect(el.style.color).toBe('red');
240
241
// Bump the state again.
242
- act(() => {
242
+ await act(() => {
243
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
244
});
245
expect(container.firstChild).toBe(el);
@@ -249,15 +249,15 @@ describe('ReactFresh', () => {
249
// Perform top-down renders with both fresh and stale types.
250
// Neither should change the state or color.
251
// They should always resolve to the latest version.
252
- render(() => OuterV1);
253
- render(() => OuterV2);
254
- render(() => OuterV1);
252
+ await render(() => OuterV1);
253
+ await render(() => OuterV2);
254
+ await render(() => OuterV1);
255
expect(container.firstChild).toBe(el);
256
expect(el.textContent).toBe('2');
257
expect(el.style.color).toBe('red');
258
259
// Finally, a render with incompatible type should reset it.
260
- render(() => {
260
+ await render(() => {
261
function Hello() {
262
const [val, setVal] = React.useState(0);
263
return (
@@ -279,9 +279,9 @@ describe('ReactFresh', () => {
279
}
280
});
281
282
- it('should not consider two forwardRefs around the same type to be equivalent', () => {
282
+ it('should not consider two forwardRefs around the same type to be equivalent', async () => {
283
if (__DEV__) {
284
- const ParentV1 = render(
284
+ const ParentV1 = await render(
285
() => {
286
function Hello() {
287
const [val, setVal] = React.useState(0);
@@ -317,32 +317,32 @@ describe('ReactFresh', () => {
317
let el = container.firstChild;
318
expect(el.textContent).toBe('0');
319
expect(el.style.color).toBe('blue');
320
- act(() => {
320
+ await act(() => {
321
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
322
});
323
expect(el.textContent).toBe('1');
324
325
// Switching up the inner types should reset the state.
326
- render(() => ParentV1, {cond: false});
326
+ await render(() => ParentV1, {cond: false});
327
expect(el).not.toBe(container.firstChild);
328
el = container.firstChild;
329
expect(el.textContent).toBe('0');
330
expect(el.style.color).toBe('blue');
331
332
- act(() => {
332
+ await act(() => {
333
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
334
});
335
expect(el.textContent).toBe('1');
336
337
// Switch them up back again.
338
- render(() => ParentV1, {cond: true});
338
+ await render(() => ParentV1, {cond: true});
339
expect(el).not.toBe(container.firstChild);
340
el = container.firstChild;
341
expect(el.textContent).toBe('0');
342
expect(el.style.color).toBe('blue');
343
344
// Now bump up the state to prepare for patching.
345
- act(() => {
345
+ await act(() => {
346
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
347
});
348
expect(el.textContent).toBe('1');
@@ -383,14 +383,14 @@ describe('ReactFresh', () => {
383
expect(el.style.color).toBe('red');
384
385
// Switching up the condition should still reset the state.
386
- render(() => ParentV2, {cond: false});
386
+ await render(() => ParentV2, {cond: false});
387
expect(el).not.toBe(container.firstChild);
388
el = container.firstChild;
389
expect(el.textContent).toBe('0');
390
expect(el.style.color).toBe('red');
391
392
// Now bump up the state to prepare for top-level renders.
393
- act(() => {
393
+ await act(() => {
394
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
395
});
396
expect(el).toBe(container.firstChild);
@@ -398,18 +398,18 @@ describe('ReactFresh', () => {
398
expect(el.style.color).toBe('red');
399
400
// Finally, verify using top-level render with stale type keeps state.
401
- render(() => ParentV1);
402
- render(() => ParentV2);
403
- render(() => ParentV1);
401
+ await render(() => ParentV1);
402
+ await render(() => ParentV2);
403
+ await render(() => ParentV1);
404
expect(container.firstChild).toBe(el);
405
expect(el.textContent).toBe('1');
406
expect(el.style.color).toBe('red');
407
}
408
});
409
410
- it('can update forwardRef render function with its wrapper', () => {
410
+ it('can update forwardRef render function with its wrapper', async () => {
411
if (__DEV__) {
412
- render(() => {
412
+ await render(() => {
413
function Hello({color}) {
414
const [val, setVal] = React.useState(0);
415
return (
@@ -429,7 +429,7 @@ describe('ReactFresh', () => {
429
const el = container.firstChild;
430
expect(el.textContent).toBe('0');
431
expect(el.style.color).toBe('blue');
432
- act(() => {
432
+ await act(() => {
433
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
434
});
435
expect(el.textContent).toBe('1');
@@ -458,9 +458,9 @@ describe('ReactFresh', () => {
458
}
459
});
460
461
- it('can update forwardRef render function in isolation', () => {
461
+ it('can update forwardRef render function in isolation', async () => {
462
if (__DEV__) {
463
- render(() => {
463
+ await render(() => {
464
function Hello({color}) {
465
const [val, setVal] = React.useState(0);
466
return (
@@ -483,7 +483,7 @@ describe('ReactFresh', () => {
483
const el = container.firstChild;
484
expect(el.textContent).toBe('0');
485
expect(el.style.color).toBe('blue');
486
- act(() => {
486
+ await act(() => {
487
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
488
});
489
expect(el.textContent).toBe('1');
@@ -515,9 +515,9 @@ describe('ReactFresh', () => {
515
}
516
});
517
518
- it('can preserve state for simple memo', () => {
518
+ it('can preserve state for simple memo', async () => {
519
if (__DEV__) {
520
- const OuterV1 = render(() => {
520
+ const OuterV1 = await render(() => {
521
function Hello() {
522
const [val, setVal] = React.useState(0);
523
return (
@@ -537,7 +537,7 @@ describe('ReactFresh', () => {
537
const el = container.firstChild;
538
expect(el.textContent).toBe('0');
539
expect(el.style.color).toBe('blue');
540
- act(() => {
540
+ await act(() => {
541
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
542
});
543
expect(el.textContent).toBe('1');
@@ -565,7 +565,7 @@ describe('ReactFresh', () => {
565
expect(el.style.color).toBe('red');
566
567
// Bump the state again.
568
- act(() => {
568
+ await act(() => {
569
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
570
});
571
expect(container.firstChild).toBe(el);
@@ -575,15 +575,15 @@ describe('ReactFresh', () => {
575
// Perform top-down renders with both fresh and stale types.
576
// Neither should change the state or color.
577
// They should always resolve to the latest version.
578
- render(() => OuterV1);
579
- render(() => OuterV2);
580
- render(() => OuterV1);
578
+ await render(() => OuterV1);
579
+ await render(() => OuterV2);
580
+ await render(() => OuterV1);
581
expect(container.firstChild).toBe(el);
582
expect(el.textContent).toBe('2');
583
expect(el.style.color).toBe('red');
584
585
// Finally, a render with incompatible type should reset it.
586
- render(() => {
586
+ await render(() => {
587
function Hello() {
588
const [val, setVal] = React.useState(0);
589
return (
@@ -605,9 +605,9 @@ describe('ReactFresh', () => {
605
}
606
});
607
608
- it('can preserve state for memo with custom comparison', () => {
608
+ it('can preserve state for memo with custom comparison', async () => {
609
if (__DEV__) {
610
- const OuterV1 = render(() => {
610
+ const OuterV1 = await render(() => {
611
function Hello() {
612
const [val, setVal] = React.useState(0);
613
return (
@@ -626,7 +626,7 @@ describe('ReactFresh', () => {
626
const el = container.firstChild;
627
expect(el.textContent).toBe('0');
628
expect(el.style.color).toBe('blue');
629
- act(() => {
629
+ await act(() => {
630
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
631
});
632
expect(el.textContent).toBe('1');
@@ -653,7 +653,7 @@ describe('ReactFresh', () => {
653
expect(el.style.color).toBe('red');
654
655
// Bump the state again.
656
- act(() => {
656
+ await act(() => {
657
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
658
});
659
expect(container.firstChild).toBe(el);
@@ -663,15 +663,15 @@ describe('ReactFresh', () => {
663
// Perform top-down renders with both fresh and stale types.
664
// Neither should change the state or color.
665
// They should always resolve to the latest version.
666
- render(() => OuterV1);
667
- render(() => OuterV2);
668
- render(() => OuterV1);
666
+ await render(() => OuterV1);
667
+ await render(() => OuterV2);
668
+ await render(() => OuterV1);
669
expect(container.firstChild).toBe(el);
670
expect(el.textContent).toBe('2');
671
expect(el.style.color).toBe('red');
672
673
// Finally, a render with incompatible type should reset it.
674
- render(() => {
674
+ await render(() => {
675
function Hello() {
676
const [val, setVal] = React.useState(0);
677
return (
@@ -693,9 +693,9 @@ describe('ReactFresh', () => {
693
}
694
});
695
696
- it('can update simple memo function in isolation', () => {
696
+ it('can update simple memo function in isolation', async () => {
697
if (__DEV__) {
698
- render(() => {
698
+ await render(() => {
699
function Hello() {
700
const [val, setVal] = React.useState(0);
701
return (
@@ -713,7 +713,7 @@ describe('ReactFresh', () => {
713
const el = container.firstChild;
714
expect(el.textContent).toBe('0');
715
expect(el.style.color).toBe('blue');
716
- act(() => {
716
+ await act(() => {
717
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
718
});
719
expect(el.textContent).toBe('1');
@@ -740,9 +740,9 @@ describe('ReactFresh', () => {
740
}
741
});
742
743
- it('can preserve state for memo(forwardRef)', () => {
743
+ it('can preserve state for memo(forwardRef)', async () => {
744
if (__DEV__) {
745
- const OuterV1 = render(() => {
745
+ const OuterV1 = await render(() => {
746
function Hello() {
747
const [val, setVal] = React.useState(0);
748
return (
@@ -762,7 +762,7 @@ describe('ReactFresh', () => {
762
const el = container.firstChild;
763
expect(el.textContent).toBe('0');
764
expect(el.style.color).toBe('blue');
765
- act(() => {
765
+ await act(() => {
766
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
767
});
768
expect(el.textContent).toBe('1');
@@ -790,7 +790,7 @@ describe('ReactFresh', () => {
790
expect(el.style.color).toBe('red');
791
792
// Bump the state again.
793
- act(() => {
793
+ await act(() => {
794
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
795
});
796
expect(container.firstChild).toBe(el);
@@ -800,15 +800,15 @@ describe('ReactFresh', () => {
800
// Perform top-down renders with both fresh and stale types.
801
// Neither should change the state or color.
802
// They should always resolve to the latest version.
803
- render(() => OuterV1);
804
- render(() => OuterV2);
805
- render(() => OuterV1);
803
+ await render(() => OuterV1);
804
+ await render(() => OuterV2);
805
+ await render(() => OuterV1);
806
expect(container.firstChild).toBe(el);
807
expect(el.textContent).toBe('2');
808
expect(el.style.color).toBe('red');
809
810
// Finally, a render with incompatible type should reset it.
811
- render(() => {
811
+ await render(() => {
812
function Hello() {
813
const [val, setVal] = React.useState(0);
814
return (
@@ -832,7 +832,8 @@ describe('ReactFresh', () => {
832
833
it('can preserve state for lazy after resolution', async () => {
834
if (__DEV__) {
835
- const AppV1 = render(() => {
835
+ let resolve;
836
+ const AppV1 = await render(() => {
837
function Hello() {
838
const [val, setVal] = React.useState(0);
839
return (
@@ -845,8 +846,8 @@ describe('ReactFresh', () => {
846
847
const Outer = React.lazy(
848
() =>
848
- new Promise(resolve => {
849
- setTimeout(() => resolve({default: Hello}), 100);
849
+ new Promise(_resolve => {
850
+ resolve = () => _resolve({default: Hello});
851
}),
852
);
853
$RefreshReg$(Outer, 'Outer');
@@ -865,7 +866,7 @@ describe('ReactFresh', () => {
866
867
expect(container.textContent).toBe('Loading');
868
await act(() => {
868
- jest.runAllTimers();
869
+ resolve();
870
});
871
expect(container.textContent).toBe('0');
872
@@ -873,7 +874,7 @@ describe('ReactFresh', () => {
874
const el = container.firstChild;
875
expect(el.textContent).toBe('0');
876
expect(el.style.color).toBe('blue');
876
- act(() => {
877
+ await act(() => {
878
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
879
});
880
expect(el.textContent).toBe('1');
@@ -892,8 +893,8 @@ describe('ReactFresh', () => {
893
894
const Outer = React.lazy(
895
() =>
895
- new Promise(resolve => {
896
- setTimeout(() => resolve({default: Hello}), 100);
896
+ new Promise(_resolve => {
897
+ resolve = () => _resolve({default: Hello});
898
}),
899
);
900
$RefreshReg$(Outer, 'Outer');
@@ -916,7 +917,7 @@ describe('ReactFresh', () => {
917
expect(el.style.color).toBe('red');
918
919
// Bump the state again.
919
- act(() => {
920
+ await act(() => {
921
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
922
});
923
expect(container.firstChild).toBe(el);
@@ -926,15 +927,15 @@ describe('ReactFresh', () => {
927
// Perform top-down renders with both fresh and stale types.
928
// Neither should change the state or color.
929
// They should always resolve to the latest version.
929
- render(() => AppV1);
930
- render(() => AppV2);
931
- render(() => AppV1);
930
+ await render(() => AppV1);
931
+ await render(() => AppV2);
932
+ await render(() => AppV1);
933
expect(container.firstChild).toBe(el);
934
expect(el.textContent).toBe('2');
935
expect(el.style.color).toBe('red');
936
937
// Finally, a render with incompatible type should reset it.
937
- render(() => {
938
+ await render(() => {
939
function Hello() {
940
const [val, setVal] = React.useState(0);
941
return (
@@ -968,7 +969,8 @@ describe('ReactFresh', () => {
969
970
it('can patch lazy before resolution', async () => {
971
if (__DEV__) {
971
- render(() => {
972
+ let resolve;
973
+ await render(() => {
974
function Hello() {
975
const [val, setVal] = React.useState(0);
976
return (
@@ -981,8 +983,8 @@ describe('ReactFresh', () => {
983
984
const Outer = React.lazy(
985
() =>
984
- new Promise(resolve => {
985
- setTimeout(() => resolve({default: Hello}), 100);
986
+ new Promise(_resolve => {
987
+ resolve = () => _resolve({default: Hello});
988
}),
989
);
990
$RefreshReg$(Outer, 'Outer');
@@ -1014,7 +1016,7 @@ describe('ReactFresh', () => {
1016
});
1017
1018
await act(() => {
1017
- jest.runAllTimers();
1019
+ resolve();
1020
});
1021
1022
// Expect different color on initial mount.
@@ -1023,7 +1025,7 @@ describe('ReactFresh', () => {
1025
expect(el.style.color).toBe('red');
1026
1027
// Bump state.
1026
- act(() => {
1028
+ await act(() => {
1029
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1030
});
1031
expect(container.firstChild).toBe(el);
@@ -1050,7 +1052,8 @@ describe('ReactFresh', () => {
1052
1053
it('can patch lazy(forwardRef) before resolution', async () => {
1054
if (__DEV__) {
1053
- render(() => {
1055
+ let resolve;
1056
+ await render(() => {
1057
function renderHello() {
1058
const [val, setVal] = React.useState(0);
1059
return (
@@ -1064,8 +1067,8 @@ describe('ReactFresh', () => {
1067
1068
const Outer = React.lazy(
1069
() =>
1067
- new Promise(resolve => {
1068
- setTimeout(() => resolve({default: Hello}), 100);
1070
+ new Promise(_resolve => {
1071
+ resolve = () => _resolve({default: Hello});
1072
}),
1073
);
1074
$RefreshReg$(Outer, 'Outer');
@@ -1098,7 +1101,7 @@ describe('ReactFresh', () => {
1101
});
1102
1103
await act(() => {
1101
- jest.runAllTimers();
1104
+ resolve();
1105
});
1106
1107
// Expect different color on initial mount.
@@ -1107,7 +1110,7 @@ describe('ReactFresh', () => {
1110
expect(el.style.color).toBe('red');
1111
1112
// Bump state.
1110
- act(() => {
1113
+ await act(() => {
1114
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1115
});
1116
expect(container.firstChild).toBe(el);
@@ -1135,7 +1138,8 @@ describe('ReactFresh', () => {
1138
1139
it('can patch lazy(memo) before resolution', async () => {
1140
if (__DEV__) {
1138
- render(() => {
1141
+ let resolve;
1142
+ await render(() => {
1143
function renderHello() {
1144
const [val, setVal] = React.useState(0);
1145
return (
@@ -1149,8 +1153,8 @@ describe('ReactFresh', () => {
1153
1154
const Outer = React.lazy(
1155
() =>
1152
- new Promise(resolve => {
1153
- setTimeout(() => resolve({default: Hello}), 100);
1156
+ new Promise(_resolve => {
1157
+ resolve = () => _resolve({default: Hello});
1158
}),
1159
);
1160
$RefreshReg$(Outer, 'Outer');
@@ -1183,7 +1187,7 @@ describe('ReactFresh', () => {
1187
});
1188
1189
await act(() => {
1186
- jest.runAllTimers();
1190
+ resolve();
1191
});
1192
1193
// Expect different color on initial mount.
@@ -1192,7 +1196,7 @@ describe('ReactFresh', () => {
1196
expect(el.style.color).toBe('red');
1197
1198
// Bump state.
1195
- act(() => {
1199
+ await act(() => {
1200
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1201
});
1202
expect(container.firstChild).toBe(el);
@@ -1220,7 +1224,8 @@ describe('ReactFresh', () => {
1224
1225
it('can patch lazy(memo(forwardRef)) before resolution', async () => {
1226
if (__DEV__) {
1223
- render(() => {
1227
+ let resolve;
1228
+ await render(() => {
1229
function renderHello() {
1230
const [val, setVal] = React.useState(0);
1231
return (
@@ -1234,8 +1239,8 @@ describe('ReactFresh', () => {
1239
1240
const Outer = React.lazy(
1241
() =>
1237
- new Promise(resolve => {
1238
- setTimeout(() => resolve({default: Hello}), 100);
1242
+ new Promise(_resolve => {
1243
+ resolve = () => _resolve({default: Hello});
1244
}),
1245
);
1246
$RefreshReg$(Outer, 'Outer');
@@ -1268,7 +1273,7 @@ describe('ReactFresh', () => {
1273
});
1274
1275
await act(() => {
1271
- jest.runAllTimers();
1276
+ resolve();
1277
});
1278
1279
// Expect different color on initial mount.
@@ -1277,7 +1282,7 @@ describe('ReactFresh', () => {
1282
expect(el.style.color).toBe('red');
1283
1284
// Bump state.
1280
- act(() => {
1285
+ await act(() => {
1286
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1287
});
1288
expect(container.firstChild).toBe(el);
@@ -1303,9 +1308,9 @@ describe('ReactFresh', () => {
1308
}
1309
});
1310
1306
- it('can patch both trees while suspense is displaying the fallback', async () => {
1311
+ it('only patches the fallback tree while suspended', async () => {
1312
if (__DEV__) {
1308
- const AppV1 = render(
1313
+ const AppV1 = await render(
1314
() => {
1315
function Hello({children}) {
1316
const [val, setVal] = React.useState(0);
@@ -1343,7 +1348,7 @@ describe('ReactFresh', () => {
1348
expect(primaryChild.style.display).toBe('');
1349
1350
// Bump primary content state.
1346
- act(() => {
1351
+ await act(() => {
1352
primaryChild.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1353
});
1354
expect(container.childNodes.length).toBe(1);
@@ -1371,7 +1376,7 @@ describe('ReactFresh', () => {
1376
expect(primaryChild.style.display).toBe('');
1377
1378
// Now force the tree to suspend.
1374
- render(() => AppV1, {shouldSuspend: true});
1379
+ await render(() => AppV1, {shouldSuspend: true});
1380
1381
// Expect to see two trees, one of them is hidden.
1382
expect(container.childNodes.length).toBe(2);
@@ -1385,7 +1390,7 @@ describe('ReactFresh', () => {
1390
expect(fallbackChild.style.display).toBe('');
1391
1392
// Bump fallback state.
1388
- act(() => {
1393
+ await act(() => {
1394
fallbackChild.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1395
});
1396
expect(container.childNodes.length).toBe(2);
@@ -1411,19 +1416,19 @@ describe('ReactFresh', () => {
1416
$RefreshReg$(Hello, 'Hello');
1417
});
1418
1414
- // Colors inside both trees should change:
1419
+ // Only update color in the visible child
1420
expect(container.childNodes.length).toBe(2);
1421
expect(container.childNodes[0]).toBe(primaryChild);
1422
expect(container.childNodes[1]).toBe(fallbackChild);
1423
expect(primaryChild.textContent).toBe('Content 1');
1419
- expect(primaryChild.style.color).toBe('red');
1424
+ expect(primaryChild.style.color).toBe('green');
1425
expect(primaryChild.style.display).toBe('none');
1426
expect(fallbackChild.textContent).toBe('Fallback 1');
1427
expect(fallbackChild.style.color).toBe('red');
1428
expect(fallbackChild.style.display).toBe('');
1429
1430
// Only primary tree should exist now:
1426
- render(() => AppV1, {shouldSuspend: false});
1431
+ await render(() => AppV1, {shouldSuspend: false});
1432
expect(container.childNodes.length).toBe(1);
1433
expect(container.childNodes[0]).toBe(primaryChild);
1434
expect(primaryChild.textContent).toBe('Content 1');
@@ -1450,11 +1455,11 @@ describe('ReactFresh', () => {
1455
}
1456
});
1457
1453
- it('does not re-render ancestor components unnecessarily during a hot update', () => {
1458
+ it('does not re-render ancestor components unnecessarily during a hot update', async () => {
1459
if (__DEV__) {
1460
let appRenders = 0;
1461
1457
- render(() => {
1462
+ await render(() => {
1463
function Hello() {
1464
const [val, setVal] = React.useState(0);
1465
return (
@@ -1478,7 +1483,7 @@ describe('ReactFresh', () => {
1483
const el = container.firstChild;
1484
expect(el.textContent).toBe('0');
1485
expect(el.style.color).toBe('blue');
1481
- act(() => {
1486
+ await act(() => {
1487
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1488
});
1489
expect(el.textContent).toBe('1');
@@ -1508,7 +1513,7 @@ describe('ReactFresh', () => {
1513
expect(appRenders).toBe(1);
1514
1515
// Bump the state.
1511
- act(() => {
1516
+ await act(() => {
1517
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1518
});
1519
expect(el.textContent).toBe('2');
@@ -1518,11 +1523,11 @@ describe('ReactFresh', () => {
1523
}
1524
});
1525
1521
- it('batches re-renders during a hot update', () => {
1526
+ it('batches re-renders during a hot update', async () => {
1527
if (__DEV__) {
1528
let helloRenders = 0;
1529
1525
- render(() => {
1530
+ await render(() => {
1531
function Hello({children}) {
1532
helloRenders++;
1533
return <div>X{children}X</div>;
@@ -1559,9 +1564,9 @@ describe('ReactFresh', () => {
1564
}
1565
});
1566
1562
- it('does not leak state between components', () => {
1567
+ it('does not leak state between components', async () => {
1568
if (__DEV__) {
1564
- const AppV1 = render(
1569
+ const AppV1 = await render(
1570
() => {
1571
function Hello1() {
1572
const [val, setVal] = React.useState(0);
@@ -1594,21 +1599,21 @@ describe('ReactFresh', () => {
1599
const el = container.firstChild;
1600
expect(el.textContent).toBe('0');
1601
expect(el.style.color).toBe('blue');
1597
- act(() => {
1602
+ await act(() => {
1603
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1604
});
1605
expect(el.textContent).toBe('1');
1606
1607
// Switch the condition, flipping inner content.
1608
// This should reset the state.
1604
- render(() => AppV1, {cond: true});
1609
+ await render(() => AppV1, {cond: true});
1610
const el2 = container.firstChild;
1611
expect(el2).not.toBe(el);
1612
expect(el2.textContent).toBe('0');
1613
expect(el2.style.color).toBe('blue');
1614
1615
// Bump it again.
1611
- act(() => {
1616
+ await act(() => {
1617
el2.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1618
});
1619
expect(el2.textContent).toBe('1');
@@ -1641,7 +1646,7 @@ describe('ReactFresh', () => {
1646
expect(el2.style.color).toBe('red');
1647
1648
// Flip the condition again.
1644
- render(() => AppV1, {cond: false});
1649
+ await render(() => AppV1, {cond: false});
1650
const el3 = container.firstChild;
1651
expect(el3).not.toBe(el2);
1652
expect(el3.textContent).toBe('0');
@@ -1649,9 +1654,9 @@ describe('ReactFresh', () => {
1654
}
1655
});
1656
1652
- it('can force remount by changing signature', () => {
1657
+ it('can force remount by changing signature', async () => {
1658
if (__DEV__) {
1654
- const HelloV1 = render(() => {
1659
+ const HelloV1 = await render(() => {
1660
function Hello() {
1661
const [val, setVal] = React.useState(0);
1662
return (
@@ -1670,7 +1675,7 @@ describe('ReactFresh', () => {
1675
const el = container.firstChild;
1676
expect(el.textContent).toBe('0');
1677
expect(el.style.color).toBe('blue');
1673
- act(() => {
1678
+ await act(() => {
1679
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1680
});
1681
expect(el.textContent).toBe('1');
@@ -1719,7 +1724,7 @@ describe('ReactFresh', () => {
1724
expect(newEl.style.color).toBe('yellow');
1725
1726
// Bump state again.
1722
- act(() => {
1727
+ await act(() => {
1728
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
1729
});
1730
expect(newEl.textContent).toBe('1');
@@ -1728,11 +1733,11 @@ describe('ReactFresh', () => {
1733
// Perform top-down renders with both fresh and stale types.
1734
// Neither should change the state or color.
1735
// They should always resolve to the latest version.
1731
- render(() => HelloV1);
1732
- render(() => HelloV2);
1733
- render(() => HelloV3);
1734
- render(() => HelloV2);
1735
- render(() => HelloV1);
1736
+ await render(() => HelloV1);
1737
+ await render(() => HelloV2);
1738
+ await render(() => HelloV3);
1739
+ await render(() => HelloV2);
1740
+ await render(() => HelloV1);
1741
expect(container.firstChild).toBe(newEl);
1742
expect(newEl.textContent).toBe('1');
1743
expect(newEl.style.color).toBe('yellow');
@@ -1780,7 +1785,7 @@ describe('ReactFresh', () => {
1785
}
1786
});
1787
1783
- it('keeps a valid tree when forcing remount', () => {
1788
+ it('keeps a valid tree when forcing remount', async () => {
1789
if (__DEV__) {
1790
const HelloV1 = prepare(() => {
1791
function Hello() {
@@ -1910,26 +1915,31 @@ describe('ReactFresh', () => {
1915
</HelloV1>,
1916
];
1917
1913
- // First, check that each tree handles remounts in isolation.
1914
- ReactDOM.render(null, container);
1918
+ await act(() => {
1919
+ root.render(null);
1920
+ });
1921
+
1922
for (let i = 0; i < trees.length; i++) {
1916
- runRemountingStressTest(trees[i]);
1923
+ await runRemountingStressTest(trees[i]);
1924
}
1925
1926
// Then check that each tree is resilient to updates from another tree.
1927
for (let i = 0; i < trees.length; i++) {
1928
for (let j = 0; j < trees.length; j++) {
1922
- ReactDOM.render(null, container);
1929
+ await act(() => {
1930
+ root.render(null);
1931
+ });
1932
+
1933
// Intentionally don't clean up between the tests:
1924
- runRemountingStressTest(trees[i]);
1925
- runRemountingStressTest(trees[j]);
1926
- runRemountingStressTest(trees[i]);
1934
+ await runRemountingStressTest(trees[i]);
1935
+ await runRemountingStressTest(trees[j]);
1936
+ await runRemountingStressTest(trees[i]);
1937
}
1938
}
1939
}
1930
- });
1940
+ }, 10000);
1941
1932
- function runRemountingStressTest(tree) {
1942
+ async function runRemountingStressTest(tree) {
1943
patch(() => {
1944
function Hello({children}) {
1945
return <section data-color="blue">{children}</section>;
@@ -1939,7 +1949,10 @@ describe('ReactFresh', () => {
1949
return Hello;
1950
});
1951
1942
- ReactDOM.render(tree, container);
1952
+ await act(() => {
1953
+ root.render(tree);
1954
+ });
1955
+
1956
const elements = container.querySelectorAll('section');
1957
// Each tree above produces exactly three <section> elements:
1958
expect(elements.length).toBe(3);
@@ -2003,8 +2016,10 @@ describe('ReactFresh', () => {
2016
expect(el.dataset.color).toBe('black');
2017
});
2018
2006
- // Do another render just in case.
2007
- ReactDOM.render(tree, container);
2019
+ await act(() => {
2020
+ root.render(tree);
2021
+ });
2022
+
2023
expect(container.querySelectorAll('section').length).toBe(3);
2024
container.querySelectorAll('section').forEach((el, index) => {
2025
expect(el).toBe(elementsAfterRemount[index]);
@@ -2012,21 +2027,21 @@ describe('ReactFresh', () => {
2027
});
2028
}
2029
2015
- it('can remount on signature change within a <root> wrapper', () => {
2030
+ it('can remount on signature change within a <root> wrapper', async () => {
2031
if (__DEV__) {
2017
- testRemountingWithWrapper(Hello => Hello);
2032
+ await testRemountingWithWrapper(Hello => Hello);
2033
}
2034
});
2035
2021
- it('can remount on signature change within a simple memo wrapper', () => {
2036
+ it('can remount on signature change within a simple memo wrapper', async () => {
2037
if (__DEV__) {
2023
- testRemountingWithWrapper(Hello => React.memo(Hello));
2038
+ await testRemountingWithWrapper(Hello => React.memo(Hello));
2039
}
2040
});
2041
2027
- it('can remount on signature change within a lazy simple memo wrapper', () => {
2042
+ it('can remount on signature change within a lazy simple memo wrapper', async () => {
2043
if (__DEV__) {
2029
- testRemountingWithWrapper(Hello =>
2044
+ await testRemountingWithWrapper(Hello =>
2045
React.lazy(() => ({
2046
then(cb) {
2047
cb({default: React.memo(Hello)});
@@ -2036,35 +2051,37 @@ describe('ReactFresh', () => {
2051
}
2052
});
2053
2039
- it('can remount on signature change within forwardRef', () => {
2054
+ it('can remount on signature change within forwardRef', async () => {
2055
if (__DEV__) {
2041
- testRemountingWithWrapper(Hello => React.forwardRef(Hello));
2056
+ await testRemountingWithWrapper(Hello => React.forwardRef(Hello));
2057
}
2058
});
2059
2045
- it('can remount on signature change within forwardRef render function', () => {
2060
+ it('can remount on signature change within forwardRef render function', async () => {
2061
if (__DEV__) {
2047
- testRemountingWithWrapper(Hello => React.forwardRef(() => <Hello />));
2062
+ await testRemountingWithWrapper(Hello =>
2063
+ React.forwardRef(() => <Hello />),
2064
+ );
2065
}
2066
});
2067
2051
- it('can remount on signature change within nested memo', () => {
2068
+ it('can remount on signature change within nested memo', async () => {
2069
if (__DEV__) {
2053
- testRemountingWithWrapper(Hello =>
2070
+ await testRemountingWithWrapper(Hello =>
2071
React.memo(React.memo(React.memo(Hello))),
2072
);
2073
}
2074
});
2075
2059
- it('can remount on signature change within a memo wrapper and custom comparison', () => {
2076
+ it('can remount on signature change within a memo wrapper and custom comparison', async () => {
2077
if (__DEV__) {
2061
- testRemountingWithWrapper(Hello => React.memo(Hello, () => true));
2078
+ await testRemountingWithWrapper(Hello => React.memo(Hello, () => true));
2079
}
2080
});
2081
2065
- it('can remount on signature change within a class', () => {
2082
+ it('can remount on signature change within a class', async () => {
2083
if (__DEV__) {
2067
- testRemountingWithWrapper(Hello => {
2084
+ await testRemountingWithWrapper(Hello => {
2085
const child = <Hello />;
2086
return class Wrapper extends React.PureComponent {
2087
render() {
@@ -2075,9 +2092,9 @@ describe('ReactFresh', () => {
2092
}
2093
});
2094
2078
- it('can remount on signature change within a context provider', () => {
2095
+ it('can remount on signature change within a context provider', async () => {
2096
if (__DEV__) {
2080
- testRemountingWithWrapper(Hello => {
2097
+ await testRemountingWithWrapper(Hello => {
2098
const Context = React.createContext();
2099
const child = (
2100
<Context.Provider value="constant">
@@ -2091,9 +2108,9 @@ describe('ReactFresh', () => {
2108
}
2109
});
2110
2094
- it('can remount on signature change within a context consumer', () => {
2111
+ it('can remount on signature change within a context consumer', async () => {
2112
if (__DEV__) {
2096
- testRemountingWithWrapper(Hello => {
2113
+ await testRemountingWithWrapper(Hello => {
2114
const Context = React.createContext();
2115
const child = <Context.Consumer>{() => <Hello />}</Context.Consumer>;
2116
return function Wrapper() {
@@ -2103,9 +2120,9 @@ describe('ReactFresh', () => {
2120
}
2121
});
2122
2106
- it('can remount on signature change within a suspense node', () => {
2123
+ it('can remount on signature change within a suspense node', async () => {
2124
if (__DEV__) {
2108
- testRemountingWithWrapper(Hello => {
2125
+ await testRemountingWithWrapper(Hello => {
2126
// TODO: we'll probably want to test fallback trees too.
2127
const child = (
2128
<React.Suspense>
@@ -2119,9 +2136,9 @@ describe('ReactFresh', () => {
2136
}
2137
});
2138
2122
- it('can remount on signature change within a mode node', () => {
2139
+ it('can remount on signature change within a mode node', async () => {
2140
if (__DEV__) {
2124
- testRemountingWithWrapper(Hello => {
2141
+ await testRemountingWithWrapper(Hello => {
2142
const child = (
2143
<React.StrictMode>
2144
<Hello />
@@ -2134,9 +2151,9 @@ describe('ReactFresh', () => {
2151
}
2152
});
2153
2137
- it('can remount on signature change within a fragment node', () => {
2154
+ it('can remount on signature change within a fragment node', async () => {
2155
if (__DEV__) {
2139
- testRemountingWithWrapper(Hello => {
2156
+ await testRemountingWithWrapper(Hello => {
2157
const child = (
2158
<>
2159
<Hello />
@@ -2149,9 +2166,9 @@ describe('ReactFresh', () => {
2166
}
2167
});
2168
2152
- it('can remount on signature change within multiple siblings', () => {
2169
+ it('can remount on signature change within multiple siblings', async () => {
2170
if (__DEV__) {
2154
- testRemountingWithWrapper(Hello => {
2171
+ await testRemountingWithWrapper(Hello => {
2172
const child = (
2173
<>
2174
<>
@@ -2168,9 +2185,9 @@ describe('ReactFresh', () => {
2185
}
2186
});
2187
2171
- it('can remount on signature change within a profiler node', () => {
2188
+ it('can remount on signature change within a profiler node', async () => {
2189
if (__DEV__) {
2173
- testRemountingWithWrapper(Hello => {
2190
+ await testRemountingWithWrapper(Hello => {
2191
const child = <Hello />;
2192
return function Wrapper() {
2193
return (
@@ -2183,8 +2200,8 @@ describe('ReactFresh', () => {
2200
}
2201
});
2202
2186
- function testRemountingWithWrapper(wrap) {
2187
- render(() => {
2203
+ async function testRemountingWithWrapper(wrap) {
2204
+ await render(() => {
2205
function Hello() {
2206
const [val, setVal] = React.useState(0);
2207
return (
@@ -2206,7 +2223,7 @@ describe('ReactFresh', () => {
2223
const el = container.firstChild;
2224
expect(el.textContent).toBe('0');
2225
expect(el.style.color).toBe('blue');
2209
- act(() => {
2226
+ await act(() => {
2227
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
2228
});
2229
expect(el.textContent).toBe('1');
@@ -2255,7 +2272,7 @@ describe('ReactFresh', () => {
2272
expect(newEl.style.color).toBe('yellow');
2273
2274
// Bump state again.
2258
- act(() => {
2275
+ await act(() => {
2276
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
2277
});
2278
expect(newEl.textContent).toBe('1');
@@ -2303,11 +2320,11 @@ describe('ReactFresh', () => {
2320
expect(finalEl.style.color).toBe('orange');
2321
}
2322
2306
- it('resets hooks with dependencies on hot reload', () => {
2323
+ it('resets hooks with dependencies on hot reload', async () => {
2324
if (__DEV__) {
2325
let useEffectWithEmptyArrayCalls = 0;
2326
2310
- render(() => {
2327
+ await render(() => {
2328
function Hello() {
2329
const [val, setVal] = React.useState(0);
2330
const tranformed = React.useMemo(() => val * 2, [val]);
@@ -2332,33 +2349,31 @@ describe('ReactFresh', () => {
2349
expect(el.textContent).toBe('0');
2350
expect(el.style.color).toBe('blue');
2351
expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect ran
2335
- act(() => {
2352
+ await act(() => {
2353
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
2354
});
2355
expect(el.textContent).toBe('2'); // val * 2
2356
expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect didn't re-run
2357
2358
// Perform a hot update.
2342
- act(() => {
2343
- patch(() => {
2344
- function Hello() {
2345
- const [val, setVal] = React.useState(0);
2346
- const tranformed = React.useMemo(() => val * 10, [val]);
2347
- const handleClick = React.useCallback(() => setVal(v => v - 1), []);
2359
+ patch(() => {
2360
+ function Hello() {
2361
+ const [val, setVal] = React.useState(0);
2362
+ const tranformed = React.useMemo(() => val * 10, [val]);
2363
+ const handleClick = React.useCallback(() => setVal(v => v - 1), []);
2364
2349
- React.useEffect(() => {
2350
- useEffectWithEmptyArrayCalls++;
2351
- }, []);
2365
+ React.useEffect(() => {
2366
+ useEffectWithEmptyArrayCalls++;
2367
+ }, []);
2368
2353
- return (
2354
- <p style={{color: 'red'}} onClick={handleClick}>
2355
- {tranformed}
2356
- </p>
2357
- );
2358
- }
2359
- $RefreshReg$(Hello, 'Hello');
2360
- return Hello;
2361
- });
2369
+ return (
2370
+ <p style={{color: 'red'}} onClick={handleClick}>
2371
+ {tranformed}
2372
+ </p>
2373
+ );
2374
+ }
2375
+ $RefreshReg$(Hello, 'Hello');
2376
+ return Hello;
2377
});
2378
2379
// Assert the state was preserved but memo was evicted.
@@ -2368,7 +2383,7 @@ describe('ReactFresh', () => {
2383
expect(useEffectWithEmptyArrayCalls).toBe(2); // useEffect re-ran
2384
2385
// This should fire the new callback which decreases the counter.
2371
- act(() => {
2386
+ await act(() => {
2387
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
2388
});
2389
expect(el.textContent).toBe('0');
@@ -2378,9 +2393,9 @@ describe('ReactFresh', () => {
2393
});
2394
2395
// This pattern is inspired by useSubscription and similar mechanisms.
2381
- it('does not get into infinite loops during render phase updates', () => {
2396
+ it('does not get into infinite loops during render phase updates', async () => {
2397
if (__DEV__) {
2383
- render(() => {
2398
+ await render(() => {
2399
function Hello() {
2400
const source = React.useMemo(() => ({value: 10}), []);
2401
const [state, setState] = React.useState({value: null});
@@ -2398,20 +2413,18 @@ describe('ReactFresh', () => {
2413
expect(el.style.color).toBe('blue');
2414
2415
// Perform a hot update.
2401
- act(() => {
2402
- patch(() => {
2403
- function Hello() {
2404
- const source = React.useMemo(() => ({value: 20}), []);
2405
- const [state, setState] = React.useState({value: null});
2406
- if (state !== source) {
2407
- // This should perform a single render-phase update.
2408
- setState(source);
2409
- }
2410
- return <p style={{color: 'red'}}>{state.value}</p>;
2416
+ patch(() => {
2417
+ function Hello() {
2418
+ const source = React.useMemo(() => ({value: 20}), []);
2419
+ const [state, setState] = React.useState({value: null});
2420
+ if (state !== source) {
2421
+ // This should perform a single render-phase update.
2422
+ setState(source);
2423
}
2412
- $RefreshReg$(Hello, 'Hello');
2413
- return Hello;
2414
- });
2424
+ return <p style={{color: 'red'}}>{state.value}</p>;
2425
+ }
2426
+ $RefreshReg$(Hello, 'Hello');
2427
+ return Hello;
2428
});
2429
2430
expect(container.firstChild).toBe(el);
@@ -2448,7 +2461,6 @@ describe('ReactFresh', () => {
2461
};
2462
});
2463
2451
- const root = ReactDOMClient.createRoot(container);
2464
root.render(<AppV1 offscreen={true} />);
2465
await waitFor(['App#layout']);
2466
const el = container.firstChild;
@@ -2482,7 +2494,7 @@ describe('ReactFresh', () => {
2494
expect(el.firstChild.textContent).toBe('0');
2495
expect(el.firstChild.style.color).toBe('red');
2496
2485
- await internalAct(() => {
2497
+ await act(() => {
2498
el.firstChild.dispatchEvent(
2499
new MouseEvent('click', {
2500
bubbles: true,
@@ -2522,9 +2534,9 @@ describe('ReactFresh', () => {
2534
expect(el.firstChild.style.color).toBe('orange');
2535
});
2536
2525
- it('remounts failed error boundaries (componentDidCatch)', () => {
2537
+ it('remounts failed error boundaries (componentDidCatch)', async () => {
2538
if (__DEV__) {
2527
- render(() => {
2539
+ await render(() => {
2540
function Hello() {
2541
return <h1>Hi</h1>;
2542
}
@@ -2600,9 +2612,9 @@ describe('ReactFresh', () => {
2612
}
2613
});
2614
2603
- it('remounts failed error boundaries (getDerivedStateFromError)', () => {
2615
+ it('remounts failed error boundaries (getDerivedStateFromError)', async () => {
2616
if (__DEV__) {
2605
- render(() => {
2617
+ await render(() => {
2618
function Hello() {
2619
return <h1>Hi</h1>;
2620
}
@@ -2678,9 +2690,9 @@ describe('ReactFresh', () => {
2690
}
2691
});
2692
2681
- it('remounts error boundaries that failed asynchronously after hot update', () => {
2693
+ it('remounts error boundaries that failed asynchronously after hot update', async () => {
2694
if (__DEV__) {
2683
- render(() => {
2695
+ await render(() => {
2696
function Hello() {
2697
const [x] = React.useState('');
2698
React.useEffect(() => {}, []);
@@ -2722,26 +2734,25 @@ describe('ReactFresh', () => {
2734
const secondP = firstP.nextSibling.nextSibling;
2735
2736
// Perform a hot update that fails.
2725
- act(() => {
2726
- patch(() => {
2727
- function Hello() {
2728
- const [x, setX] = React.useState('');
2729
- React.useEffect(() => {
2730
- setTimeout(() => {
2731
- setX(42); // This will crash next render.
2732
- }, 1);
2733
- }, []);
2734
- x.slice();
2735
- return <h1>Hi</h1>;
2736
- }
2737
- $RefreshReg$(Hello, 'Hello');
2738
- });
2737
+ let crash;
2738
+ patch(() => {
2739
+ function Hello() {
2740
+ const [x, setX] = React.useState('');
2741
+ React.useEffect(() => {
2742
+ crash = () => {
2743
+ setX(42); // This will crash next render.
2744
+ };
2745
+ }, []);
2746
+ x.slice();
2747
+ return <h1>Hi</h1>;
2748
+ }
2749
+ $RefreshReg$(Hello, 'Hello');
2750
});
2751
2752
expect(container.innerHTML).toBe('<p>A</p><h1>Hi</h1><p>B</p>');
2753
// Run timeout inside effect:
2743
- act(() => {
2744
- jest.runAllTimers();
2754
+ await act(() => {
2755
+ crash();
2756
});
2757
expect(container.innerHTML).toBe(
2758
'<p>A</p><h1>Oops: x.slice is not a function</h1><p>B</p>',
@@ -2750,16 +2761,14 @@ describe('ReactFresh', () => {
2761
expect(container.firstChild.nextSibling.nextSibling).toBe(secondP);
2762
2763
// Perform a hot update that fixes the error.
2753
- act(() => {
2754
- patch(() => {
2755
- function Hello() {
2756
- const [x] = React.useState('');
2757
- React.useEffect(() => {}, []); // Removes the bad effect code.
2758
- x.slice(); // Doesn't throw initially.
2759
- return <h1>Fixed!</h1>;
2760
- }
2761
- $RefreshReg$(Hello, 'Hello');
2762
- });
2764
+ patch(() => {
2765
+ function Hello() {
2766
+ const [x] = React.useState('');
2767
+ React.useEffect(() => {}, []); // Removes the bad effect code.
2768
+ x.slice(); // Doesn't throw initially.
2769
+ return <h1>Fixed!</h1>;
2770
+ }
2771
+ $RefreshReg$(Hello, 'Hello');
2772
});
2773
2774
// This should remount the error boundary (but not anything above it).
@@ -2769,16 +2778,14 @@ describe('ReactFresh', () => {
2778
2779
// Verify next hot reload doesn't remount anything.
2780
const helloNode = container.firstChild.nextSibling;
2772
- act(() => {
2773
- patch(() => {
2774
- function Hello() {
2775
- const [x] = React.useState('');
2776
- React.useEffect(() => {}, []);
2777
- x.slice();
2778
- return <h1>Nice.</h1>;
2779
- }
2780
- $RefreshReg$(Hello, 'Hello');
2781
- });
2781
+ patch(() => {
2782
+ function Hello() {
2783
+ const [x] = React.useState('');
2784
+ React.useEffect(() => {}, []);
2785
+ x.slice();
2786
+ return <h1>Nice.</h1>;
2787
+ }
2788
+ $RefreshReg$(Hello, 'Hello');
2789
});
2790
2791
expect(container.firstChild.nextSibling).toBe(helloNode);
@@ -2786,9 +2793,9 @@ describe('ReactFresh', () => {
2793
}
2794
});
2795
2789
- it('remounts a failed root on mount', () => {
2796
+ it('remounts a failed root on mount', async () => {
2797
if (__DEV__) {
2791
- expect(() => {
2798
+ await expect(
2799
render(() => {
2800
function Hello() {
2801
throw new Error('No');
@@ -2796,8 +2803,8 @@ describe('ReactFresh', () => {
2803
$RefreshReg$(Hello, 'Hello');
2804
2805
return Hello;
2799
- });
2800
- }).toThrow('No');
2806
+ }),
2807
+ ).rejects.toThrow('No');
2808
expect(container.innerHTML).toBe('');
2809
2810
// A bad retry
@@ -2849,7 +2856,9 @@ describe('ReactFresh', () => {
2856
expect(container.innerHTML).toBe('<h1>Fixed 2!</h1>');
2857
2858
// Updates after intentional unmount are ignored.
2852
- ReactDOM.unmountComponentAtNode(container);
2859
+ await act(() => {
2860
+ root.unmount();
2861
+ });
2862
patch(() => {
2863
function Hello() {
2864
throw new Error('Ignored');
@@ -2867,9 +2876,9 @@ describe('ReactFresh', () => {
2876
}
2877
});
2878
2870
- it('does not retry an intentionally unmounted failed root', () => {
2879
+ it('does not retry an intentionally unmounted failed root', async () => {
2880
if (__DEV__) {
2872
- expect(() => {
2881
+ await expect(
2882
render(() => {
2883
function Hello() {
2884
throw new Error('No');
@@ -2877,12 +2886,14 @@ describe('ReactFresh', () => {
2886
$RefreshReg$(Hello, 'Hello');
2887
2888
return Hello;
2880
- });
2881
- }).toThrow('No');
2889
+ }),
2890
+ ).rejects.toThrow('No');
2891
expect(container.innerHTML).toBe('');
2892
2893
// Intentional unmount.
2885
- ReactDOM.unmountComponentAtNode(container);
2894
+ await act(() => {
2895
+ root.unmount();
2896
+ });
2897
2898
// Perform a hot update that fixes the error.
2899
patch(() => {
@@ -2896,9 +2907,9 @@ describe('ReactFresh', () => {
2907
}
2908
});
2909
2899
- it('remounts a failed root on update', () => {
2910
+ it('remounts a failed root on update', async () => {
2911
if (__DEV__) {
2901
- render(() => {
2912
+ await render(() => {
2913
function Hello() {
2914
return <h1>Hi</h1>;
2915
}
@@ -2974,7 +2985,9 @@ describe('ReactFresh', () => {
2985
expect(container.innerHTML).toBe('<h1>At last.</h1>');
2986
2987
// Check we don't attempt to reverse an intentional unmount.
2977
- ReactDOM.unmountComponentAtNode(container);
2988
+ await act(() => {
2989
+ root.unmount();
2990
+ });
2991
expect(container.innerHTML).toBe('');
2992
patch(() => {
2993
function Hello() {
@@ -2985,7 +2998,8 @@ describe('ReactFresh', () => {
2998
expect(container.innerHTML).toBe('');
2999
3000
// Mount a new container.
2988
- render(() => {
3001
+ root = ReactDOMClient.createRoot(container);
3002
+ await render(() => {
3003
function Hello() {
3004
return <h1>Hi</h1>;
3005
}
@@ -3007,7 +3021,9 @@ describe('ReactFresh', () => {
3021
expect(container.innerHTML).toBe('');
3022
3023
// Check we don't attempt to reverse an intentional unmount, even after an error.
3010
- ReactDOM.unmountComponentAtNode(container);
3024
+ await act(() => {
3025
+ root.unmount();
3026
+ });
3027
expect(container.innerHTML).toBe('');
3028
patch(() => {
3029
function Hello() {
@@ -3019,10 +3035,12 @@ describe('ReactFresh', () => {
3035
}
3036
});
3037
3022
- it('regression test: does not get into an infinite loop', () => {
3038
+ it('regression test: does not get into an infinite loop', async () => {
3039
if (__DEV__) {
3040
const containerA = document.createElement('div');
3041
const containerB = document.createElement('div');
3042
+ const rootA = ReactDOMClient.createRoot(containerA);
3043
+ const rootB = ReactDOMClient.createRoot(containerB);
3044
3045
// Initially, nothing interesting.
3046
const RootAV1 = () => {
@@ -3034,9 +3052,9 @@ describe('ReactFresh', () => {
3052
};
3053
$RefreshReg$(RootBV1, 'RootB');
3054
3037
- act(() => {
3038
- ReactDOM.render(<RootAV1 />, containerA);
3039
- ReactDOM.render(<RootBV1 />, containerB);
3055
+ await act(() => {
3056
+ rootA.render(<RootAV1 />);
3057
+ rootB.render(<RootBV1 />);
3058
});
3059
expect(containerA.innerHTML).toBe('A1');
3060
expect(containerB.innerHTML).toBe('B1');
@@ -3046,7 +3064,11 @@ describe('ReactFresh', () => {
3064
throw new Error('A2!');
3065
};
3066
$RefreshReg$(RootAV2, 'RootA');
3049
- expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A2!');
3067
+ await expect(
3068
+ act(() => {
3069
+ ReactFreshRuntime.performReactRefresh();
3070
+ }),
3071
+ ).rejects.toThrow('A2!');
3072
expect(containerA.innerHTML).toBe('');
3073
expect(containerB.innerHTML).toBe('B1');
3074
@@ -3060,7 +3082,11 @@ describe('ReactFresh', () => {
3082
return 'A3';
3083
};
3084
$RefreshReg$(RootAV3, 'RootA');
3063
- expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A3!');
3085
+ await expect(
3086
+ act(() => {
3087
+ ReactFreshRuntime.performReactRefresh();
3088
+ }),
3089
+ ).rejects.toThrow('A3!');
3090
expect(containerA.innerHTML).toBe('');
3091
expect(containerB.innerHTML).toBe('B1');
3092
@@ -3068,15 +3094,17 @@ describe('ReactFresh', () => {
3094
return 'A4';
3095
};
3096
$RefreshReg$(RootAV4, 'RootA');
3071
- ReactFreshRuntime.performReactRefresh();
3097
+ await act(() => {
3098
+ ReactFreshRuntime.performReactRefresh();
3099
+ });
3100
expect(containerA.innerHTML).toBe('A4');
3101
expect(containerB.innerHTML).toBe('B1');
3102
}
3103
});
3104
3077
- it('remounts classes on every edit', () => {
3105
+ it('remounts classes on every edit', async () => {
3106
if (__DEV__) {
3079
- const HelloV1 = render(() => {
3107
+ const HelloV1 = await render(() => {
3108
class Hello extends React.Component {
3109
state = {count: 0};
3110
handleClick = () => {
@@ -3105,7 +3133,7 @@ describe('ReactFresh', () => {
3133
const el = container.firstChild;
3134
expect(el.textContent).toBe('0');
3135
expect(el.style.color).toBe('blue');
3108
- act(() => {
3136
+ await act(() => {
3137
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3138
});
3139
expect(el.textContent).toBe('1');
@@ -3136,14 +3164,14 @@ describe('ReactFresh', () => {
3164
const newEl = container.firstChild;
3165
expect(newEl.textContent).toBe('0');
3166
expect(newEl.style.color).toBe('red');
3139
- act(() => {
3167
+ await act(() => {
3168
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3169
});
3170
expect(newEl.textContent).toBe('1');
3171
3172
// Now top-level renders of both types resolve to latest.
3145
- render(() => HelloV1);
3146
- render(() => HelloV2);
3173
+ await render(() => HelloV1);
3174
+ await render(() => HelloV2);
3175
expect(container.firstChild).toBe(newEl);
3176
expect(newEl.style.color).toBe('red');
3177
expect(newEl.textContent).toBe('1');
@@ -3173,24 +3201,24 @@ describe('ReactFresh', () => {
3201
const finalEl = container.firstChild;
3202
expect(finalEl.textContent).toBe('0');
3203
expect(finalEl.style.color).toBe('orange');
3176
- act(() => {
3204
+ await act(() => {
3205
finalEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3206
});
3207
expect(finalEl.textContent).toBe('1');
3208
3181
- render(() => HelloV3);
3182
- render(() => HelloV2);
3183
- render(() => HelloV1);
3209
+ await render(() => HelloV3);
3210
+ await render(() => HelloV2);
3211
+ await render(() => HelloV1);
3212
expect(container.firstChild).toBe(finalEl);
3213
expect(finalEl.style.color).toBe('orange');
3214
expect(finalEl.textContent).toBe('1');
3215
}
3216
});
3217
3190
- it('updates refs when remounting', () => {
3218
+ it('updates refs when remounting', async () => {
3219
if (__DEV__) {
3220
const testRef = React.createRef();
3193
- render(
3221
+ await render(
3222
() => {
3223
class Hello extends React.Component {
3224
getColor() {
@@ -3261,9 +3289,9 @@ describe('ReactFresh', () => {
3289
}
3290
});
3291
3264
- it('remounts on conversion from class to function and back', () => {
3292
+ it('remounts on conversion from class to function and back', async () => {
3293
if (__DEV__) {
3266
- const HelloV1 = render(() => {
3294
+ const HelloV1 = await render(() => {
3295
function Hello() {
3296
const [val, setVal] = React.useState(0);
3297
return (
@@ -3280,7 +3308,7 @@ describe('ReactFresh', () => {
3308
const el = container.firstChild;
3309
expect(el.textContent).toBe('0');
3310
expect(el.style.color).toBe('blue');
3283
- act(() => {
3311
+ await act(() => {
3312
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3313
});
3314
expect(el.textContent).toBe('1');
@@ -3311,14 +3339,14 @@ describe('ReactFresh', () => {
3339
const newEl = container.firstChild;
3340
expect(newEl.textContent).toBe('0');
3341
expect(newEl.style.color).toBe('red');
3314
- act(() => {
3342
+ await act(() => {
3343
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3344
});
3345
expect(newEl.textContent).toBe('1');
3346
3347
// Now top-level renders of both types resolve to latest.
3320
- render(() => HelloV1);
3321
- render(() => HelloV2);
3348
+ await render(() => HelloV1);
3349
+ await render(() => HelloV2);
3350
expect(container.firstChild).toBe(newEl);
3351
expect(newEl.style.color).toBe('red');
3352
expect(newEl.textContent).toBe('1');
@@ -3342,14 +3370,14 @@ describe('ReactFresh', () => {
3370
const finalEl = container.firstChild;
3371
expect(finalEl.textContent).toBe('0');
3372
expect(finalEl.style.color).toBe('orange');
3345
- act(() => {
3373
+ await act(() => {
3374
finalEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3375
});
3376
expect(finalEl.textContent).toBe('1');
3377
3350
- render(() => HelloV3);
3351
- render(() => HelloV2);
3352
- render(() => HelloV1);
3378
+ await render(() => HelloV3);
3379
+ await render(() => HelloV2);
3380
+ await render(() => HelloV1);
3381
expect(container.firstChild).toBe(finalEl);
3382
expect(finalEl.style.color).toBe('orange');
3383
expect(finalEl.textContent).toBe('1');
@@ -3373,9 +3401,9 @@ describe('ReactFresh', () => {
3401
}
3402
});
3403
3376
- it('can find host instances for a family', () => {
3404
+ it('can find host instances for a family', async () => {
3405
if (__DEV__) {
3378
- render(() => {
3406
+ await render(() => {
3407
function Child({children}) {
3408
return <div className="Child">{children}</div>;
3409
}
@@ -3481,7 +3509,7 @@ describe('ReactFresh', () => {
3509
});
3510
}
3511
3484
- it('can update multiple roots independently', () => {
3512
+ it('can update multiple roots independently', async () => {
3513
if (__DEV__) {
3514
// Declare the first version.
3515
const HelloV1 = () => {
@@ -3504,7 +3532,9 @@ describe('ReactFresh', () => {
3532
);
3533
};
3534
$RefreshReg$(HelloV2, 'Hello');
3507
- ReactFreshRuntime.performReactRefresh();
3535
+ await act(() => {
3536
+ ReactFreshRuntime.performReactRefresh();
3537
+ });
3538
3539
// Mount three roots.
3540
const cont1 = document.createElement('div');
@@ -3513,10 +3543,19 @@ describe('ReactFresh', () => {
3543
document.body.appendChild(cont1);
3544
document.body.appendChild(cont2);
3545
document.body.appendChild(cont3);
3546
+ const root1 = ReactDOMClient.createRoot(cont1);
3547
+ const root2 = ReactDOMClient.createRoot(cont2);
3548
+ const root3 = ReactDOMClient.createRoot(cont3);
3549
try {
3517
- ReactDOM.render(<HelloV1 id={1} />, cont1);
3518
- ReactDOM.render(<HelloV2 id={2} />, cont2);
3519
- ReactDOM.render(<HelloV1 id={3} />, cont3);
3550
+ await act(() => {
3551
+ root1.render(<HelloV1 id={1} />);
3552
+ });
3553
+ await act(() => {
3554
+ root2.render(<HelloV2 id={2} />);
3555
+ });
3556
+ await act(() => {
3557
+ root3.render(<HelloV1 id={3} />);
3558
+ });
3559
3560
// Expect we see the V2 color.
3561
expect(cont1.firstChild.style.color).toBe('red');
@@ -3527,7 +3566,7 @@ describe('ReactFresh', () => {
3566
expect(cont3.firstChild.textContent).toBe('0');
3567
3568
// Bump the state for each of them.
3530
- act(() => {
3569
+ await act(() => {
3570
cont1.firstChild.dispatchEvent(
3571
new MouseEvent('click', {bubbles: true}),
3572
);
@@ -3555,7 +3594,9 @@ describe('ReactFresh', () => {
3594
);
3595
};
3596
$RefreshReg$(HelloV3, 'Hello');
3558
- ReactFreshRuntime.performReactRefresh();
3597
+ await act(() => {
3598
+ ReactFreshRuntime.performReactRefresh();
3599
+ });
3600
3601
// It should affect all roots.
3602
expect(cont1.firstChild.style.color).toBe('green');
@@ -3566,7 +3607,9 @@ describe('ReactFresh', () => {
3607
expect(cont3.firstChild.textContent).toBe('1');
3608
3609
// Unmount the second root.
3569
- ReactDOM.unmountComponentAtNode(cont2);
3610
+ await act(() => {
3611
+ root2.unmount();
3612
+ });
3613
// Make the first root throw and unmount on hot update.
3614
const HelloV4 = ({id}) => {
3615
if (id === 1) {
@@ -3580,9 +3623,11 @@ describe('ReactFresh', () => {
3623
);
3624
};
3625
$RefreshReg$(HelloV4, 'Hello');
3583
- expect(() => {
3584
- ReactFreshRuntime.performReactRefresh();
3585
- }).toThrow('Oops.');
3626
+ await expect(
3627
+ act(() => {
3628
+ ReactFreshRuntime.performReactRefresh();
3629
+ }),
3630
+ ).rejects.toThrow('Oops.');
3631
3632
// Still, we expect the last root to be updated.
3633
expect(cont1.innerHTML).toBe('');
@@ -3791,18 +3836,20 @@ describe('ReactFresh', () => {
3836
jest.resetModules();
3837
React = require('react');
3838
ReactDOM = require('react-dom');
3839
+ ReactDOMClient = require('react-dom/client');
3840
Scheduler = require('scheduler');
3795
- act = React.act;
3796
- internalAct = require('internal-test-utils').act;
3841
+ act = require('internal-test-utils').act;
3842
3843
// Important! Inject into the global hook *after* ReactDOM runs:
3844
ReactFreshRuntime = require('react-refresh/runtime');
3845
ReactFreshRuntime.injectIntoGlobalHook(global);
3846
3847
+ root = ReactDOMClient.createRoot(container);
3848
+
3849
// We're verifying that we're able to track roots mounted after this point.
3850
// The rest of this test is taken from the simplest first test case.
3851
3805
- render(() => {
3852
+ await render(() => {
3853
function Hello() {
3854
const [val, setVal] = React.useState(0);
3855
return (
@@ -3819,7 +3866,7 @@ describe('ReactFresh', () => {
3866
const el = container.firstChild;
3867
expect(el.textContent).toBe('0');
3868
expect(el.style.color).toBe('blue');
3822
- act(() => {
3869
+ await act(() => {
3870
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
3871
});
3872
expect(el.textContent).toBe('1');
@@ -3846,7 +3893,7 @@ describe('ReactFresh', () => {
3893
});
3894
3895
// This simulates the scenario in https://github.com/facebook/react/issues/20100
3849
- it('does not block DevTools when an unsupported renderer is injected', () => {
3896
+ it('does not block DevTools when an unsupported legacy renderer is injected', () => {
3897
if (__DEV__) {
3898
initFauxDevToolsHook();
3899
@@ -3872,13 +3919,12 @@ describe('ReactFresh', () => {
3919
ReactFreshRuntime = require('react-refresh/runtime');
3920
ReactFreshRuntime.injectIntoGlobalHook(global);
3921
3875
- render(() => {
3876
- function Hello() {
3877
- return <div>Hi!</div>;
3878
- }
3879
- $RefreshReg$(Hello, 'Hello');
3880
- return Hello;
3881
- });
3922
+ const Hello = () => {
3923
+ return <div>Hi!</div>;
3924
+ };
3925
+ $RefreshReg$(Hello, 'Hello');
3926
+ const Component = Hello;
3927
+ ReactDOM.render(<Component />, container);
3928
3929
expect(onCommitFiberRoot).toHaveBeenCalled();
3930
}