From 3499c343ab48968192aaefe295dcf4185b3a4f99 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 10 Feb 2021 01:21:46 -0600 Subject: [PATCH] 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. --- ...MServerSelectiveHydration-test.internal.js | 25 ++- packages/react-noop-renderer/src/ReactNoop.js | 1 + .../src/ReactNoopPersistent.js | 1 + .../src/createReactNoop.js | 22 ++- .../src/ReactFiberLane.new.js | 3 +- .../src/ReactFiberLane.old.js | 3 +- .../src/ReactFiberReconciler.js | 5 + .../src/ReactFiberReconciler.new.js | 1 + .../src/ReactFiberReconciler.old.js | 1 + .../src/ReactFiberWorkLoop.new.js | 10 +- .../src/__tests__/ReactExpiration-test.js | 157 ++++++------------ .../ReactHooksWithNoopRenderer-test.js | 9 +- .../ReactSchedulerIntegration-test.js | 24 +++ .../ReactSuspenseWithNoopRenderer-test.js | 31 ++-- .../useMutableSource-test.internal.js | 62 +++---- .../useMutableSourceHydration-test.js | 24 ++- .../ReactDOMTracing-test.internal.js | 15 +- 17 files changed, 197 insertions(+), 197 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index 6b543805ea102..19cf6065e6b9d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -92,6 +92,21 @@ function dispatchClickEvent(target) { return target.dispatchEvent(mouseOutEvent); } +// TODO: There's currently no React DOM API to opt into Idle priority updates, +// and there's no native DOM event that maps to idle priority, so this is a +// temporary workaround. Need something like ReactDOM.unstable_IdleUpdates. +function TODO_scheduleIdleDOMSchedulerTask(fn) { + Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () => { + const prevEvent = window.event; + window.event = {type: 'message'}; + try { + fn(); + } finally { + window.event = prevEvent; + } + }); +} + describe('ReactDOMServerSelectiveHydration', () => { beforeEach(() => { jest.resetModuleRegistry(); @@ -889,12 +904,10 @@ describe('ReactDOMServerSelectiveHydration', () => { expect(Scheduler).toFlushAndYieldThrough(['App', 'Commit']); // Render an update at Idle priority that needs to update A. - Scheduler.unstable_runWithPriority( - Scheduler.unstable_IdlePriority, - () => { - root.render(); - }, - ); + + TODO_scheduleIdleDOMSchedulerTask(() => { + root.render(); + }); // Start rendering. This will force the first boundary to hydrate // by scheduling it at one higher pri than Idle. diff --git a/packages/react-noop-renderer/src/ReactNoop.js b/packages/react-noop-renderer/src/ReactNoop.js index 071501e576b6e..8305dd6d8641f 100644 --- a/packages/react-noop-renderer/src/ReactNoop.js +++ b/packages/react-noop-renderer/src/ReactNoop.js @@ -41,6 +41,7 @@ export const { deferredUpdates, unbatchedUpdates, discreteUpdates, + idleUpdates, flushDiscreteUpdates, flushSync, flushPassiveEffects, diff --git a/packages/react-noop-renderer/src/ReactNoopPersistent.js b/packages/react-noop-renderer/src/ReactNoopPersistent.js index 845c8d3acc1a3..c4a73cdfb81b4 100644 --- a/packages/react-noop-renderer/src/ReactNoopPersistent.js +++ b/packages/react-noop-renderer/src/ReactNoopPersistent.js @@ -41,6 +41,7 @@ export const { deferredUpdates, unbatchedUpdates, discreteUpdates, + idleUpdates, flushDiscreteUpdates, flushSync, flushPassiveEffects, diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 34967ca3625ed..2aa0043d9e24a 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -27,6 +27,7 @@ import { LegacyRoot, } from 'react-reconciler/src/ReactRootTags'; +import {enableNativeEventPriorityInference} from 'shared/ReactFeatureFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import enqueueTask from 'shared/enqueueTask'; const {IsSomeRendererActing} = ReactSharedInternals; @@ -392,7 +393,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { resetAfterCommit(): void {}, getCurrentEventPriority() { - return NoopRenderer.DefaultEventPriority; + return currentEventPriority; }, now: Scheduler.unstable_now, @@ -587,6 +588,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { const roots = new Map(); const DEFAULT_ROOT_ID = ''; + let currentEventPriority = NoopRenderer.DefaultEventPriority; + function childToJSX(child, text) { if (text !== null) { return text; @@ -925,6 +928,23 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { discreteUpdates: NoopRenderer.discreteUpdates, + idleUpdates(fn: () => T): T { + if (enableNativeEventPriorityInference) { + const prevEventPriority = currentEventPriority; + currentEventPriority = NoopRenderer.IdleEventPriority; + try { + fn(); + } finally { + currentEventPriority = prevEventPriority; + } + } else { + return Scheduler.unstable_runWithPriority( + Scheduler.unstable_IdlePriority, + fn, + ); + } + }, + flushDiscreteUpdates: NoopRenderer.flushDiscreteUpdates, flushSync(fn: () => mixed) { diff --git a/packages/react-reconciler/src/ReactFiberLane.new.js b/packages/react-reconciler/src/ReactFiberLane.new.js index 315541115c37e..adb67b6e77dfe 100644 --- a/packages/react-reconciler/src/ReactFiberLane.new.js +++ b/packages/react-reconciler/src/ReactFiberLane.new.js @@ -70,7 +70,7 @@ const RetryLanePriority: LanePriority = 5; const SelectiveHydrationLanePriority: LanePriority = 4; const IdleHydrationLanePriority: LanePriority = 3; -const IdleLanePriority: LanePriority = 2; +export const IdleLanePriority: LanePriority = 2; const OffscreenLanePriority: LanePriority = 1; @@ -275,6 +275,7 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes { // Check if any work has expired. if (expiredLanes !== NoLanes) { + // TODO: Should entangle with SyncLane nextLanes = expiredLanes; nextLanePriority = return_highestLanePriority = SyncLanePriority; } else { diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js index e34c8ac0514fc..ea02f3102c902 100644 --- a/packages/react-reconciler/src/ReactFiberLane.old.js +++ b/packages/react-reconciler/src/ReactFiberLane.old.js @@ -70,7 +70,7 @@ const RetryLanePriority: LanePriority = 5; const SelectiveHydrationLanePriority: LanePriority = 4; const IdleHydrationLanePriority: LanePriority = 3; -const IdleLanePriority: LanePriority = 2; +export const IdleLanePriority: LanePriority = 2; const OffscreenLanePriority: LanePriority = 1; @@ -275,6 +275,7 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes { // Check if any work has expired. if (expiredLanes !== NoLanes) { + // TODO: Should entangle with SyncLane nextLanes = expiredLanes; nextLanePriority = return_highestLanePriority = SyncLanePriority; } else { diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 7493b73636a34..29b52c5a00092 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -55,6 +55,7 @@ import { DefaultEventPriority as DefaultEventPriority_old, DiscreteEventPriority as DiscreteEventPriority_old, ContinuousEventPriority as ContinuousEventPriority_old, + IdleEventPriority as IdleEventPriority_old, } from './ReactFiberReconciler.old'; import { @@ -98,6 +99,7 @@ import { DefaultEventPriority as DefaultEventPriority_new, DiscreteEventPriority as DiscreteEventPriority_new, ContinuousEventPriority as ContinuousEventPriority_new, + IdleEventPriority as IdleEventPriority_new, } from './ReactFiberReconciler.new'; export const createContainer = enableNewReconciler @@ -183,6 +185,9 @@ export const DiscreteEventPriority = enableNewReconciler export const ContinuousEventPriority = enableNewReconciler ? ContinuousEventPriority_new : ContinuousEventPriority_old; +export const IdleEventPriority = enableNewReconciler + ? IdleEventPriority_new + : IdleEventPriority_old; //TODO: "psuedo" is spelled "pseudo" export const createHasPsuedoClassSelector = enableNewReconciler diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js index 039bb159fd7a9..be5ee15970dc7 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.new.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js @@ -101,6 +101,7 @@ export { InputDiscreteLanePriority as DiscreteEventPriority, InputContinuousLanePriority as ContinuousEventPriority, DefaultLanePriority as DefaultEventPriority, + IdleLanePriority as IdleEventPriority, } from './ReactFiberLane.new'; export {registerMutableSourceForHydration} from './ReactMutableSource.new'; diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index 012b4c7057350..9edb4e5031206 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.old.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js @@ -101,6 +101,7 @@ export { InputDiscreteLanePriority as DiscreteEventPriority, InputContinuousLanePriority as ContinuousEventPriority, DefaultLanePriority as DefaultEventPriority, + IdleLanePriority as IdleEventPriority, } from './ReactFiberLane.old'; export {registerMutableSourceForHydration} from './ReactMutableSource.new'; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index e65e1dad45d6e..234ca61fe93f1 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -465,15 +465,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { } else { if (enableNativeEventPriorityInference) { const eventLanePriority = getCurrentEventPriority(); - if (eventLanePriority === DefaultLanePriority) { - // TODO: move this case into the ReactDOM host config. - const schedulerLanePriority = schedulerPriorityToLanePriority( - schedulerPriority, - ); - lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes); - } else { - lane = findUpdateLane(eventLanePriority, currentEventWipLanes); - } + lane = findUpdateLane(eventLanePriority, currentEventWipLanes); } else { const schedulerLanePriority = schedulerPriorityToLanePriority( schedulerPriority, diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js index 590761dbc8cd7..017c712bd814b 100644 --- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js +++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js @@ -403,23 +403,23 @@ describe('ReactExpiration', () => { expect(ReactNoop).toMatchRenderedOutput('Hi'); }); - it('prevents starvation by high priority updates', async () => { + it('prevents starvation by sync updates', async () => { const {useState} = React; - let updateHighPri; + let updateSyncPri; let updateNormalPri; function App() { const [highPri, setHighPri] = useState(0); const [normalPri, setNormalPri] = useState(0); - updateHighPri = () => - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => setHighPri(n => n + 1), - ); + updateSyncPri = () => { + ReactNoop.flushSync(() => { + setHighPri(n => n + 1); + }); + }; updateNormalPri = () => setNormalPri(n => n + 1); return ( <> - + {', '} @@ -430,29 +430,29 @@ describe('ReactExpiration', () => { await ReactNoop.act(async () => { root.render(); }); - expect(Scheduler).toHaveYielded(['High pri: 0', 'Normal pri: 0']); - expect(root).toMatchRenderedOutput('High pri: 0, Normal pri: 0'); + expect(Scheduler).toHaveYielded(['Sync pri: 0', 'Normal pri: 0']); + expect(root).toMatchRenderedOutput('Sync pri: 0, Normal pri: 0'); // First demonstrate what happens when there's no starvation await ReactNoop.act(async () => { updateNormalPri(); - expect(Scheduler).toFlushAndYieldThrough(['High pri: 0']); - updateHighPri(); + expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 0']); + updateSyncPri(); }); expect(Scheduler).toHaveYielded([ // Interrupt high pri update to render sync update - 'High pri: 1', + 'Sync pri: 1', 'Normal pri: 0', // Now render normal pri - 'High pri: 1', + 'Sync pri: 1', 'Normal pri: 1', ]); - expect(root).toMatchRenderedOutput('High pri: 1, Normal pri: 1'); + expect(root).toMatchRenderedOutput('Sync pri: 1, Normal pri: 1'); // Do the same thing, but starve the first update await ReactNoop.act(async () => { updateNormalPri(); - expect(Scheduler).toFlushAndYieldThrough(['High pri: 1']); + expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 1']); // This time, a lot of time has elapsed since the normal pri update // started rendering. (This should advance time by some number that's @@ -461,86 +461,16 @@ describe('ReactExpiration', () => { Scheduler.unstable_advanceTime(10000); // So when we get a high pri update, we shouldn't interrupt - updateHighPri(); + updateSyncPri(); }); expect(Scheduler).toHaveYielded([ // Finish normal pri update 'Normal pri: 2', // Then do high pri update - 'High pri: 2', - 'Normal pri: 2', - ]); - expect(root).toMatchRenderedOutput('High pri: 2, Normal pri: 2'); - }); - - it('prevents starvation by sync updates', async () => { - const {useState} = React; - - let updateSyncPri; - let updateHighPri; - function App() { - const [syncPri, setSyncPri] = useState(0); - const [highPri, setHighPri] = useState(0); - updateSyncPri = () => ReactNoop.flushSync(() => setSyncPri(n => n + 1)); - updateHighPri = () => - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => setHighPri(n => n + 1), - ); - return ( - <> - - {', '} - - - ); - } - - const root = ReactNoop.createRoot(); - await ReactNoop.act(async () => { - root.render(); - }); - expect(Scheduler).toHaveYielded(['Sync pri: 0', 'High pri: 0']); - expect(root).toMatchRenderedOutput('Sync pri: 0, High pri: 0'); - - // First demonstrate what happens when there's no starvation - await ReactNoop.act(async () => { - updateHighPri(); - expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 0']); - updateSyncPri(); - }); - expect(Scheduler).toHaveYielded([ - // Interrupt high pri update to render sync update - 'Sync pri: 1', - 'High pri: 0', - // Now render high pri - 'Sync pri: 1', - 'High pri: 1', - ]); - expect(root).toMatchRenderedOutput('Sync pri: 1, High pri: 1'); - - // Do the same thing, but starve the first update - await ReactNoop.act(async () => { - updateHighPri(); - expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 1']); - - // This time, a lot of time has elapsed since the high pri update started - // rendering. (This should advance time by some number that's definitely - // bigger than the constant heuristic we use to detect starvation of user - // interactions, but not as high as the onse used for normal pri updates.) - Scheduler.unstable_advanceTime(1500); - - // So when we get a sync update, we shouldn't interrupt - updateSyncPri(); - }); - expect(Scheduler).toHaveYielded([ - // Finish high pri update - 'High pri: 2', - // Then do sync update 'Sync pri: 2', - 'High pri: 2', + 'Normal pri: 2', ]); - expect(root).toMatchRenderedOutput('Sync pri: 2, High pri: 2'); + expect(root).toMatchRenderedOutput('Sync pri: 2, Normal pri: 2'); }); it('idle work never expires', async () => { @@ -553,10 +483,9 @@ describe('ReactExpiration', () => { const [highPri, setIdlePri] = useState(0); updateSyncPri = () => ReactNoop.flushSync(() => setSyncPri(n => n + 1)); updateIdlePri = () => - Scheduler.unstable_runWithPriority( - Scheduler.unstable_IdlePriority, - () => setIdlePri(n => n + 1), - ); + ReactNoop.idleUpdates(() => { + setIdlePri(n => n + 1); + }); return ( <> @@ -695,11 +624,11 @@ describe('ReactExpiration', () => { function App() { const [highPri, setHighPri] = useState(0); const [normalPri, setNormalPri] = useState(0); - updateHighPri = () => - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => setHighPri(n => n + 1), - ); + updateHighPri = () => { + ReactNoop.flushSync(() => { + setHighPri(n => n + 1); + }); + }; updateNormalPri = () => setNormalPri(n => n + 1); return ( <> @@ -735,20 +664,34 @@ describe('ReactExpiration', () => { expect(Scheduler).toFlushAndYieldThrough(['Normal pri: 1']); // More time goes by. This expires both of the updates just scheduled. Scheduler.unstable_advanceTime(10000); + expect(Scheduler).toHaveYielded([]); // Attempt to interrupt with a high pri update. updateHighPri(); // Both normal pri updates should have expired. - expect(Scheduler).toFlushExpired([ - 'Sibling', - // Notice that the high pri update didn't flush yet. Expiring one lane - // doesn't affect other lanes. (Unless they are intentionally entangled, - // like we do for overlapping transitions that affect the same state.) - 'High pri: 0', - 'Normal pri: 2', - 'Sibling', - ]); + if (gate(flags => flags.FIXME)) { + // The sync update and the expired normal pri updates render in a + // single batch. + expect(Scheduler).toHaveYielded([ + 'Sibling', + 'High pri: 1', + 'Normal pri: 2', + 'Sibling', + ]); + } else { + expect(Scheduler).toHaveYielded([ + 'Sibling', + 'High pri: 0', + 'Normal pri: 2', + 'Sibling', + // TODO: This is the sync update. We should have rendered it in the same + // batch as the expired update. + 'High pri: 1', + 'Normal pri: 2', + 'Sibling', + ]); + } }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index cc86edb132209..bf5503feb4902 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -1351,11 +1351,10 @@ describe('ReactHooksWithNoopRenderer', () => { expect(Scheduler).toFlushAndYieldThrough(['Child one render']); // Schedule unmount for the parent that unmounts children with pending update. - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => setParentState(false), - ); - expect(Scheduler).toFlushAndYieldThrough([ + ReactNoop.flushSync(() => { + setParentState(false); + }); + expect(Scheduler).toHaveYielded([ 'Parent false render', 'Parent false commit', ]); diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js index 95c721b87a7c3..4292333293e82 100644 --- a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js @@ -79,6 +79,10 @@ describe('ReactSchedulerIntegration', () => { expect(Scheduler).toHaveYielded(['Priority: Immediate']); }); + // TODO: Figure out what to do with these tests. I don't think most of them + // make sense once we decouple Scheduler from React. Perhaps need similar + // tests for React DOM. + // @gate !enableNativeEventPriorityInference it('has correct priority during rendering', () => { function ReadPriority() { Scheduler.unstable_yieldValue( @@ -100,6 +104,10 @@ describe('ReactSchedulerIntegration', () => { expect(Scheduler).toFlushAndYield(['Priority: Idle']); }); + // TODO: Figure out what to do with these tests. I don't think most of them + // make sense once we decouple Scheduler from React. Perhaps need similar + // tests for React DOM. + // @gate !enableNativeEventPriorityInference it('has correct priority when continuing a render after yielding', () => { function ReadPriority() { Scheduler.unstable_yieldValue( @@ -152,6 +160,10 @@ describe('ReactSchedulerIntegration', () => { ]); }); + // TODO: Figure out what to do with these tests. I don't think most of them + // make sense once we decouple Scheduler from React. Perhaps need similar + // tests for React DOM. + // @gate !enableNativeEventPriorityInference it('passive effects never have higher than normal priority', async () => { const {useEffect} = React; function ReadPriority({step}) { @@ -205,6 +217,10 @@ describe('ReactSchedulerIntegration', () => { ]); }); + // TODO: Figure out what to do with these tests. I don't think most of them + // make sense once we decouple Scheduler from React. Perhaps need similar + // tests for React DOM. + // @gate !enableNativeEventPriorityInference it('passive effects have correct priority even if they are flushed early', async () => { const {useEffect} = React; function ReadPriority({step}) { @@ -233,6 +249,10 @@ describe('ReactSchedulerIntegration', () => { ]); }); + // TODO: Figure out what to do with these tests. I don't think most of them + // make sense once we decouple Scheduler from React. Perhaps need similar + // tests for React DOM. + // @gate !enableNativeEventPriorityInference it('passive effect clean-up functions have correct priority even when component is deleted', async () => { const {useEffect} = React; function ReadPriority({step}) { @@ -322,6 +342,10 @@ describe('ReactSchedulerIntegration', () => { ]); }); + // TODO: Figure out what to do with these tests. I don't think most of them + // make sense once we decouple Scheduler from React. Perhaps need similar + // tests for React DOM. + // @gate !enableNativeEventPriorityInference it('after completing a level of work, infers priority of the next batch based on its expiration time', () => { function App({label}) { Scheduler.unstable_yieldValue( diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js index 18042bb4bfb55..901be8f1a6ad8 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js @@ -2248,9 +2248,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(ReactNoop.getChildren()).toEqual([]); // Schedule an update at idle pri. - Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () => - ReactNoop.render(), - ); + ReactNoop.idleUpdates(() => ReactNoop.render()); // We won't even work on Idle priority. expect(Scheduler).toFlushAndYield([]); @@ -3018,12 +3016,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { setText('B'); await resolveText('C'); - Scheduler.unstable_runWithPriority( - Scheduler.unstable_IdlePriority, - () => { - setText('C'); - }, - ); + ReactNoop.idleUpdates(() => { + setText('C'); + }); expect(Scheduler).toFlushAndYield([ // First we attempt the high pri update. It suspends. @@ -3282,12 +3277,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { // And another update at lower priority. This will unblock. await resolveText('E'); - Scheduler.unstable_runWithPriority( - Scheduler.unstable_IdlePriority, - () => { - setText('E'); - }, - ); + ReactNoop.idleUpdates(() => { + setText('E'); + }); }); // Even though the fragment fiber is not part of the return path, we should // be able to finish rendering. @@ -3838,12 +3830,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { await ReactNoop.act(async () => { setText('B'); - Scheduler.unstable_runWithPriority( - Scheduler.unstable_IdlePriority, - () => { - setText('B'); - }, - ); + ReactNoop.idleUpdates(() => { + setText('B'); + }); // Suspend the first update. The second update doesn't run because it has // Idle priority. expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']); diff --git a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js index 1969c2f7adce4..db4214bfd0656 100644 --- a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js +++ b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js @@ -529,7 +529,7 @@ describe('useMutableSource', () => { // Changing values should schedule an update with React. // Start working on this update but don't finish it. - Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => { + ReactNoop.idleUpdates(() => { source.value = 'two'; expect(Scheduler).toFlushAndYieldThrough(['a:two']); }); @@ -538,29 +538,26 @@ describe('useMutableSource', () => { // Force a higher priority render with a new config. // This should signal that the snapshot is not safe and trigger a full re-render. - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => { - ReactNoop.render( - <> - - - , - () => Scheduler.unstable_yieldValue('Sync effect'), - ); - }, - ); - expect(Scheduler).toFlushAndYieldThrough([ + ReactNoop.flushSync(() => { + ReactNoop.render( + <> + + + , + () => Scheduler.unstable_yieldValue('Sync effect'), + ); + }); + expect(Scheduler).toHaveYielded([ 'a:new:two', 'b:new:two', 'Sync effect', @@ -596,7 +593,7 @@ describe('useMutableSource', () => { // Changing values should schedule an update with React. // Start working on this update but don't finish it. - Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => { + ReactNoop.idleUpdates(() => { source.value = 'two'; expect(Scheduler).toFlushAndYieldThrough(['a:two']); }); @@ -793,19 +790,14 @@ describe('useMutableSource', () => { ReactNoop.flushPassiveEffects(); // Change the source (and schedule an update). - Scheduler.unstable_runWithPriority(Scheduler.unstable_LowPriority, () => { - source.value = 'two'; - }); + source.value = 'two'; // Schedule a higher priority update that changes getSnapshot. - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => { - updateGetSnapshot(() => newGetSnapshot); - }, - ); + ReactNoop.flushSync(() => { + updateGetSnapshot(() => newGetSnapshot); + }); - expect(Scheduler).toFlushAndYield(['only:new:two']); + expect(Scheduler).toHaveYielded(['only:new:two']); }); }); diff --git a/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js b/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js index 2cd6881c94f73..6afa7588bbfb0 100644 --- a/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js +++ b/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js @@ -31,6 +31,15 @@ describe('useMutableSourceHydration', () => { useMutableSource = React.unstable_useMutableSource; }); + function dispatchAndSetCurrentEvent(el, event) { + try { + window.event = event; + el.dispatchEvent(event); + } finally { + window.event = undefined; + } + } + const defaultGetSnapshot = source => source.value; const defaultSubscribe = (source, callback) => source.subscribe(callback); @@ -332,6 +341,7 @@ describe('useMutableSourceHydration', () => { }); // @gate experimental + // @gate enableNativeEventPriorityInference it('should detect a tear during a higher priority interruption', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -371,16 +381,22 @@ describe('useMutableSourceHydration', () => { mutableSources: [mutableSource], }, }); + expect(() => { act(() => { root.render(); expect(Scheduler).toFlushAndYieldThrough([1]); // Render an update which will be higher priority than the hydration. - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => root.render(), - ); + // We can do this by scheduling the update inside a mouseover event. + const arbitraryElement = document.createElement('div'); + const mouseOverEvent = document.createEvent('MouseEvents'); + mouseOverEvent.initEvent('mouseover', true, true); + arbitraryElement.addEventListener('mouseover', () => { + root.render(); + }); + dispatchAndSetCurrentEvent(arbitraryElement, mouseOverEvent); + expect(Scheduler).toFlushAndYieldThrough([2]); source.value = 'two'; diff --git a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js index bd097fea76548..e2a76513f721c 100644 --- a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js +++ b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js @@ -28,6 +28,7 @@ let onWorkStopped; // This is hard coded directly to avoid needing to import, and // we'll remove this as we replace runWithPriority with React APIs. const IdleLanePriority = 2; +const InputContinuousPriority = 10; function loadModules() { ReactFeatureFlags = require('shared/ReactFeatureFlags'); @@ -427,6 +428,7 @@ describe('ReactDOMTracing', () => { }); // @gate experimental + // @gate enableNativeEventPriorityInference it('should properly trace interactions when there is work of interleaved priorities', () => { const Child = () => { Scheduler.unstable_yieldValue('Child'); @@ -502,9 +504,8 @@ describe('ReactDOMTracing', () => { let interaction = null; SchedulerTracing.unstable_trace('update', 0, () => { interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - Scheduler.unstable_runWithPriority( - Scheduler.unstable_UserBlockingPriority, - () => scheduleUpdateWithHidden(), + ReactDOM.unstable_runWithPriority(InputContinuousPriority, () => + scheduleUpdateWithHidden(), ); }); scheduleUpdate(); @@ -549,6 +550,7 @@ describe('ReactDOMTracing', () => { }); // @gate experimental + // @gate enableNativeEventPriorityInference it('should properly trace interactions through a multi-pass SuspenseList render', () => { const SuspenseList = React.SuspenseList; const Suspense = React.Suspense; @@ -610,10 +612,9 @@ describe('ReactDOMTracing', () => { // Schedule an unrelated low priority update that shouldn't be included // in the previous interaction. This is meant to ensure that we don't // rely on the whole tree completing to cover up bugs. - Scheduler.unstable_runWithPriority( - Scheduler.unstable_IdlePriority, - () => root.render(), - ); + ReactDOM.unstable_runWithPriority(IdleLanePriority, () => { + root.render(); + }); expect(onInteractionTraced).toHaveBeenCalledTimes(1); expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(