/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core * @jest-environment node */ 'use strict'; let React; let ReactNoop; let Scheduler; let act; let NormalPriority; let IdlePriority; let runWithPriority; let startTransition; let waitForAll; let waitForPaint; let assertLog; let waitFor; describe('ReactSchedulerIntegration', () => { beforeEach(() => { jest.resetModules(); React = require('react'); ReactNoop = require('react-noop-renderer'); Scheduler = require('scheduler'); act = require('internal-test-utils').act; NormalPriority = Scheduler.unstable_NormalPriority; IdlePriority = Scheduler.unstable_IdlePriority; runWithPriority = Scheduler.unstable_runWithPriority; startTransition = React.startTransition; const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; waitForPaint = InternalTestUtils.waitForPaint; assertLog = InternalTestUtils.assertLog; waitFor = InternalTestUtils.waitFor; }); // Note: This is based on a similar component we use in www. We can delete // once the extra div wrapper is no longer necessary. function LegacyHiddenDiv({children, mode}) { return ( ); } it('passive effects are called before Normal-pri scheduled in layout effects', async () => { const {useEffect, useLayoutEffect} = React; function Effects({step}) { useLayoutEffect(() => { Scheduler.log('Layout Effect'); Scheduler.unstable_scheduleCallback(NormalPriority, () => Scheduler.log('Scheduled Normal Callback from Layout Effect'), ); }); useEffect(() => { Scheduler.log('Passive Effect'); }); return null; } function CleanupEffect() { useLayoutEffect(() => () => { Scheduler.log('Cleanup Layout Effect'); Scheduler.unstable_scheduleCallback(NormalPriority, () => Scheduler.log('Scheduled Normal Callback from Cleanup Layout Effect'), ); }); return null; } await act(() => { ReactNoop.render(); }); assertLog([]); await act(() => { ReactNoop.render(); }); assertLog([ 'Cleanup Layout Effect', 'Layout Effect', 'Passive Effect', // These callbacks should be scheduled after the passive effects. 'Scheduled Normal Callback from Cleanup Layout Effect', 'Scheduled Normal Callback from Layout Effect', ]); }); it('requests a paint after committing', async () => { const scheduleCallback = Scheduler.unstable_scheduleCallback; const root = ReactNoop.createRoot(); root.render('Initial'); await waitForAll([]); expect(root).toMatchRenderedOutput('Initial'); scheduleCallback(NormalPriority, () => Scheduler.log('A')); scheduleCallback(NormalPriority, () => Scheduler.log('B')); scheduleCallback(NormalPriority, () => Scheduler.log('C')); // Schedule a React render. React will request a paint after committing it. React.startTransition(() => { root.render('Update'); }); // Perform just a little bit of work. By now, the React task will have // already been scheduled, behind A, B, and C. await waitFor(['A']); // Schedule some additional tasks. These won't fire until after the React // update has finished. scheduleCallback(NormalPriority, () => Scheduler.log('D')); scheduleCallback(NormalPriority, () => Scheduler.log('E')); // Flush everything up to the next paint. Should yield after the // React commit. await waitForPaint(['B', 'C']); expect(root).toMatchRenderedOutput('Update'); // Now flush the rest of the work. await waitForAll(['D', 'E']); }); // @gate enableLegacyHidden it('idle updates are not blocked by offscreen work', async () => { function Text({text}) { Scheduler.log(text); return text; } function App({label}) { return ( <> ); } const root = ReactNoop.createRoot(); await act(async () => { root.render(); // Commit the visible content await waitForPaint(['Visible: A']); expect(root).toMatchRenderedOutput( <> Visible: A