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(