Apply #20778 to new fork, too (#20782)
* Apply #20778 to new fork, too * Fix tests that use runWithPriority Where possible, I tried to rewrite in terms of an idiomatic API. For DOM tests, we should be dispatching an event with the desired priority level. For Idle updates (very unstable feature), probably need an unstable API like ReactDOM.unstable_IdleUpdates. Some of these fixes are not great, but we can replace them once we've landed the more of our planned changes to the layering between Scheduler, the reconciler, and the renderer.
Andrew Clark committed
Feb 10, 2021 at 01:21 UTC
3499c343ab48968192aaefe295dcf4185b3a4f99
17 files changed
+197
-197
packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
+19
-6
@@ -92,6 +92,21 @@ function dispatchClickEvent(target) {
92
return target.dispatchEvent(mouseOutEvent);
93
}
94
95
+// TODO: There's currently no React DOM API to opt into Idle priority updates,
96
+// and there's no native DOM event that maps to idle priority, so this is a
97
+// temporary workaround. Need something like ReactDOM.unstable_IdleUpdates.
98
+function TODO_scheduleIdleDOMSchedulerTask(fn) {
99
+ Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () => {
100
+ const prevEvent = window.event;
101
+ window.event = {type: 'message'};
102
+ try {
103
+ fn();
104
+ } finally {
105
+ window.event = prevEvent;
106
+ }
107
+ });
108
+}
109
+
110
describe('ReactDOMServerSelectiveHydration', () => {
111
beforeEach(() => {
112
jest.resetModuleRegistry();
@@ -889,12 +904,10 @@ describe('ReactDOMServerSelectiveHydration', () => {
904
expect(Scheduler).toFlushAndYieldThrough(['App', 'Commit']);
905
906
// Render an update at Idle priority that needs to update A.
892
- Scheduler.unstable_runWithPriority(
893
- Scheduler.unstable_IdlePriority,
894
- () => {
895
- root.render(<App a="AA" />);
896
- },
897
- );
907
+
908
+ TODO_scheduleIdleDOMSchedulerTask(() => {
909
+ root.render(<App a="AA" />);
910
+ });
911
912
// Start rendering. This will force the first boundary to hydrate
913
// by scheduling it at one higher pri than Idle.
packages/react-noop-renderer/src/ReactNoop.js
+1
@@ -41,6 +41,7 @@ export const {
41
deferredUpdates,
42
unbatchedUpdates,
43
discreteUpdates,
44
+ idleUpdates,
45
flushDiscreteUpdates,
46
flushSync,
47
flushPassiveEffects,
packages/react-noop-renderer/src/ReactNoopPersistent.js
+1
@@ -41,6 +41,7 @@ export const {
41
deferredUpdates,
42
unbatchedUpdates,
43
discreteUpdates,
44
+ idleUpdates,
45
flushDiscreteUpdates,
46
flushSync,
47
flushPassiveEffects,
packages/react-noop-renderer/src/createReactNoop.js
+21
-1
@@ -27,6 +27,7 @@ import {
27
LegacyRoot,
28
} from 'react-reconciler/src/ReactRootTags';
29
30
+import {enableNativeEventPriorityInference} from 'shared/ReactFeatureFlags';
31
import ReactSharedInternals from 'shared/ReactSharedInternals';
32
import enqueueTask from 'shared/enqueueTask';
33
const {IsSomeRendererActing} = ReactSharedInternals;
@@ -392,7 +393,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
393
resetAfterCommit(): void {},
394
395
getCurrentEventPriority() {
395
- return NoopRenderer.DefaultEventPriority;
396
+ return currentEventPriority;
397
},
398
399
now: Scheduler.unstable_now,
@@ -587,6 +588,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
588
const roots = new Map();
589
const DEFAULT_ROOT_ID = '<default>';
590
591
+ let currentEventPriority = NoopRenderer.DefaultEventPriority;
592
+
593
function childToJSX(child, text) {
594
if (text !== null) {
595
return text;
@@ -925,6 +928,23 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
928
929
discreteUpdates: NoopRenderer.discreteUpdates,
930
931
+ idleUpdates<T>(fn: () => T): T {
932
+ if (enableNativeEventPriorityInference) {
933
+ const prevEventPriority = currentEventPriority;
934
+ currentEventPriority = NoopRenderer.IdleEventPriority;
935
+ try {
936
+ fn();
937
+ } finally {
938
+ currentEventPriority = prevEventPriority;
939
+ }
940
+ } else {
941
+ return Scheduler.unstable_runWithPriority(
942
+ Scheduler.unstable_IdlePriority,
943
+ fn,
944
+ );
945
+ }
946
+ },
947
+
948
flushDiscreteUpdates: NoopRenderer.flushDiscreteUpdates,
949
950
flushSync(fn: () => mixed) {
packages/react-reconciler/src/ReactFiberLane.new.js
+2
-1
@@ -70,7 +70,7 @@ const RetryLanePriority: LanePriority = 5;
70
const SelectiveHydrationLanePriority: LanePriority = 4;
71
72
const IdleHydrationLanePriority: LanePriority = 3;
73
-const IdleLanePriority: LanePriority = 2;
73
+export const IdleLanePriority: LanePriority = 2;
74
75
const OffscreenLanePriority: LanePriority = 1;
76
@@ -275,6 +275,7 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
275
276
// Check if any work has expired.
277
if (expiredLanes !== NoLanes) {
278
+ // TODO: Should entangle with SyncLane
279
nextLanes = expiredLanes;
280
nextLanePriority = return_highestLanePriority = SyncLanePriority;
281
} else {
packages/react-reconciler/src/ReactFiberLane.old.js
+2
-1
@@ -70,7 +70,7 @@ const RetryLanePriority: LanePriority = 5;
70
const SelectiveHydrationLanePriority: LanePriority = 4;
71
72
const IdleHydrationLanePriority: LanePriority = 3;
73
-const IdleLanePriority: LanePriority = 2;
73
+export const IdleLanePriority: LanePriority = 2;
74
75
const OffscreenLanePriority: LanePriority = 1;
76
@@ -275,6 +275,7 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
275
276
// Check if any work has expired.
277
if (expiredLanes !== NoLanes) {
278
+ // TODO: Should entangle with SyncLane
279
nextLanes = expiredLanes;
280
nextLanePriority = return_highestLanePriority = SyncLanePriority;
281
} else {
packages/react-reconciler/src/ReactFiberReconciler.js
+5
@@ -55,6 +55,7 @@ import {
55
DefaultEventPriority as DefaultEventPriority_old,
56
DiscreteEventPriority as DiscreteEventPriority_old,
57
ContinuousEventPriority as ContinuousEventPriority_old,
58
+ IdleEventPriority as IdleEventPriority_old,
59
} from './ReactFiberReconciler.old';
60
61
import {
@@ -98,6 +99,7 @@ import {
99
DefaultEventPriority as DefaultEventPriority_new,
100
DiscreteEventPriority as DiscreteEventPriority_new,
101
ContinuousEventPriority as ContinuousEventPriority_new,
102
+ IdleEventPriority as IdleEventPriority_new,
103
} from './ReactFiberReconciler.new';
104
105
export const createContainer = enableNewReconciler
@@ -183,6 +185,9 @@ export const DiscreteEventPriority = enableNewReconciler
185
export const ContinuousEventPriority = enableNewReconciler
186
? ContinuousEventPriority_new
187
: ContinuousEventPriority_old;
188
+export const IdleEventPriority = enableNewReconciler
189
+ ? IdleEventPriority_new
190
+ : IdleEventPriority_old;
191
192
//TODO: "psuedo" is spelled "pseudo"
193
export const createHasPsuedoClassSelector = enableNewReconciler
packages/react-reconciler/src/ReactFiberReconciler.new.js
+1
@@ -101,6 +101,7 @@ export {
101
InputDiscreteLanePriority as DiscreteEventPriority,
102
InputContinuousLanePriority as ContinuousEventPriority,
103
DefaultLanePriority as DefaultEventPriority,
104
+ IdleLanePriority as IdleEventPriority,
105
} from './ReactFiberLane.new';
106
107
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
packages/react-reconciler/src/ReactFiberReconciler.old.js
+1
@@ -101,6 +101,7 @@ export {
101
InputDiscreteLanePriority as DiscreteEventPriority,
102
InputContinuousLanePriority as ContinuousEventPriority,
103
DefaultLanePriority as DefaultEventPriority,
104
+ IdleLanePriority as IdleEventPriority,
105
} from './ReactFiberLane.old';
106
107
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+1
-9
@@ -465,15 +465,7 @@ export function requestUpdateLane(fiber: Fiber): Lane {
465
} else {
466
if (enableNativeEventPriorityInference) {
467
const eventLanePriority = getCurrentEventPriority();
468
- if (eventLanePriority === DefaultLanePriority) {
469
- // TODO: move this case into the ReactDOM host config.
470
- const schedulerLanePriority = schedulerPriorityToLanePriority(
471
- schedulerPriority,
472
- );
473
- lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
474
- } else {
475
- lane = findUpdateLane(eventLanePriority, currentEventWipLanes);
476
- }
468
+ lane = findUpdateLane(eventLanePriority, currentEventWipLanes);
469
} else {
470
const schedulerLanePriority = schedulerPriorityToLanePriority(
471
schedulerPriority,
packages/react-reconciler/src/__tests__/ReactExpiration-test.js
+50
-107
@@ -403,23 +403,23 @@ describe('ReactExpiration', () => {
403
expect(ReactNoop).toMatchRenderedOutput('Hi');
404
});
405
406
- it('prevents starvation by high priority updates', async () => {
406
+ it('prevents starvation by sync updates', async () => {
407
const {useState} = React;
408
409
- let updateHighPri;
409
+ let updateSyncPri;
410
let updateNormalPri;
411
function App() {
412
const [highPri, setHighPri] = useState(0);
413
const [normalPri, setNormalPri] = useState(0);
414
- updateHighPri = () =>
415
- Scheduler.unstable_runWithPriority(
416
- Scheduler.unstable_UserBlockingPriority,
417
- () => setHighPri(n => n + 1),
418
- );
414
+ updateSyncPri = () => {
415
+ ReactNoop.flushSync(() => {
416
+ setHighPri(n => n + 1);
417
+ });
418
+ };
419
updateNormalPri = () => setNormalPri(n => n + 1);
420
return (
421
<>
422
- <Text text={'High pri: ' + highPri} />
422
+ <Text text={'Sync pri: ' + highPri} />
423
{', '}
424
<Text text={'Normal pri: ' + normalPri} />
425
</>
@@ -430,29 +430,29 @@ describe('ReactExpiration', () => {
430
await ReactNoop.act(async () => {
431
root.render(<App />);
432
});
433
- expect(Scheduler).toHaveYielded(['High pri: 0', 'Normal pri: 0']);
434
- expect(root).toMatchRenderedOutput('High pri: 0, Normal pri: 0');
433
+ expect(Scheduler).toHaveYielded(['Sync pri: 0', 'Normal pri: 0']);
434
+ expect(root).toMatchRenderedOutput('Sync pri: 0, Normal pri: 0');
435
436
// First demonstrate what happens when there's no starvation
437
await ReactNoop.act(async () => {
438
updateNormalPri();
439
- expect(Scheduler).toFlushAndYieldThrough(['High pri: 0']);
440
- updateHighPri();
439
+ expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 0']);
440
+ updateSyncPri();
441
});
442
expect(Scheduler).toHaveYielded([
443
// Interrupt high pri update to render sync update
444
- 'High pri: 1',
444
+ 'Sync pri: 1',
445
'Normal pri: 0',
446
// Now render normal pri
447
- 'High pri: 1',
447
+ 'Sync pri: 1',
448
'Normal pri: 1',
449
]);
450
- expect(root).toMatchRenderedOutput('High pri: 1, Normal pri: 1');
450
+ expect(root).toMatchRenderedOutput('Sync pri: 1, Normal pri: 1');
451
452
// Do the same thing, but starve the first update
453
await ReactNoop.act(async () => {
454
updateNormalPri();
455
- expect(Scheduler).toFlushAndYieldThrough(['High pri: 1']);
455
+ expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 1']);
456
457
// This time, a lot of time has elapsed since the normal pri update
458
// started rendering. (This should advance time by some number that's
@@ -461,86 +461,16 @@ describe('ReactExpiration', () => {
461
Scheduler.unstable_advanceTime(10000);
462
463
// So when we get a high pri update, we shouldn't interrupt
464
- updateHighPri();
464
+ updateSyncPri();
465
});
466
expect(Scheduler).toHaveYielded([
467
// Finish normal pri update
468
'Normal pri: 2',
469
// Then do high pri update
470
- 'High pri: 2',
471
- 'Normal pri: 2',
472
- ]);
473
- expect(root).toMatchRenderedOutput('High pri: 2, Normal pri: 2');
474
- });
475
-
476
- it('prevents starvation by sync updates', async () => {
477
- const {useState} = React;
478
-
479
- let updateSyncPri;
480
- let updateHighPri;
481
- function App() {
482
- const [syncPri, setSyncPri] = useState(0);
483
- const [highPri, setHighPri] = useState(0);
484
- updateSyncPri = () => ReactNoop.flushSync(() => setSyncPri(n => n + 1));
485
- updateHighPri = () =>
486
- Scheduler.unstable_runWithPriority(
487
- Scheduler.unstable_UserBlockingPriority,
488
- () => setHighPri(n => n + 1),
489
- );
490
- return (
491
- <>
492
- <Text text={'Sync pri: ' + syncPri} />
493
- {', '}
494
- <Text text={'High pri: ' + highPri} />
495
- </>
496
- );
497
- }
498
-
499
- const root = ReactNoop.createRoot();
500
- await ReactNoop.act(async () => {
501
- root.render(<App />);
502
- });
503
- expect(Scheduler).toHaveYielded(['Sync pri: 0', 'High pri: 0']);
504
- expect(root).toMatchRenderedOutput('Sync pri: 0, High pri: 0');
505
-
506
- // First demonstrate what happens when there's no starvation
507
- await ReactNoop.act(async () => {
508
- updateHighPri();
509
- expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 0']);
510
- updateSyncPri();
511
- });
512
- expect(Scheduler).toHaveYielded([
513
- // Interrupt high pri update to render sync update
514
- 'Sync pri: 1',
515
- 'High pri: 0',
516
- // Now render high pri
517
- 'Sync pri: 1',
518
- 'High pri: 1',
519
- ]);
520
- expect(root).toMatchRenderedOutput('Sync pri: 1, High pri: 1');
521
-
522
- // Do the same thing, but starve the first update
523
- await ReactNoop.act(async () => {
524
- updateHighPri();
525
- expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 1']);
526
-
527
- // This time, a lot of time has elapsed since the high pri update started
528
- // rendering. (This should advance time by some number that's definitely
529
- // bigger than the constant heuristic we use to detect starvation of user
530
- // interactions, but not as high as the onse used for normal pri updates.)
531
- Scheduler.unstable_advanceTime(1500);
532
-
533
- // So when we get a sync update, we shouldn't interrupt
534
- updateSyncPri();
535
- });
536
- expect(Scheduler).toHaveYielded([
537
- // Finish high pri update
538
- 'High pri: 2',
539
- // Then do sync update
470
'Sync pri: 2',
541
- 'High pri: 2',
471
+ 'Normal pri: 2',
472
]);
543
- expect(root).toMatchRenderedOutput('Sync pri: 2, High pri: 2');
473
+ expect(root).toMatchRenderedOutput('Sync pri: 2, Normal pri: 2');
474
});
475
476
it('idle work never expires', async () => {
@@ -553,10 +483,9 @@ describe('ReactExpiration', () => {
483
const [highPri, setIdlePri] = useState(0);
484
updateSyncPri = () => ReactNoop.flushSync(() => setSyncPri(n => n + 1));
485
updateIdlePri = () =>
556
- Scheduler.unstable_runWithPriority(
557
- Scheduler.unstable_IdlePriority,
558
- () => setIdlePri(n => n + 1),
559
- );
486
+ ReactNoop.idleUpdates(() => {
487
+ setIdlePri(n => n + 1);
488
+ });
489
return (
490
<>
491
<Text text={'Sync pri: ' + syncPri} />
@@ -695,11 +624,11 @@ describe('ReactExpiration', () => {
624
function App() {
625
const [highPri, setHighPri] = useState(0);
626
const [normalPri, setNormalPri] = useState(0);
698
- updateHighPri = () =>
699
- Scheduler.unstable_runWithPriority(
700
- Scheduler.unstable_UserBlockingPriority,
701
- () => setHighPri(n => n + 1),
702
- );
627
+ updateHighPri = () => {
628
+ ReactNoop.flushSync(() => {
629
+ setHighPri(n => n + 1);
630
+ });
631
+ };
632
updateNormalPri = () => setNormalPri(n => n + 1);
633
return (
634
<>
@@ -735,20 +664,34 @@ describe('ReactExpiration', () => {
664
expect(Scheduler).toFlushAndYieldThrough(['Normal pri: 1']);
665
// More time goes by. This expires both of the updates just scheduled.
666
Scheduler.unstable_advanceTime(10000);
667
+ expect(Scheduler).toHaveYielded([]);
668
669
// Attempt to interrupt with a high pri update.
670
updateHighPri();
671
672
// Both normal pri updates should have expired.
743
- expect(Scheduler).toFlushExpired([
744
- 'Sibling',
745
- // Notice that the high pri update didn't flush yet. Expiring one lane
746
- // doesn't affect other lanes. (Unless they are intentionally entangled,
747
- // like we do for overlapping transitions that affect the same state.)
748
- 'High pri: 0',
749
- 'Normal pri: 2',
750
- 'Sibling',
751
- ]);
673
+ if (gate(flags => flags.FIXME)) {
674
+ // The sync update and the expired normal pri updates render in a
675
+ // single batch.
676
+ expect(Scheduler).toHaveYielded([
677
+ 'Sibling',
678
+ 'High pri: 1',
679
+ 'Normal pri: 2',
680
+ 'Sibling',
681
+ ]);
682
+ } else {
683
+ expect(Scheduler).toHaveYielded([
684
+ 'Sibling',
685
+ 'High pri: 0',
686
+ 'Normal pri: 2',
687
+ 'Sibling',
688
+ // TODO: This is the sync update. We should have rendered it in the same
689
+ // batch as the expired update.
690
+ 'High pri: 1',
691
+ 'Normal pri: 2',
692
+ 'Sibling',
693
+ ]);
694
+ }
695
});
696
});
697
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+4
-5
@@ -1351,11 +1351,10 @@ describe('ReactHooksWithNoopRenderer', () => {
1351
expect(Scheduler).toFlushAndYieldThrough(['Child one render']);
1352
1353
// Schedule unmount for the parent that unmounts children with pending update.
1354
- Scheduler.unstable_runWithPriority(
1355
- Scheduler.unstable_UserBlockingPriority,
1356
- () => setParentState(false),
1357
- );
1358
- expect(Scheduler).toFlushAndYieldThrough([
1354
+ ReactNoop.flushSync(() => {
1355
+ setParentState(false);
1356
+ });
1357
+ expect(Scheduler).toHaveYielded([
1358
'Parent false render',
1359
'Parent false commit',
1360
]);
packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
+24
@@ -79,6 +79,10 @@ describe('ReactSchedulerIntegration', () => {
79
expect(Scheduler).toHaveYielded(['Priority: Immediate']);
80
});
81
82
+ // TODO: Figure out what to do with these tests. I don't think most of them
83
+ // make sense once we decouple Scheduler from React. Perhaps need similar
84
+ // tests for React DOM.
85
+ // @gate !enableNativeEventPriorityInference
86
it('has correct priority during rendering', () => {
87
function ReadPriority() {
88
Scheduler.unstable_yieldValue(
@@ -100,6 +104,10 @@ describe('ReactSchedulerIntegration', () => {
104
expect(Scheduler).toFlushAndYield(['Priority: Idle']);
105
});
106
107
+ // TODO: Figure out what to do with these tests. I don't think most of them
108
+ // make sense once we decouple Scheduler from React. Perhaps need similar
109
+ // tests for React DOM.
110
+ // @gate !enableNativeEventPriorityInference
111
it('has correct priority when continuing a render after yielding', () => {
112
function ReadPriority() {
113
Scheduler.unstable_yieldValue(
@@ -152,6 +160,10 @@ describe('ReactSchedulerIntegration', () => {
160
]);
161
});
162
163
+ // TODO: Figure out what to do with these tests. I don't think most of them
164
+ // make sense once we decouple Scheduler from React. Perhaps need similar
165
+ // tests for React DOM.
166
+ // @gate !enableNativeEventPriorityInference
167
it('passive effects never have higher than normal priority', async () => {
168
const {useEffect} = React;
169
function ReadPriority({step}) {
@@ -205,6 +217,10 @@ describe('ReactSchedulerIntegration', () => {
217
]);
218
});
219
220
+ // TODO: Figure out what to do with these tests. I don't think most of them
221
+ // make sense once we decouple Scheduler from React. Perhaps need similar
222
+ // tests for React DOM.
223
+ // @gate !enableNativeEventPriorityInference
224
it('passive effects have correct priority even if they are flushed early', async () => {
225
const {useEffect} = React;
226
function ReadPriority({step}) {
@@ -233,6 +249,10 @@ describe('ReactSchedulerIntegration', () => {
249
]);
250
});
251
252
+ // TODO: Figure out what to do with these tests. I don't think most of them
253
+ // make sense once we decouple Scheduler from React. Perhaps need similar
254
+ // tests for React DOM.
255
+ // @gate !enableNativeEventPriorityInference
256
it('passive effect clean-up functions have correct priority even when component is deleted', async () => {
257
const {useEffect} = React;
258
function ReadPriority({step}) {
@@ -322,6 +342,10 @@ describe('ReactSchedulerIntegration', () => {
342
]);
343
});
344
345
+ // TODO: Figure out what to do with these tests. I don't think most of them
346
+ // make sense once we decouple Scheduler from React. Perhaps need similar
347
+ // tests for React DOM.
348
+ // @gate !enableNativeEventPriorityInference
349
it('after completing a level of work, infers priority of the next batch based on its expiration time', () => {
350
function App({label}) {
351
Scheduler.unstable_yieldValue(
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+10
-21
@@ -2248,9 +2248,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2248
expect(ReactNoop.getChildren()).toEqual([]);
2249
2250
// Schedule an update at idle pri.
2251
- Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () =>
2252
- ReactNoop.render(<Foo renderContent={2} />),
2253
- );
2251
+ ReactNoop.idleUpdates(() => ReactNoop.render(<Foo renderContent={2} />));
2252
// We won't even work on Idle priority.
2253
expect(Scheduler).toFlushAndYield([]);
2254
@@ -3018,12 +3016,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3016
setText('B');
3017
3018
await resolveText('C');
3021
- Scheduler.unstable_runWithPriority(
3022
- Scheduler.unstable_IdlePriority,
3023
- () => {
3024
- setText('C');
3025
- },
3026
- );
3019
+ ReactNoop.idleUpdates(() => {
3020
+ setText('C');
3021
+ });
3022
3023
expect(Scheduler).toFlushAndYield([
3024
// First we attempt the high pri update. It suspends.
@@ -3282,12 +3277,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3277
3278
// And another update at lower priority. This will unblock.
3279
await resolveText('E');
3285
- Scheduler.unstable_runWithPriority(
3286
- Scheduler.unstable_IdlePriority,
3287
- () => {
3288
- setText('E');
3289
- },
3290
- );
3280
+ ReactNoop.idleUpdates(() => {
3281
+ setText('E');
3282
+ });
3283
});
3284
// Even though the fragment fiber is not part of the return path, we should
3285
// be able to finish rendering.
@@ -3838,12 +3830,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3830
3831
await ReactNoop.act(async () => {
3832
setText('B');
3841
- Scheduler.unstable_runWithPriority(
3842
- Scheduler.unstable_IdlePriority,
3843
- () => {
3844
- setText('B');
3845
- },
3846
- );
3833
+ ReactNoop.idleUpdates(() => {
3834
+ setText('B');
3835
+ });
3836
// Suspend the first update. The second update doesn't run because it has
3837
// Idle priority.
3838
expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
+27
-35
@@ -529,7 +529,7 @@ describe('useMutableSource', () => {
529
530
// Changing values should schedule an update with React.
531
// Start working on this update but don't finish it.
532
- Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => {
532
+ ReactNoop.idleUpdates(() => {
533
source.value = 'two';
534
expect(Scheduler).toFlushAndYieldThrough(['a:two']);
535
});
@@ -538,29 +538,26 @@ describe('useMutableSource', () => {
538
539
// Force a higher priority render with a new config.
540
// This should signal that the snapshot is not safe and trigger a full re-render.
541
- Scheduler.unstable_runWithPriority(
542
- Scheduler.unstable_UserBlockingPriority,
543
- () => {
544
- ReactNoop.render(
545
- <>
546
- <Component
547
- label="a"
548
- getSnapshot={newGetSnapshot}
549
- mutableSource={mutableSource}
550
- subscribe={defaultSubscribe}
551
- />
552
- <Component
553
- label="b"
554
- getSnapshot={newGetSnapshot}
555
- mutableSource={mutableSource}
556
- subscribe={defaultSubscribe}
557
- />
558
- </>,
559
- () => Scheduler.unstable_yieldValue('Sync effect'),
560
- );
561
- },
562
- );
563
- expect(Scheduler).toFlushAndYieldThrough([
541
+ ReactNoop.flushSync(() => {
542
+ ReactNoop.render(
543
+ <>
544
+ <Component
545
+ label="a"
546
+ getSnapshot={newGetSnapshot}
547
+ mutableSource={mutableSource}
548
+ subscribe={defaultSubscribe}
549
+ />
550
+ <Component
551
+ label="b"
552
+ getSnapshot={newGetSnapshot}
553
+ mutableSource={mutableSource}
554
+ subscribe={defaultSubscribe}
555
+ />
556
+ </>,
557
+ () => Scheduler.unstable_yieldValue('Sync effect'),
558
+ );
559
+ });
560
+ expect(Scheduler).toHaveYielded([
561
'a:new:two',
562
'b:new:two',
563
'Sync effect',
@@ -596,7 +593,7 @@ describe('useMutableSource', () => {
593
594
// Changing values should schedule an update with React.
595
// Start working on this update but don't finish it.
599
- Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => {
596
+ ReactNoop.idleUpdates(() => {
597
source.value = 'two';
598
expect(Scheduler).toFlushAndYieldThrough(['a:two']);
599
});
@@ -793,19 +790,14 @@ describe('useMutableSource', () => {
790
ReactNoop.flushPassiveEffects();
791
792
// Change the source (and schedule an update).
796
- Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => {
797
- source.value = 'two';
798
- });
793
+ source.value = 'two';
794
795
// Schedule a higher priority update that changes getSnapshot.
801
- Scheduler.unstable_runWithPriority(
802
- Scheduler.unstable_UserBlockingPriority,
803
- () => {
804
- updateGetSnapshot(() => newGetSnapshot);
805
- },
806
- );
796
+ ReactNoop.flushSync(() => {
797
+ updateGetSnapshot(() => newGetSnapshot);
798
+ });
799
808
- expect(Scheduler).toFlushAndYield(['only:new:two']);
800
+ expect(Scheduler).toHaveYielded(['only:new:two']);
801
});
802
});
803
packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js
+20
-4
@@ -31,6 +31,15 @@ describe('useMutableSourceHydration', () => {
31
useMutableSource = React.unstable_useMutableSource;
32
});
33
34
+ function dispatchAndSetCurrentEvent(el, event) {
35
+ try {
36
+ window.event = event;
37
+ el.dispatchEvent(event);
38
+ } finally {
39
+ window.event = undefined;
40
+ }
41
+ }
42
+
43
const defaultGetSnapshot = source => source.value;
44
const defaultSubscribe = (source, callback) => source.subscribe(callback);
45
@@ -332,6 +341,7 @@ describe('useMutableSourceHydration', () => {
341
});
342
343
// @gate experimental
344
+ // @gate enableNativeEventPriorityInference
345
it('should detect a tear during a higher priority interruption', () => {
346
const source = createSource('one');
347
const mutableSource = createMutableSource(source, param => param.version);
@@ -371,16 +381,22 @@ describe('useMutableSourceHydration', () => {
381
mutableSources: [mutableSource],
382
},
383
});
384
+
385
expect(() => {
386
act(() => {
387
root.render(<TestComponent flag={1} />);
388
expect(Scheduler).toFlushAndYieldThrough([1]);
389
390
// Render an update which will be higher priority than the hydration.
380
- Scheduler.unstable_runWithPriority(
381
- Scheduler.unstable_UserBlockingPriority,
382
- () => root.render(<TestComponent flag={2} />),
383
- );
391
+ // We can do this by scheduling the update inside a mouseover event.
392
+ const arbitraryElement = document.createElement('div');
393
+ const mouseOverEvent = document.createEvent('MouseEvents');
394
+ mouseOverEvent.initEvent('mouseover', true, true);
395
+ arbitraryElement.addEventListener('mouseover', () => {
396
+ root.render(<TestComponent flag={2} />);
397
+ });
398
+ dispatchAndSetCurrentEvent(arbitraryElement, mouseOverEvent);
399
+
400
expect(Scheduler).toFlushAndYieldThrough([2]);
401
402
source.value = 'two';
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+8
-7
@@ -28,6 +28,7 @@ let onWorkStopped;
28
// This is hard coded directly to avoid needing to import, and
29
// we'll remove this as we replace runWithPriority with React APIs.
30
const IdleLanePriority = 2;
31
+const InputContinuousPriority = 10;
32
33
function loadModules() {
34
ReactFeatureFlags = require('shared/ReactFeatureFlags');
@@ -427,6 +428,7 @@ describe('ReactDOMTracing', () => {
428
});
429
430
// @gate experimental
431
+ // @gate enableNativeEventPriorityInference
432
it('should properly trace interactions when there is work of interleaved priorities', () => {
433
const Child = () => {
434
Scheduler.unstable_yieldValue('Child');
@@ -502,9 +504,8 @@ describe('ReactDOMTracing', () => {
504
let interaction = null;
505
SchedulerTracing.unstable_trace('update', 0, () => {
506
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
505
- Scheduler.unstable_runWithPriority(
506
- Scheduler.unstable_UserBlockingPriority,
507
- () => scheduleUpdateWithHidden(),
507
+ ReactDOM.unstable_runWithPriority(InputContinuousPriority, () =>
508
+ scheduleUpdateWithHidden(),
509
);
510
});
511
scheduleUpdate();
@@ -549,6 +550,7 @@ describe('ReactDOMTracing', () => {
550
});
551
552
// @gate experimental
553
+ // @gate enableNativeEventPriorityInference
554
it('should properly trace interactions through a multi-pass SuspenseList render', () => {
555
const SuspenseList = React.SuspenseList;
556
const Suspense = React.Suspense;
@@ -610,10 +612,9 @@ describe('ReactDOMTracing', () => {
612
// Schedule an unrelated low priority update that shouldn't be included
613
// in the previous interaction. This is meant to ensure that we don't
614
// rely on the whole tree completing to cover up bugs.
613
- Scheduler.unstable_runWithPriority(
614
- Scheduler.unstable_IdlePriority,
615
- () => root.render(<App />),
616
- );
615
+ ReactDOM.unstable_runWithPriority(IdleLanePriority, () => {
616
+ root.render(<App />);
617
+ });
618
619
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
620
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(