@samitouri / QOS-React-2 / commits / 3499c343ab

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(