1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. let Scheduler;
    
  13. let runWithPriority;
    
  14. let ImmediatePriority;
    
  15. let UserBlockingPriority;
    
  16. let NormalPriority;
    
  17. let LowPriority;
    
  18. let IdlePriority;
    
  19. let scheduleCallback;
    
  20. let cancelCallback;
    
  21. let wrapCallback;
    
  22. let getCurrentPriorityLevel;
    
  23. let shouldYield;
    
  24. let waitForAll;
    
  25. let assertLog;
    
  26. let waitFor;
    
  27. let waitForPaint;
    
  28. 
    
  29. describe('Scheduler', () => {
    
  30.   beforeEach(() => {
    
  31.     jest.resetModules();
    
  32.     jest.mock('scheduler', () => require('scheduler/unstable_mock'));
    
  33. 
    
  34.     Scheduler = require('scheduler');
    
  35. 
    
  36.     runWithPriority = Scheduler.unstable_runWithPriority;
    
  37.     ImmediatePriority = Scheduler.unstable_ImmediatePriority;
    
  38.     UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
    
  39.     NormalPriority = Scheduler.unstable_NormalPriority;
    
  40.     LowPriority = Scheduler.unstable_LowPriority;
    
  41.     IdlePriority = Scheduler.unstable_IdlePriority;
    
  42.     scheduleCallback = Scheduler.unstable_scheduleCallback;
    
  43.     cancelCallback = Scheduler.unstable_cancelCallback;
    
  44.     wrapCallback = Scheduler.unstable_wrapCallback;
    
  45.     getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
    
  46.     shouldYield = Scheduler.unstable_shouldYield;
    
  47. 
    
  48.     const InternalTestUtils = require('internal-test-utils');
    
  49.     waitForAll = InternalTestUtils.waitForAll;
    
  50.     assertLog = InternalTestUtils.assertLog;
    
  51.     waitFor = InternalTestUtils.waitFor;
    
  52.     waitForPaint = InternalTestUtils.waitForPaint;
    
  53.   });
    
  54. 
    
  55.   it('flushes work incrementally', async () => {
    
  56.     scheduleCallback(NormalPriority, () => Scheduler.log('A'));
    
  57.     scheduleCallback(NormalPriority, () => Scheduler.log('B'));
    
  58.     scheduleCallback(NormalPriority, () => Scheduler.log('C'));
    
  59.     scheduleCallback(NormalPriority, () => Scheduler.log('D'));
    
  60. 
    
  61.     await waitFor(['A', 'B']);
    
  62.     await waitFor(['C']);
    
  63.     await waitForAll(['D']);
    
  64.   });
    
  65. 
    
  66.   it('cancels work', async () => {
    
  67.     scheduleCallback(NormalPriority, () => Scheduler.log('A'));
    
  68.     const callbackHandleB = scheduleCallback(NormalPriority, () =>
    
  69.       Scheduler.log('B'),
    
  70.     );
    
  71.     scheduleCallback(NormalPriority, () => Scheduler.log('C'));
    
  72. 
    
  73.     cancelCallback(callbackHandleB);
    
  74. 
    
  75.     await waitForAll([
    
  76.       'A',
    
  77.       // B should have been cancelled
    
  78.       'C',
    
  79.     ]);
    
  80.   });
    
  81. 
    
  82.   it('executes the highest priority callbacks first', async () => {
    
  83.     scheduleCallback(NormalPriority, () => Scheduler.log('A'));
    
  84.     scheduleCallback(NormalPriority, () => Scheduler.log('B'));
    
  85. 
    
  86.     // Yield before B is flushed
    
  87.     await waitFor(['A']);
    
  88. 
    
  89.     scheduleCallback(UserBlockingPriority, () => Scheduler.log('C'));
    
  90.     scheduleCallback(UserBlockingPriority, () => Scheduler.log('D'));
    
  91. 
    
  92.     // C and D should come first, because they are higher priority
    
  93.     await waitForAll(['C', 'D', 'B']);
    
  94.   });
    
  95. 
    
  96.   it('expires work', async () => {
    
  97.     scheduleCallback(NormalPriority, didTimeout => {
    
  98.       Scheduler.unstable_advanceTime(100);
    
  99.       Scheduler.log(`A (did timeout: ${didTimeout})`);
    
  100.     });
    
  101.     scheduleCallback(UserBlockingPriority, didTimeout => {
    
  102.       Scheduler.unstable_advanceTime(100);
    
  103.       Scheduler.log(`B (did timeout: ${didTimeout})`);
    
  104.     });
    
  105.     scheduleCallback(UserBlockingPriority, didTimeout => {
    
  106.       Scheduler.unstable_advanceTime(100);
    
  107.       Scheduler.log(`C (did timeout: ${didTimeout})`);
    
  108.     });
    
  109. 
    
  110.     // Advance time, but not by enough to expire any work
    
  111.     Scheduler.unstable_advanceTime(249);
    
  112.     assertLog([]);
    
  113. 
    
  114.     // Schedule a few more callbacks
    
  115.     scheduleCallback(NormalPriority, didTimeout => {
    
  116.       Scheduler.unstable_advanceTime(100);
    
  117.       Scheduler.log(`D (did timeout: ${didTimeout})`);
    
  118.     });
    
  119.     scheduleCallback(NormalPriority, didTimeout => {
    
  120.       Scheduler.unstable_advanceTime(100);
    
  121.       Scheduler.log(`E (did timeout: ${didTimeout})`);
    
  122.     });
    
  123. 
    
  124.     // Advance by just a bit more to expire the user blocking callbacks
    
  125.     Scheduler.unstable_advanceTime(1);
    
  126.     await waitFor(['B (did timeout: true)', 'C (did timeout: true)']);
    
  127. 
    
  128.     // Expire A
    
  129.     Scheduler.unstable_advanceTime(4600);
    
  130.     await waitFor(['A (did timeout: true)']);
    
  131. 
    
  132.     // Flush the rest without expiring
    
  133.     await waitForAll(['D (did timeout: false)', 'E (did timeout: true)']);
    
  134.   });
    
  135. 
    
  136.   it('has a default expiration of ~5 seconds', () => {
    
  137.     scheduleCallback(NormalPriority, () => Scheduler.log('A'));
    
  138. 
    
  139.     Scheduler.unstable_advanceTime(4999);
    
  140.     assertLog([]);
    
  141. 
    
  142.     Scheduler.unstable_advanceTime(1);
    
  143.     Scheduler.unstable_flushExpired();
    
  144.     assertLog(['A']);
    
  145.   });
    
  146. 
    
  147.   it('continues working on same task after yielding', async () => {
    
  148.     scheduleCallback(NormalPriority, () => {
    
  149.       Scheduler.unstable_advanceTime(100);
    
  150.       Scheduler.log('A');
    
  151.     });
    
  152.     scheduleCallback(NormalPriority, () => {
    
  153.       Scheduler.unstable_advanceTime(100);
    
  154.       Scheduler.log('B');
    
  155.     });
    
  156. 
    
  157.     let didYield = false;
    
  158.     const tasks = [
    
  159.       ['C1', 100],
    
  160.       ['C2', 100],
    
  161.       ['C3', 100],
    
  162.     ];
    
  163.     const C = () => {
    
  164.       while (tasks.length > 0) {
    
  165.         const [label, ms] = tasks.shift();
    
  166.         Scheduler.unstable_advanceTime(ms);
    
  167.         Scheduler.log(label);
    
  168.         if (shouldYield()) {
    
  169.           didYield = true;
    
  170.           return C;
    
  171.         }
    
  172.       }
    
  173.     };
    
  174. 
    
  175.     scheduleCallback(NormalPriority, C);
    
  176. 
    
  177.     scheduleCallback(NormalPriority, () => {
    
  178.       Scheduler.unstable_advanceTime(100);
    
  179.       Scheduler.log('D');
    
  180.     });
    
  181.     scheduleCallback(NormalPriority, () => {
    
  182.       Scheduler.unstable_advanceTime(100);
    
  183.       Scheduler.log('E');
    
  184.     });
    
  185. 
    
  186.     // Flush, then yield while in the middle of C.
    
  187.     expect(didYield).toBe(false);
    
  188.     await waitFor(['A', 'B', 'C1']);
    
  189.     expect(didYield).toBe(true);
    
  190. 
    
  191.     // When we resume, we should continue working on C.
    
  192.     await waitForAll(['C2', 'C3', 'D', 'E']);
    
  193.   });
    
  194. 
    
  195.   it('continuation callbacks inherit the expiration of the previous callback', async () => {
    
  196.     const tasks = [
    
  197.       ['A', 125],
    
  198.       ['B', 124],
    
  199.       ['C', 100],
    
  200.       ['D', 100],
    
  201.     ];
    
  202.     const work = () => {
    
  203.       while (tasks.length > 0) {
    
  204.         const [label, ms] = tasks.shift();
    
  205.         Scheduler.unstable_advanceTime(ms);
    
  206.         Scheduler.log(label);
    
  207.         if (shouldYield()) {
    
  208.           return work;
    
  209.         }
    
  210.       }
    
  211.     };
    
  212. 
    
  213.     // Schedule a high priority callback
    
  214.     scheduleCallback(UserBlockingPriority, work);
    
  215. 
    
  216.     // Flush until just before the expiration time
    
  217.     await waitFor(['A', 'B']);
    
  218. 
    
  219.     // Advance time by just a bit more. This should expire all the remaining work.
    
  220.     Scheduler.unstable_advanceTime(1);
    
  221.     Scheduler.unstable_flushExpired();
    
  222.     assertLog(['C', 'D']);
    
  223.   });
    
  224. 
    
  225.   it('continuations are interrupted by higher priority work', async () => {
    
  226.     const tasks = [
    
  227.       ['A', 100],
    
  228.       ['B', 100],
    
  229.       ['C', 100],
    
  230.       ['D', 100],
    
  231.     ];
    
  232.     const work = () => {
    
  233.       while (tasks.length > 0) {
    
  234.         const [label, ms] = tasks.shift();
    
  235.         Scheduler.unstable_advanceTime(ms);
    
  236.         Scheduler.log(label);
    
  237.         if (tasks.length > 0 && shouldYield()) {
    
  238.           return work;
    
  239.         }
    
  240.       }
    
  241.     };
    
  242.     scheduleCallback(NormalPriority, work);
    
  243.     await waitFor(['A']);
    
  244. 
    
  245.     scheduleCallback(UserBlockingPriority, () => {
    
  246.       Scheduler.unstable_advanceTime(100);
    
  247.       Scheduler.log('High pri');
    
  248.     });
    
  249. 
    
  250.     await waitForAll(['High pri', 'B', 'C', 'D']);
    
  251.   });
    
  252. 
    
  253.   it(
    
  254.     'continuations do not block higher priority work scheduled ' +
    
  255.       'inside an executing callback',
    
  256.     async () => {
    
  257.       const tasks = [
    
  258.         ['A', 100],
    
  259.         ['B', 100],
    
  260.         ['C', 100],
    
  261.         ['D', 100],
    
  262.       ];
    
  263.       const work = () => {
    
  264.         while (tasks.length > 0) {
    
  265.           const task = tasks.shift();
    
  266.           const [label, ms] = task;
    
  267.           Scheduler.unstable_advanceTime(ms);
    
  268.           Scheduler.log(label);
    
  269.           if (label === 'B') {
    
  270.             // Schedule high pri work from inside another callback
    
  271.             Scheduler.log('Schedule high pri');
    
  272.             scheduleCallback(UserBlockingPriority, () => {
    
  273.               Scheduler.unstable_advanceTime(100);
    
  274.               Scheduler.log('High pri');
    
  275.             });
    
  276.           }
    
  277.           if (tasks.length > 0) {
    
  278.             // Return a continuation
    
  279.             return work;
    
  280.           }
    
  281.         }
    
  282.       };
    
  283.       scheduleCallback(NormalPriority, work);
    
  284.       await waitForAll([
    
  285.         'A',
    
  286.         'B',
    
  287.         'Schedule high pri',
    
  288.         // The high pri callback should fire before the continuation of the
    
  289.         // lower pri work
    
  290.         'High pri',
    
  291.         // Continue low pri work
    
  292.         'C',
    
  293.         'D',
    
  294.       ]);
    
  295.     },
    
  296.   );
    
  297. 
    
  298.   it('cancelling a continuation', async () => {
    
  299.     const task = scheduleCallback(NormalPriority, () => {
    
  300.       Scheduler.log('Yield');
    
  301.       return () => {
    
  302.         Scheduler.log('Continuation');
    
  303.       };
    
  304.     });
    
  305. 
    
  306.     await waitFor(['Yield']);
    
  307.     cancelCallback(task);
    
  308.     await waitForAll([]);
    
  309.   });
    
  310. 
    
  311.   it('top-level immediate callbacks fire in a subsequent task', () => {
    
  312.     scheduleCallback(ImmediatePriority, () => Scheduler.log('A'));
    
  313.     scheduleCallback(ImmediatePriority, () => Scheduler.log('B'));
    
  314.     scheduleCallback(ImmediatePriority, () => Scheduler.log('C'));
    
  315.     scheduleCallback(ImmediatePriority, () => Scheduler.log('D'));
    
  316.     // Immediate callback hasn't fired, yet.
    
  317.     assertLog([]);
    
  318.     // They all flush immediately within the subsequent task.
    
  319.     Scheduler.unstable_flushExpired();
    
  320.     assertLog(['A', 'B', 'C', 'D']);
    
  321.   });
    
  322. 
    
  323.   it('nested immediate callbacks are added to the queue of immediate callbacks', () => {
    
  324.     scheduleCallback(ImmediatePriority, () => Scheduler.log('A'));
    
  325.     scheduleCallback(ImmediatePriority, () => {
    
  326.       Scheduler.log('B');
    
  327.       // This callback should go to the end of the queue
    
  328.       scheduleCallback(ImmediatePriority, () => Scheduler.log('C'));
    
  329.     });
    
  330.     scheduleCallback(ImmediatePriority, () => Scheduler.log('D'));
    
  331.     assertLog([]);
    
  332.     // C should flush at the end
    
  333.     Scheduler.unstable_flushExpired();
    
  334.     assertLog(['A', 'B', 'D', 'C']);
    
  335.   });
    
  336. 
    
  337.   it('wrapped callbacks have same signature as original callback', () => {
    
  338.     const wrappedCallback = wrapCallback((...args) => ({args}));
    
  339.     expect(wrappedCallback('a', 'b')).toEqual({args: ['a', 'b']});
    
  340.   });
    
  341. 
    
  342.   it('wrapped callbacks inherit the current priority', () => {
    
  343.     const wrappedCallback = runWithPriority(NormalPriority, () =>
    
  344.       wrapCallback(() => {
    
  345.         Scheduler.log(getCurrentPriorityLevel());
    
  346.       }),
    
  347.     );
    
  348. 
    
  349.     const wrappedUserBlockingCallback = runWithPriority(
    
  350.       UserBlockingPriority,
    
  351.       () =>
    
  352.         wrapCallback(() => {
    
  353.           Scheduler.log(getCurrentPriorityLevel());
    
  354.         }),
    
  355.     );
    
  356. 
    
  357.     wrappedCallback();
    
  358.     assertLog([NormalPriority]);
    
  359. 
    
  360.     wrappedUserBlockingCallback();
    
  361.     assertLog([UserBlockingPriority]);
    
  362.   });
    
  363. 
    
  364.   it('wrapped callbacks inherit the current priority even when nested', () => {
    
  365.     let wrappedCallback;
    
  366.     let wrappedUserBlockingCallback;
    
  367. 
    
  368.     runWithPriority(NormalPriority, () => {
    
  369.       wrappedCallback = wrapCallback(() => {
    
  370.         Scheduler.log(getCurrentPriorityLevel());
    
  371.       });
    
  372.       wrappedUserBlockingCallback = runWithPriority(UserBlockingPriority, () =>
    
  373.         wrapCallback(() => {
    
  374.           Scheduler.log(getCurrentPriorityLevel());
    
  375.         }),
    
  376.       );
    
  377.     });
    
  378. 
    
  379.     wrappedCallback();
    
  380.     assertLog([NormalPriority]);
    
  381. 
    
  382.     wrappedUserBlockingCallback();
    
  383.     assertLog([UserBlockingPriority]);
    
  384.   });
    
  385. 
    
  386.   it("immediate callbacks fire even if there's an error", () => {
    
  387.     scheduleCallback(ImmediatePriority, () => {
    
  388.       Scheduler.log('A');
    
  389.       throw new Error('Oops A');
    
  390.     });
    
  391.     scheduleCallback(ImmediatePriority, () => {
    
  392.       Scheduler.log('B');
    
  393.     });
    
  394.     scheduleCallback(ImmediatePriority, () => {
    
  395.       Scheduler.log('C');
    
  396.       throw new Error('Oops C');
    
  397.     });
    
  398. 
    
  399.     expect(() => Scheduler.unstable_flushExpired()).toThrow('Oops A');
    
  400.     assertLog(['A']);
    
  401. 
    
  402.     // B and C flush in a subsequent event. That way, the second error is not
    
  403.     // swallowed.
    
  404.     expect(() => Scheduler.unstable_flushExpired()).toThrow('Oops C');
    
  405.     assertLog(['B', 'C']);
    
  406.   });
    
  407. 
    
  408.   it('multiple immediate callbacks can throw and there will be an error for each one', () => {
    
  409.     scheduleCallback(ImmediatePriority, () => {
    
  410.       throw new Error('First error');
    
  411.     });
    
  412.     scheduleCallback(ImmediatePriority, () => {
    
  413.       throw new Error('Second error');
    
  414.     });
    
  415.     expect(() => Scheduler.unstable_flushAll()).toThrow('First error');
    
  416.     // The next error is thrown in the subsequent event
    
  417.     expect(() => Scheduler.unstable_flushAll()).toThrow('Second error');
    
  418.   });
    
  419. 
    
  420.   it('exposes the current priority level', () => {
    
  421.     Scheduler.log(getCurrentPriorityLevel());
    
  422.     runWithPriority(ImmediatePriority, () => {
    
  423.       Scheduler.log(getCurrentPriorityLevel());
    
  424.       runWithPriority(NormalPriority, () => {
    
  425.         Scheduler.log(getCurrentPriorityLevel());
    
  426.         runWithPriority(UserBlockingPriority, () => {
    
  427.           Scheduler.log(getCurrentPriorityLevel());
    
  428.         });
    
  429.       });
    
  430.       Scheduler.log(getCurrentPriorityLevel());
    
  431.     });
    
  432. 
    
  433.     assertLog([
    
  434.       NormalPriority,
    
  435.       ImmediatePriority,
    
  436.       NormalPriority,
    
  437.       UserBlockingPriority,
    
  438.       ImmediatePriority,
    
  439.     ]);
    
  440.   });
    
  441. 
    
  442.   if (__DEV__) {
    
  443.     // Function names are minified in prod, though you could still infer the
    
  444.     // priority if you have sourcemaps.
    
  445.     // TODO: Feature temporarily disabled while we investigate a bug in one of
    
  446.     // our minifiers.
    
  447.     it.skip('adds extra function to the JS stack whose name includes the priority level', async () => {
    
  448.       function inferPriorityFromCallstack() {
    
  449.         try {
    
  450.           throw Error();
    
  451.         } catch (e) {
    
  452.           const stack = e.stack;
    
  453.           const lines = stack.split('\n');
    
  454.           for (let i = lines.length - 1; i >= 0; i--) {
    
  455.             const line = lines[i];
    
  456.             const found = line.match(
    
  457.               /scheduler_flushTaskAtPriority_([A-Za-z]+)/,
    
  458.             );
    
  459.             if (found !== null) {
    
  460.               const priorityStr = found[1];
    
  461.               switch (priorityStr) {
    
  462.                 case 'Immediate':
    
  463.                   return ImmediatePriority;
    
  464.                 case 'UserBlocking':
    
  465.                   return UserBlockingPriority;
    
  466.                 case 'Normal':
    
  467.                   return NormalPriority;
    
  468.                 case 'Low':
    
  469.                   return LowPriority;
    
  470.                 case 'Idle':
    
  471.                   return IdlePriority;
    
  472.               }
    
  473.             }
    
  474.           }
    
  475.           return null;
    
  476.         }
    
  477.       }
    
  478. 
    
  479.       scheduleCallback(ImmediatePriority, () =>
    
  480.         Scheduler.log('Immediate: ' + inferPriorityFromCallstack()),
    
  481.       );
    
  482.       scheduleCallback(UserBlockingPriority, () =>
    
  483.         Scheduler.log('UserBlocking: ' + inferPriorityFromCallstack()),
    
  484.       );
    
  485.       scheduleCallback(NormalPriority, () =>
    
  486.         Scheduler.log('Normal: ' + inferPriorityFromCallstack()),
    
  487.       );
    
  488.       scheduleCallback(LowPriority, () =>
    
  489.         Scheduler.log('Low: ' + inferPriorityFromCallstack()),
    
  490.       );
    
  491.       scheduleCallback(IdlePriority, () =>
    
  492.         Scheduler.log('Idle: ' + inferPriorityFromCallstack()),
    
  493.       );
    
  494. 
    
  495.       await waitForAll([
    
  496.         'Immediate: ' + ImmediatePriority,
    
  497.         'UserBlocking: ' + UserBlockingPriority,
    
  498.         'Normal: ' + NormalPriority,
    
  499.         'Low: ' + LowPriority,
    
  500.         'Idle: ' + IdlePriority,
    
  501.       ]);
    
  502.     });
    
  503.   }
    
  504. 
    
  505.   describe('delayed tasks', () => {
    
  506.     it('schedules a delayed task', async () => {
    
  507.       scheduleCallback(NormalPriority, () => Scheduler.log('A'), {
    
  508.         delay: 1000,
    
  509.       });
    
  510. 
    
  511.       // Should flush nothing, because delay hasn't elapsed
    
  512.       await waitForAll([]);
    
  513. 
    
  514.       // Advance time until right before the threshold
    
  515.       Scheduler.unstable_advanceTime(999);
    
  516.       // Still nothing
    
  517.       await waitForAll([]);
    
  518. 
    
  519.       // Advance time past the threshold
    
  520.       Scheduler.unstable_advanceTime(1);
    
  521. 
    
  522.       // Now it should flush like normal
    
  523.       await waitForAll(['A']);
    
  524.     });
    
  525. 
    
  526.     it('schedules multiple delayed tasks', async () => {
    
  527.       scheduleCallback(NormalPriority, () => Scheduler.log('C'), {
    
  528.         delay: 300,
    
  529.       });
    
  530. 
    
  531.       scheduleCallback(NormalPriority, () => Scheduler.log('B'), {
    
  532.         delay: 200,
    
  533.       });
    
  534. 
    
  535.       scheduleCallback(NormalPriority, () => Scheduler.log('D'), {
    
  536.         delay: 400,
    
  537.       });
    
  538. 
    
  539.       scheduleCallback(NormalPriority, () => Scheduler.log('A'), {
    
  540.         delay: 100,
    
  541.       });
    
  542. 
    
  543.       // Should flush nothing, because delay hasn't elapsed
    
  544.       await waitForAll([]);
    
  545. 
    
  546.       // Advance some time.
    
  547.       Scheduler.unstable_advanceTime(200);
    
  548.       // Both A and B are no longer delayed. They can now flush incrementally.
    
  549.       await waitFor(['A']);
    
  550.       await waitForAll(['B']);
    
  551. 
    
  552.       // Advance the rest
    
  553.       Scheduler.unstable_advanceTime(200);
    
  554.       await waitForAll(['C', 'D']);
    
  555.     });
    
  556. 
    
  557.     it('interleaves normal tasks and delayed tasks', async () => {
    
  558.       // Schedule some high priority callbacks with a delay. When their delay
    
  559.       // elapses, they will be the most important callback in the queue.
    
  560.       scheduleCallback(UserBlockingPriority, () => Scheduler.log('Timer 2'), {
    
  561.         delay: 300,
    
  562.       });
    
  563.       scheduleCallback(UserBlockingPriority, () => Scheduler.log('Timer 1'), {
    
  564.         delay: 100,
    
  565.       });
    
  566. 
    
  567.       // Schedule some tasks at default priority.
    
  568.       scheduleCallback(NormalPriority, () => {
    
  569.         Scheduler.log('A');
    
  570.         Scheduler.unstable_advanceTime(100);
    
  571.       });
    
  572.       scheduleCallback(NormalPriority, () => {
    
  573.         Scheduler.log('B');
    
  574.         Scheduler.unstable_advanceTime(100);
    
  575.       });
    
  576.       scheduleCallback(NormalPriority, () => {
    
  577.         Scheduler.log('C');
    
  578.         Scheduler.unstable_advanceTime(100);
    
  579.       });
    
  580.       scheduleCallback(NormalPriority, () => {
    
  581.         Scheduler.log('D');
    
  582.         Scheduler.unstable_advanceTime(100);
    
  583.       });
    
  584. 
    
  585.       // Flush all the work. The timers should be interleaved with the
    
  586.       // other tasks.
    
  587.       await waitForAll(['A', 'Timer 1', 'B', 'C', 'Timer 2', 'D']);
    
  588.     });
    
  589. 
    
  590.     it('interleaves delayed tasks with time-sliced tasks', async () => {
    
  591.       // Schedule some high priority callbacks with a delay. When their delay
    
  592.       // elapses, they will be the most important callback in the queue.
    
  593.       scheduleCallback(UserBlockingPriority, () => Scheduler.log('Timer 2'), {
    
  594.         delay: 300,
    
  595.       });
    
  596.       scheduleCallback(UserBlockingPriority, () => Scheduler.log('Timer 1'), {
    
  597.         delay: 100,
    
  598.       });
    
  599. 
    
  600.       // Schedule a time-sliced task at default priority.
    
  601.       const tasks = [
    
  602.         ['A', 100],
    
  603.         ['B', 100],
    
  604.         ['C', 100],
    
  605.         ['D', 100],
    
  606.       ];
    
  607.       const work = () => {
    
  608.         while (tasks.length > 0) {
    
  609.           const task = tasks.shift();
    
  610.           const [label, ms] = task;
    
  611.           Scheduler.unstable_advanceTime(ms);
    
  612.           Scheduler.log(label);
    
  613.           if (tasks.length > 0) {
    
  614.             return work;
    
  615.           }
    
  616.         }
    
  617.       };
    
  618.       scheduleCallback(NormalPriority, work);
    
  619. 
    
  620.       // Flush all the work. The timers should be interleaved with the
    
  621.       // other tasks.
    
  622.       await waitForAll(['A', 'Timer 1', 'B', 'C', 'Timer 2', 'D']);
    
  623.     });
    
  624. 
    
  625.     it('cancels a delayed task', async () => {
    
  626.       // Schedule several tasks with the same delay
    
  627.       const options = {delay: 100};
    
  628. 
    
  629.       scheduleCallback(NormalPriority, () => Scheduler.log('A'), options);
    
  630.       const taskB = scheduleCallback(
    
  631.         NormalPriority,
    
  632.         () => Scheduler.log('B'),
    
  633.         options,
    
  634.       );
    
  635.       const taskC = scheduleCallback(
    
  636.         NormalPriority,
    
  637.         () => Scheduler.log('C'),
    
  638.         options,
    
  639.       );
    
  640. 
    
  641.       // Cancel B before its delay has elapsed
    
  642.       await waitForAll([]);
    
  643.       cancelCallback(taskB);
    
  644. 
    
  645.       // Cancel C after its delay has elapsed
    
  646.       Scheduler.unstable_advanceTime(500);
    
  647.       cancelCallback(taskC);
    
  648. 
    
  649.       // Only A should flush
    
  650.       await waitForAll(['A']);
    
  651.     });
    
  652. 
    
  653.     it('gracefully handles scheduled tasks that are not a function', async () => {
    
  654.       scheduleCallback(ImmediatePriority, null);
    
  655.       await waitForAll([]);
    
  656. 
    
  657.       scheduleCallback(ImmediatePriority, undefined);
    
  658.       await waitForAll([]);
    
  659. 
    
  660.       scheduleCallback(ImmediatePriority, {});
    
  661.       await waitForAll([]);
    
  662. 
    
  663.       scheduleCallback(ImmediatePriority, 42);
    
  664.       await waitForAll([]);
    
  665.     });
    
  666. 
    
  667.     it('toFlushUntilNextPaint stops if a continuation is returned', async () => {
    
  668.       scheduleCallback(NormalPriority, () => {
    
  669.         Scheduler.log('Original Task');
    
  670.         Scheduler.log('shouldYield: ' + shouldYield());
    
  671.         Scheduler.log('Return a continuation');
    
  672.         return () => {
    
  673.           Scheduler.log('Continuation Task');
    
  674.         };
    
  675.       });
    
  676. 
    
  677.       await waitForPaint([
    
  678.         'Original Task',
    
  679.         // Immediately before returning a continuation, `shouldYield` returns
    
  680.         // false, which means there must be time remaining in the frame.
    
  681.         'shouldYield: false',
    
  682.         'Return a continuation',
    
  683. 
    
  684.         // The continuation should not flush yet.
    
  685.       ]);
    
  686. 
    
  687.       // No time has elapsed
    
  688.       expect(Scheduler.unstable_now()).toBe(0);
    
  689. 
    
  690.       // Continue the task
    
  691.       await waitForAll(['Continuation Task']);
    
  692.     });
    
  693. 
    
  694.     it("toFlushAndYield keeps flushing even if there's a continuation", async () => {
    
  695.       scheduleCallback(NormalPriority, () => {
    
  696.         Scheduler.log('Original Task');
    
  697.         Scheduler.log('shouldYield: ' + shouldYield());
    
  698.         Scheduler.log('Return a continuation');
    
  699.         return () => {
    
  700.           Scheduler.log('Continuation Task');
    
  701.         };
    
  702.       });
    
  703. 
    
  704.       await waitForAll([
    
  705.         'Original Task',
    
  706.         // Immediately before returning a continuation, `shouldYield` returns
    
  707.         // false, which means there must be time remaining in the frame.
    
  708.         'shouldYield: false',
    
  709.         'Return a continuation',
    
  710. 
    
  711.         // The continuation should flush immediately, even though the task
    
  712.         // yielded a continuation.
    
  713.         'Continuation Task',
    
  714.       ]);
    
  715.     });
    
  716.   });
    
  717. });