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.  * @jest-environment node
    
  9.  */
    
  10. 
    
  11. let React;
    
  12. let ReactNoop;
    
  13. let Scheduler;
    
  14. let act;
    
  15. let waitForAll;
    
  16. let assertLog;
    
  17. 
    
  18. let getCacheForType;
    
  19. let useState;
    
  20. let Suspense;
    
  21. let Activity;
    
  22. let startTransition;
    
  23. 
    
  24. let caches;
    
  25. let seededCache;
    
  26. 
    
  27. describe('ReactInteractionTracing', () => {
    
  28.   function stringifyDeletions(deletions) {
    
  29.     return deletions
    
  30.       .map(
    
  31.         d =>
    
  32.           `{${Object.keys(d)
    
  33.             .map(key => `${key}: ${d[key]}`)
    
  34.             .sort()
    
  35.             .join(', ')}}`,
    
  36.       )
    
  37.       .join(', ');
    
  38.   }
    
  39.   beforeEach(() => {
    
  40.     jest.resetModules();
    
  41. 
    
  42.     React = require('react');
    
  43.     ReactNoop = require('react-noop-renderer');
    
  44.     Scheduler = require('scheduler');
    
  45. 
    
  46.     act = require('internal-test-utils').act;
    
  47. 
    
  48.     const InternalTestUtils = require('internal-test-utils');
    
  49.     waitForAll = InternalTestUtils.waitForAll;
    
  50.     assertLog = InternalTestUtils.assertLog;
    
  51. 
    
  52.     useState = React.useState;
    
  53.     startTransition = React.startTransition;
    
  54.     Suspense = React.Suspense;
    
  55.     Activity = React.unstable_Activity;
    
  56. 
    
  57.     getCacheForType = React.unstable_getCacheForType;
    
  58. 
    
  59.     caches = [];
    
  60.     seededCache = null;
    
  61.   });
    
  62. 
    
  63.   function createTextCache() {
    
  64.     if (seededCache !== null) {
    
  65.       const cache = seededCache;
    
  66.       seededCache = null;
    
  67.       return cache;
    
  68.     }
    
  69. 
    
  70.     const data = new Map();
    
  71.     const cache = {
    
  72.       data,
    
  73.       resolve(text) {
    
  74.         const record = data.get(text);
    
  75. 
    
  76.         if (record === undefined) {
    
  77.           const newRecord = {
    
  78.             status: 'resolved',
    
  79.             value: text,
    
  80.           };
    
  81.           data.set(text, newRecord);
    
  82.         } else if (record.status === 'pending') {
    
  83.           const thenable = record.value;
    
  84.           record.status = 'resolved';
    
  85.           record.value = text;
    
  86.           thenable.pings.forEach(t => t());
    
  87.         }
    
  88.       },
    
  89.       reject(text, error) {
    
  90.         const record = data.get(text);
    
  91.         if (record === undefined) {
    
  92.           const newRecord = {
    
  93.             status: 'rejected',
    
  94.             value: error,
    
  95.           };
    
  96.           data.set(text, newRecord);
    
  97.         } else if (record.status === 'pending') {
    
  98.           const thenable = record.value;
    
  99.           record.status = 'rejected';
    
  100.           record.value = error;
    
  101.           thenable.pings.forEach(t => t());
    
  102.         }
    
  103.       },
    
  104.     };
    
  105.     caches.push(cache);
    
  106.     return cache;
    
  107.   }
    
  108. 
    
  109.   function readText(text) {
    
  110.     const textCache = getCacheForType(createTextCache);
    
  111.     const record = textCache.data.get(text);
    
  112.     if (record !== undefined) {
    
  113.       switch (record.status) {
    
  114.         case 'pending':
    
  115.           Scheduler.log(`Suspend [${text}]`);
    
  116.           throw record.value;
    
  117.         case 'rejected':
    
  118.           Scheduler.log(`Error [${text}]`);
    
  119.           throw record.value;
    
  120.         case 'resolved':
    
  121.           return record.value;
    
  122.       }
    
  123.     } else {
    
  124.       Scheduler.log(`Suspend [${text}]`);
    
  125. 
    
  126.       const thenable = {
    
  127.         pings: [],
    
  128.         then(resolve) {
    
  129.           if (newRecord.status === 'pending') {
    
  130.             thenable.pings.push(resolve);
    
  131.           } else {
    
  132.             Promise.resolve().then(() => resolve(newRecord.value));
    
  133.           }
    
  134.         },
    
  135.       };
    
  136. 
    
  137.       const newRecord = {
    
  138.         status: 'pending',
    
  139.         value: thenable,
    
  140.       };
    
  141.       textCache.data.set(text, newRecord);
    
  142. 
    
  143.       throw thenable;
    
  144.     }
    
  145.   }
    
  146. 
    
  147.   function AsyncText({text}) {
    
  148.     const fullText = readText(text);
    
  149.     Scheduler.log(fullText);
    
  150.     return fullText;
    
  151.   }
    
  152. 
    
  153.   function Text({text}) {
    
  154.     Scheduler.log(text);
    
  155.     return text;
    
  156.   }
    
  157. 
    
  158.   function resolveMostRecentTextCache(text) {
    
  159.     if (caches.length === 0) {
    
  160.       throw Error('Cache does not exist');
    
  161.     } else {
    
  162.       // Resolve the most recently created cache. An older cache can by
    
  163.       // resolved with `caches[index].resolve(text)`.
    
  164.       caches[caches.length - 1].resolve(text);
    
  165.     }
    
  166.   }
    
  167. 
    
  168.   const resolveText = resolveMostRecentTextCache;
    
  169. 
    
  170.   function advanceTimers(ms) {
    
  171.     // Note: This advances Jest's virtual time but not React's. Use
    
  172.     // ReactNoop.expire for that.
    
  173.     if (typeof ms !== 'number') {
    
  174.       throw new Error('Must specify ms');
    
  175.     }
    
  176.     jest.advanceTimersByTime(ms);
    
  177.     // Wait until the end of the current tick
    
  178.     // We cannot use a timer since we're faking them
    
  179.     return Promise.resolve().then(() => {});
    
  180.   }
    
  181. 
    
  182.   // @gate enableTransitionTracing
    
  183.   it(' should not call callbacks when transition is not defined', async () => {
    
  184.     const transitionCallbacks = {
    
  185.       onTransitionStart: (name, startTime) => {
    
  186.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  187.       },
    
  188.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  189.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  190.         Scheduler.log(
    
  191.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  192.         );
    
  193.       },
    
  194.       onTransitionComplete: (name, startTime, endTime) => {
    
  195.         Scheduler.log(
    
  196.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  197.         );
    
  198.       },
    
  199.       onMarkerProgress: (
    
  200.         transitioName,
    
  201.         markerName,
    
  202.         startTime,
    
  203.         currentTime,
    
  204.         pending,
    
  205.       ) => {
    
  206.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  207.         Scheduler.log(
    
  208.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  209.         );
    
  210.       },
    
  211.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  212.         Scheduler.log(
    
  213.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  214.         );
    
  215.       },
    
  216.     };
    
  217. 
    
  218.     function App({navigate}) {
    
  219.       return (
    
  220.         <div>
    
  221.           {navigate ? (
    
  222.             <React.unstable_TracingMarker name="marker">
    
  223.               <Text text="Page Two" />
    
  224.             </React.unstable_TracingMarker>
    
  225.           ) : (
    
  226.             <Text text="Page One" />
    
  227.           )}
    
  228.         </div>
    
  229.       );
    
  230.     }
    
  231. 
    
  232.     const root = ReactNoop.createRoot({
    
  233.       unstable_transitionCallbacks: transitionCallbacks,
    
  234.     });
    
  235.     await act(async () => {
    
  236.       root.render(<App navigate={false} />);
    
  237.       ReactNoop.expire(1000);
    
  238.       await advanceTimers(1000);
    
  239. 
    
  240.       await waitForAll(['Page One']);
    
  241. 
    
  242.       await act(async () => {
    
  243.         startTransition(() => root.render(<App navigate={true} />));
    
  244. 
    
  245.         ReactNoop.expire(1000);
    
  246.         await advanceTimers(1000);
    
  247. 
    
  248.         // Doesn't call transition or marker code
    
  249.         await waitForAll(['Page Two']);
    
  250. 
    
  251.         startTransition(() => root.render(<App navigate={false} />), {
    
  252.           name: 'transition',
    
  253.         });
    
  254.         await waitForAll([
    
  255.           'Page One',
    
  256.           'onTransitionStart(transition, 2000)',
    
  257.           'onTransitionComplete(transition, 2000, 2000)',
    
  258.         ]);
    
  259.       });
    
  260.     });
    
  261.   });
    
  262. 
    
  263.   // @gate enableTransitionTracing
    
  264.   it('should correctly trace basic interaction', async () => {
    
  265.     const transitionCallbacks = {
    
  266.       onTransitionStart: (name, startTime) => {
    
  267.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  268.       },
    
  269.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  270.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  271.         Scheduler.log(
    
  272.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  273.         );
    
  274.       },
    
  275.       onTransitionComplete: (name, startTime, endTime) => {
    
  276.         Scheduler.log(
    
  277.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  278.         );
    
  279.       },
    
  280.     };
    
  281. 
    
  282.     let navigateToPageTwo;
    
  283.     function App() {
    
  284.       const [navigate, setNavigate] = useState(false);
    
  285.       navigateToPageTwo = () => {
    
  286.         setNavigate(true);
    
  287.       };
    
  288. 
    
  289.       return (
    
  290.         <div>
    
  291.           {navigate ? <Text text="Page Two" /> : <Text text="Page One" />}
    
  292.         </div>
    
  293.       );
    
  294.     }
    
  295. 
    
  296.     const root = ReactNoop.createRoot({
    
  297.       unstable_transitionCallbacks: transitionCallbacks,
    
  298.     });
    
  299.     await act(async () => {
    
  300.       root.render(<App />);
    
  301.       ReactNoop.expire(1000);
    
  302.       await advanceTimers(1000);
    
  303. 
    
  304.       await waitForAll(['Page One']);
    
  305. 
    
  306.       await act(async () => {
    
  307.         startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  308. 
    
  309.         ReactNoop.expire(1000);
    
  310.         await advanceTimers(1000);
    
  311. 
    
  312.         await waitForAll([
    
  313.           'Page Two',
    
  314.           'onTransitionStart(page transition, 1000)',
    
  315.           'onTransitionComplete(page transition, 1000, 2000)',
    
  316.         ]);
    
  317.       });
    
  318.     });
    
  319.   });
    
  320. 
    
  321.   // @gate enableTransitionTracing
    
  322.   it('multiple updates in transition callback should only result in one transitionStart/transitionComplete call', async () => {
    
  323.     const transitionCallbacks = {
    
  324.       onTransitionStart: (name, startTime) => {
    
  325.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  326.       },
    
  327.       onTransitionComplete: (name, startTime, endTime) => {
    
  328.         Scheduler.log(
    
  329.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  330.         );
    
  331.       },
    
  332.     };
    
  333. 
    
  334.     let navigateToPageTwo;
    
  335.     let setText;
    
  336.     function App() {
    
  337.       const [navigate, setNavigate] = useState(false);
    
  338.       const [text, _setText] = useState('hide');
    
  339.       navigateToPageTwo = () => setNavigate(true);
    
  340.       setText = () => _setText('show');
    
  341. 
    
  342.       return (
    
  343.         <div>
    
  344.           {navigate ? (
    
  345.             <Text text={`Page Two: ${text}`} />
    
  346.           ) : (
    
  347.             <Text text={`Page One: ${text}`} />
    
  348.           )}
    
  349.         </div>
    
  350.       );
    
  351.     }
    
  352. 
    
  353.     const root = ReactNoop.createRoot({
    
  354.       unstable_transitionCallbacks: transitionCallbacks,
    
  355.     });
    
  356.     await act(async () => {
    
  357.       root.render(<App />);
    
  358.       ReactNoop.expire(1000);
    
  359.       await advanceTimers(1000);
    
  360. 
    
  361.       await waitForAll(['Page One: hide']);
    
  362. 
    
  363.       await act(async () => {
    
  364.         startTransition(
    
  365.           () => {
    
  366.             navigateToPageTwo();
    
  367.             setText();
    
  368.           },
    
  369.           {name: 'page transition'},
    
  370.         );
    
  371. 
    
  372.         ReactNoop.expire(1000);
    
  373.         await advanceTimers(1000);
    
  374. 
    
  375.         await waitForAll([
    
  376.           'Page Two: show',
    
  377.           'onTransitionStart(page transition, 1000)',
    
  378.           'onTransitionComplete(page transition, 1000, 2000)',
    
  379.         ]);
    
  380.       });
    
  381.     });
    
  382.   });
    
  383. 
    
  384.   // @gate enableTransitionTracing
    
  385.   it('should correctly trace interactions for async roots', async () => {
    
  386.     const transitionCallbacks = {
    
  387.       onTransitionStart: (name, startTime) => {
    
  388.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  389.       },
    
  390.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  391.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  392.         Scheduler.log(
    
  393.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  394.         );
    
  395.       },
    
  396.       onTransitionComplete: (name, startTime, endTime) => {
    
  397.         Scheduler.log(
    
  398.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  399.         );
    
  400.       },
    
  401.     };
    
  402.     let navigateToPageTwo;
    
  403.     function App() {
    
  404.       const [navigate, setNavigate] = useState(false);
    
  405.       navigateToPageTwo = () => {
    
  406.         setNavigate(true);
    
  407.       };
    
  408. 
    
  409.       return (
    
  410.         <div>
    
  411.           {navigate ? (
    
  412.             <Suspense
    
  413.               fallback={<Text text="Loading..." />}
    
  414.               unstable_name="suspense page">
    
  415.               <AsyncText text="Page Two" />
    
  416.             </Suspense>
    
  417.           ) : (
    
  418.             <Text text="Page One" />
    
  419.           )}
    
  420.         </div>
    
  421.       );
    
  422.     }
    
  423. 
    
  424.     const root = ReactNoop.createRoot({
    
  425.       unstable_transitionCallbacks: transitionCallbacks,
    
  426.     });
    
  427.     await act(async () => {
    
  428.       root.render(<App />);
    
  429.       ReactNoop.expire(1000);
    
  430.       await advanceTimers(1000);
    
  431. 
    
  432.       await waitForAll(['Page One']);
    
  433.     });
    
  434. 
    
  435.     await act(async () => {
    
  436.       startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  437. 
    
  438.       ReactNoop.expire(1000);
    
  439.       await advanceTimers(1000);
    
  440. 
    
  441.       await waitForAll([
    
  442.         'Suspend [Page Two]',
    
  443.         'Loading...',
    
  444.         'onTransitionStart(page transition, 1000)',
    
  445.         'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
    
  446.       ]);
    
  447. 
    
  448.       ReactNoop.expire(1000);
    
  449.       await advanceTimers(1000);
    
  450.       await resolveText('Page Two');
    
  451. 
    
  452.       await waitForAll([
    
  453.         'Page Two',
    
  454.         'onTransitionProgress(page transition, 1000, 3000, [])',
    
  455.         'onTransitionComplete(page transition, 1000, 3000)',
    
  456.       ]);
    
  457.     });
    
  458.   });
    
  459. 
    
  460.   // @gate enableTransitionTracing
    
  461.   it('should correctly trace multiple separate root interactions', async () => {
    
  462.     const transitionCallbacks = {
    
  463.       onTransitionStart: (name, startTime) => {
    
  464.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  465.       },
    
  466.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  467.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  468.         Scheduler.log(
    
  469.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  470.         );
    
  471.       },
    
  472.       onTransitionComplete: (name, startTime, endTime) => {
    
  473.         Scheduler.log(
    
  474.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  475.         );
    
  476.       },
    
  477.     };
    
  478. 
    
  479.     let navigateToPageTwo;
    
  480.     let showTextFn;
    
  481.     function App() {
    
  482.       const [navigate, setNavigate] = useState(false);
    
  483.       const [showText, setShowText] = useState(false);
    
  484. 
    
  485.       navigateToPageTwo = () => {
    
  486.         setNavigate(true);
    
  487.       };
    
  488. 
    
  489.       showTextFn = () => {
    
  490.         setShowText(true);
    
  491.       };
    
  492. 
    
  493.       return (
    
  494.         <div>
    
  495.           {navigate ? (
    
  496.             <>
    
  497.               {showText ? (
    
  498.                 <Suspense
    
  499.                   unstable_name="show text"
    
  500.                   fallback={<Text text="Show Text Loading..." />}>
    
  501.                   <AsyncText text="Show Text" />
    
  502.                 </Suspense>
    
  503.               ) : null}
    
  504.               <Suspense
    
  505.                 fallback={<Text text="Loading..." />}
    
  506.                 unstable_name="suspense page">
    
  507.                 <AsyncText text="Page Two" />
    
  508.               </Suspense>
    
  509.             </>
    
  510.           ) : (
    
  511.             <Text text="Page One" />
    
  512.           )}
    
  513.         </div>
    
  514.       );
    
  515.     }
    
  516. 
    
  517.     const root = ReactNoop.createRoot({
    
  518.       unstable_transitionCallbacks: transitionCallbacks,
    
  519.     });
    
  520.     await act(async () => {
    
  521.       root.render(<App />);
    
  522.       ReactNoop.expire(1000);
    
  523.       await advanceTimers(1000);
    
  524. 
    
  525.       await waitForAll(['Page One']);
    
  526.     });
    
  527. 
    
  528.     await act(async () => {
    
  529.       startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  530. 
    
  531.       await waitForAll([
    
  532.         'Suspend [Page Two]',
    
  533.         'Loading...',
    
  534.         'onTransitionStart(page transition, 1000)',
    
  535.         'onTransitionProgress(page transition, 1000, 1000, [suspense page])',
    
  536.       ]);
    
  537. 
    
  538.       await resolveText('Page Two');
    
  539.       ReactNoop.expire(1000);
    
  540.       await advanceTimers(1000);
    
  541.       await waitForAll([
    
  542.         'Page Two',
    
  543.         'onTransitionProgress(page transition, 1000, 2000, [])',
    
  544.         'onTransitionComplete(page transition, 1000, 2000)',
    
  545.       ]);
    
  546. 
    
  547.       startTransition(() => showTextFn(), {name: 'text transition'});
    
  548.       await waitForAll([
    
  549.         'Suspend [Show Text]',
    
  550.         'Show Text Loading...',
    
  551.         'Page Two',
    
  552.         'onTransitionStart(text transition, 2000)',
    
  553.         'onTransitionProgress(text transition, 2000, 2000, [show text])',
    
  554.       ]);
    
  555. 
    
  556.       await resolveText('Show Text');
    
  557.       ReactNoop.expire(1000);
    
  558.       await advanceTimers(1000);
    
  559.       await waitForAll([
    
  560.         'Show Text',
    
  561.         'onTransitionProgress(text transition, 2000, 3000, [])',
    
  562.         'onTransitionComplete(text transition, 2000, 3000)',
    
  563.       ]);
    
  564.     });
    
  565.   });
    
  566. 
    
  567.   // @gate enableTransitionTracing
    
  568.   it('should correctly trace multiple intertwined root interactions', async () => {
    
  569.     const transitionCallbacks = {
    
  570.       onTransitionStart: (name, startTime) => {
    
  571.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  572.       },
    
  573.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  574.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  575.         Scheduler.log(
    
  576.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  577.         );
    
  578.       },
    
  579.       onTransitionComplete: (name, startTime, endTime) => {
    
  580.         Scheduler.log(
    
  581.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  582.         );
    
  583.       },
    
  584.     };
    
  585.     let navigateToPageTwo;
    
  586.     let showTextFn;
    
  587.     function App() {
    
  588.       const [navigate, setNavigate] = useState(false);
    
  589.       const [showText, setShowText] = useState(false);
    
  590.       navigateToPageTwo = () => {
    
  591.         setNavigate(true);
    
  592.       };
    
  593. 
    
  594.       showTextFn = () => {
    
  595.         setShowText(true);
    
  596.       };
    
  597. 
    
  598.       return (
    
  599.         <div>
    
  600.           {navigate ? (
    
  601.             <>
    
  602.               {showText ? (
    
  603.                 <Suspense
    
  604.                   unstable_name="show text"
    
  605.                   fallback={<Text text="Show Text Loading..." />}>
    
  606.                   <AsyncText text="Show Text" />
    
  607.                 </Suspense>
    
  608.               ) : null}
    
  609.               <Suspense
    
  610.                 fallback={<Text text="Loading..." />}
    
  611.                 unstable_name="suspense page">
    
  612.                 <AsyncText text="Page Two" />
    
  613.               </Suspense>
    
  614.             </>
    
  615.           ) : (
    
  616.             <Text text="Page One" />
    
  617.           )}
    
  618.         </div>
    
  619.       );
    
  620.     }
    
  621. 
    
  622.     const root = ReactNoop.createRoot({
    
  623.       unstable_transitionCallbacks: transitionCallbacks,
    
  624.     });
    
  625.     await act(async () => {
    
  626.       root.render(<App />);
    
  627.       ReactNoop.expire(1000);
    
  628.       await advanceTimers(1000);
    
  629. 
    
  630.       await waitForAll(['Page One']);
    
  631.     });
    
  632. 
    
  633.     await act(async () => {
    
  634.       startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  635.       ReactNoop.expire(1000);
    
  636.       await advanceTimers(1000);
    
  637. 
    
  638.       await waitForAll([
    
  639.         'Suspend [Page Two]',
    
  640.         'Loading...',
    
  641.         'onTransitionStart(page transition, 1000)',
    
  642.         'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
    
  643.       ]);
    
  644.     });
    
  645. 
    
  646.     await act(async () => {
    
  647.       startTransition(() => showTextFn(), {name: 'show text'});
    
  648. 
    
  649.       await waitForAll([
    
  650.         'Suspend [Show Text]',
    
  651.         'Show Text Loading...',
    
  652.         'Suspend [Page Two]',
    
  653.         'Loading...',
    
  654.         'onTransitionStart(show text, 2000)',
    
  655.         'onTransitionProgress(show text, 2000, 2000, [show text])',
    
  656.       ]);
    
  657.     });
    
  658. 
    
  659.     await act(async () => {
    
  660.       await resolveText('Page Two');
    
  661.       ReactNoop.expire(1000);
    
  662.       await advanceTimers(1000);
    
  663. 
    
  664.       await waitForAll([
    
  665.         'Page Two',
    
  666.         'onTransitionProgress(page transition, 1000, 3000, [])',
    
  667.         'onTransitionComplete(page transition, 1000, 3000)',
    
  668.       ]);
    
  669. 
    
  670.       await resolveText('Show Text');
    
  671.       ReactNoop.expire(1000);
    
  672.       await advanceTimers(1000);
    
  673. 
    
  674.       await waitForAll([
    
  675.         'Show Text',
    
  676.         'onTransitionProgress(show text, 2000, 4000, [])',
    
  677.         'onTransitionComplete(show text, 2000, 4000)',
    
  678.       ]);
    
  679.     });
    
  680.   });
    
  681. 
    
  682.   // @gate enableTransitionTracing
    
  683.   it('trace interaction with nested and sibling suspense boundaries', async () => {
    
  684.     const transitionCallbacks = {
    
  685.       onTransitionStart: (name, startTime) => {
    
  686.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  687.       },
    
  688.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  689.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  690.         Scheduler.log(
    
  691.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  692.         );
    
  693.       },
    
  694.       onTransitionComplete: (name, startTime, endTime) => {
    
  695.         Scheduler.log(
    
  696.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  697.         );
    
  698.       },
    
  699.     };
    
  700. 
    
  701.     let navigateToPageTwo;
    
  702.     function App() {
    
  703.       const [navigate, setNavigate] = useState(false);
    
  704.       navigateToPageTwo = () => {
    
  705.         setNavigate(true);
    
  706.       };
    
  707. 
    
  708.       return (
    
  709.         <div>
    
  710.           {navigate ? (
    
  711.             <>
    
  712.               <Suspense
    
  713.                 fallback={<Text text="Loading..." />}
    
  714.                 unstable_name="suspense page">
    
  715.                 <AsyncText text="Page Two" />
    
  716.                 <Suspense
    
  717.                   unstable_name="show text one"
    
  718.                   fallback={<Text text="Show Text One Loading..." />}>
    
  719.                   <AsyncText text="Show Text One" />
    
  720.                 </Suspense>
    
  721.                 <div>
    
  722.                   <Suspense
    
  723.                     unstable_name="show text two"
    
  724.                     fallback={<Text text="Show Text Two Loading..." />}>
    
  725.                     <AsyncText text="Show Text Two" />
    
  726.                   </Suspense>
    
  727.                 </div>
    
  728.               </Suspense>
    
  729.             </>
    
  730.           ) : (
    
  731.             <Text text="Page One" />
    
  732.           )}
    
  733.         </div>
    
  734.       );
    
  735.     }
    
  736. 
    
  737.     const root = ReactNoop.createRoot({
    
  738.       unstable_transitionCallbacks: transitionCallbacks,
    
  739.     });
    
  740.     await act(async () => {
    
  741.       root.render(<App />);
    
  742.       ReactNoop.expire(1000);
    
  743.       await advanceTimers(1000);
    
  744. 
    
  745.       await waitForAll(['Page One']);
    
  746.     });
    
  747. 
    
  748.     await act(async () => {
    
  749.       startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  750.       ReactNoop.expire(1000);
    
  751.       await advanceTimers(1000);
    
  752. 
    
  753.       await waitForAll([
    
  754.         'Suspend [Page Two]',
    
  755.         'Loading...',
    
  756.         'onTransitionStart(page transition, 1000)',
    
  757.         'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
    
  758.       ]);
    
  759. 
    
  760.       resolveText('Page Two');
    
  761.       ReactNoop.expire(1000);
    
  762.       await advanceTimers(1000);
    
  763. 
    
  764.       await waitForAll([
    
  765.         'Page Two',
    
  766.         'Suspend [Show Text One]',
    
  767.         'Show Text One Loading...',
    
  768.         'Suspend [Show Text Two]',
    
  769.         'Show Text Two Loading...',
    
  770.         'onTransitionProgress(page transition, 1000, 3000, [show text one, show text two])',
    
  771.       ]);
    
  772. 
    
  773.       resolveText('Show Text One');
    
  774.       ReactNoop.expire(1000);
    
  775.       await advanceTimers(1000);
    
  776. 
    
  777.       await waitForAll([
    
  778.         'Show Text One',
    
  779.         'onTransitionProgress(page transition, 1000, 4000, [show text two])',
    
  780.       ]);
    
  781. 
    
  782.       resolveText('Show Text Two');
    
  783.       ReactNoop.expire(1000);
    
  784.       await advanceTimers(1000);
    
  785. 
    
  786.       await waitForAll([
    
  787.         'Show Text Two',
    
  788.         'onTransitionProgress(page transition, 1000, 5000, [])',
    
  789.         'onTransitionComplete(page transition, 1000, 5000)',
    
  790.       ]);
    
  791.     });
    
  792.   });
    
  793. 
    
  794.   // @gate enableTransitionTracing
    
  795.   it('trace interactions with the same child suspense boundaries', async () => {
    
  796.     const transitionCallbacks = {
    
  797.       onTransitionStart: (name, startTime) => {
    
  798.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  799.       },
    
  800.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  801.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  802.         Scheduler.log(
    
  803.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  804.         );
    
  805.       },
    
  806.       onTransitionComplete: (name, startTime, endTime) => {
    
  807.         Scheduler.log(
    
  808.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  809.         );
    
  810.       },
    
  811.     };
    
  812. 
    
  813.     let setNavigate;
    
  814.     let setShowTextOne;
    
  815.     let setShowTextTwo;
    
  816.     function App() {
    
  817.       const [navigate, _setNavigate] = useState(false);
    
  818.       const [showTextOne, _setShowTextOne] = useState(false);
    
  819.       const [showTextTwo, _setShowTextTwo] = useState(false);
    
  820. 
    
  821.       setNavigate = () => _setNavigate(true);
    
  822.       setShowTextOne = () => _setShowTextOne(true);
    
  823.       setShowTextTwo = () => _setShowTextTwo(true);
    
  824. 
    
  825.       return (
    
  826.         <div>
    
  827.           {navigate ? (
    
  828.             <>
    
  829.               <Suspense
    
  830.                 fallback={<Text text="Loading..." />}
    
  831.                 unstable_name="suspense page">
    
  832.                 <AsyncText text="Page Two" />
    
  833.                 {/* showTextOne is entangled with navigate */}
    
  834.                 {showTextOne ? (
    
  835.                   <Suspense
    
  836.                     unstable_name="show text one"
    
  837.                     fallback={<Text text="Show Text One Loading..." />}>
    
  838.                     <AsyncText text="Show Text One" />
    
  839.                   </Suspense>
    
  840.                 ) : null}
    
  841.                 <Suspense fallback={<Text text="Show Text Loading..." />}>
    
  842.                   <AsyncText text="Show Text" />
    
  843.                 </Suspense>
    
  844.                 {/* showTextTwo's suspense boundaries shouldn't stop navigate's suspense boundaries
    
  845.                  from completing */}
    
  846.                 {showTextTwo ? (
    
  847.                   <Suspense
    
  848.                     unstable_name="show text two"
    
  849.                     fallback={<Text text="Show Text Two Loading..." />}>
    
  850.                     <AsyncText text="Show Text Two" />
    
  851.                   </Suspense>
    
  852.                 ) : null}
    
  853.               </Suspense>
    
  854.             </>
    
  855.           ) : (
    
  856.             <Text text="Page One" />
    
  857.           )}
    
  858.         </div>
    
  859.       );
    
  860.     }
    
  861. 
    
  862.     const root = ReactNoop.createRoot({
    
  863.       unstable_transitionCallbacks: transitionCallbacks,
    
  864.     });
    
  865.     await act(async () => {
    
  866.       root.render(<App />);
    
  867.       ReactNoop.expire(1000);
    
  868.       await advanceTimers(1000);
    
  869. 
    
  870.       await waitForAll(['Page One']);
    
  871.     });
    
  872. 
    
  873.     await act(async () => {
    
  874.       startTransition(() => setNavigate(), {name: 'navigate'});
    
  875.       startTransition(() => setShowTextOne(), {name: 'show text one'});
    
  876.       ReactNoop.expire(1000);
    
  877.       await advanceTimers(1000);
    
  878. 
    
  879.       await waitForAll([
    
  880.         'Suspend [Page Two]',
    
  881.         'Loading...',
    
  882.         'onTransitionStart(navigate, 1000)',
    
  883.         'onTransitionStart(show text one, 1000)',
    
  884.         'onTransitionProgress(navigate, 1000, 2000, [suspense page])',
    
  885.         'onTransitionProgress(show text one, 1000, 2000, [suspense page])',
    
  886.       ]);
    
  887. 
    
  888.       resolveText('Page Two');
    
  889.       ReactNoop.expire(1000);
    
  890.       await advanceTimers(1000);
    
  891.       await waitForAll([
    
  892.         'Page Two',
    
  893.         'Suspend [Show Text One]',
    
  894.         'Show Text One Loading...',
    
  895.         'Suspend [Show Text]',
    
  896.         'Show Text Loading...',
    
  897.         'onTransitionProgress(navigate, 1000, 3000, [show text one, <null>])',
    
  898.         'onTransitionProgress(show text one, 1000, 3000, [show text one, <null>])',
    
  899.       ]);
    
  900. 
    
  901.       startTransition(() => setShowTextTwo(), {name: 'show text two'});
    
  902.       ReactNoop.expire(1000);
    
  903.       await advanceTimers(1000);
    
  904. 
    
  905.       await waitForAll([
    
  906.         'Page Two',
    
  907.         'Suspend [Show Text One]',
    
  908.         'Show Text One Loading...',
    
  909.         'Suspend [Show Text]',
    
  910.         'Show Text Loading...',
    
  911.         'Suspend [Show Text Two]',
    
  912.         'Show Text Two Loading...',
    
  913.         'onTransitionStart(show text two, 3000)',
    
  914.         'onTransitionProgress(show text two, 3000, 4000, [show text two])',
    
  915.       ]);
    
  916. 
    
  917.       // This should not cause navigate to finish because it's entangled with
    
  918.       // show text one
    
  919.       resolveText('Show Text');
    
  920.       ReactNoop.expire(1000);
    
  921.       await advanceTimers(1000);
    
  922. 
    
  923.       await waitForAll([
    
  924.         'Show Text',
    
  925.         'onTransitionProgress(navigate, 1000, 5000, [show text one])',
    
  926.         'onTransitionProgress(show text one, 1000, 5000, [show text one])',
    
  927.       ]);
    
  928. 
    
  929.       // This should not cause show text two to finish but nothing else
    
  930.       resolveText('Show Text Two');
    
  931.       ReactNoop.expire(1000);
    
  932.       await advanceTimers(1000);
    
  933.       await waitForAll([
    
  934.         'Show Text Two',
    
  935.         'onTransitionProgress(show text two, 3000, 6000, [])',
    
  936.         'onTransitionComplete(show text two, 3000, 6000)',
    
  937.       ]);
    
  938. 
    
  939.       // This should cause everything to finish
    
  940.       resolveText('Show Text One');
    
  941.       ReactNoop.expire(1000);
    
  942.       await advanceTimers(1000);
    
  943. 
    
  944.       await waitForAll([
    
  945.         'Show Text One',
    
  946.         'onTransitionProgress(navigate, 1000, 7000, [])',
    
  947.         'onTransitionProgress(show text one, 1000, 7000, [])',
    
  948.         'onTransitionComplete(navigate, 1000, 7000)',
    
  949.         'onTransitionComplete(show text one, 1000, 7000)',
    
  950.       ]);
    
  951.     });
    
  952.   });
    
  953. 
    
  954.   // @gate enableTransitionTracing
    
  955.   it('should correctly trace basic interaction with tracing markers', async () => {
    
  956.     const transitionCallbacks = {
    
  957.       onTransitionStart: (name, startTime) => {
    
  958.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  959.       },
    
  960.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  961.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  962.         Scheduler.log(
    
  963.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  964.         );
    
  965.       },
    
  966.       onTransitionComplete: (name, startTime, endTime) => {
    
  967.         Scheduler.log(
    
  968.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  969.         );
    
  970.       },
    
  971.       onMarkerProgress: (
    
  972.         transitioName,
    
  973.         markerName,
    
  974.         startTime,
    
  975.         currentTime,
    
  976.         pending,
    
  977.       ) => {
    
  978.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  979.         Scheduler.log(
    
  980.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  981.         );
    
  982.       },
    
  983.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  984.         Scheduler.log(
    
  985.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  986.         );
    
  987.       },
    
  988.     };
    
  989. 
    
  990.     let navigateToPageTwo;
    
  991.     function App() {
    
  992.       const [navigate, setNavigate] = useState(false);
    
  993.       navigateToPageTwo = () => {
    
  994.         setNavigate(true);
    
  995.       };
    
  996. 
    
  997.       return (
    
  998.         <div>
    
  999.           {navigate ? (
    
  1000.             <React.unstable_TracingMarker name="marker two" key="marker two">
    
  1001.               <Text text="Page Two" />
    
  1002.             </React.unstable_TracingMarker>
    
  1003.           ) : (
    
  1004.             <React.unstable_TracingMarker name="marker one">
    
  1005.               <Text text="Page One" />
    
  1006.             </React.unstable_TracingMarker>
    
  1007.           )}
    
  1008.         </div>
    
  1009.       );
    
  1010.     }
    
  1011. 
    
  1012.     const root = ReactNoop.createRoot({
    
  1013.       unstable_transitionCallbacks: transitionCallbacks,
    
  1014.     });
    
  1015.     await act(async () => {
    
  1016.       root.render(<App />);
    
  1017.       ReactNoop.expire(1000);
    
  1018.       await advanceTimers(1000);
    
  1019. 
    
  1020.       await waitForAll(['Page One']);
    
  1021. 
    
  1022.       await act(async () => {
    
  1023.         startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  1024. 
    
  1025.         ReactNoop.expire(1000);
    
  1026.         await advanceTimers(1000);
    
  1027. 
    
  1028.         await waitForAll([
    
  1029.           'Page Two',
    
  1030.           'onTransitionStart(page transition, 1000)',
    
  1031.           'onMarkerComplete(page transition, marker two, 1000, 2000)',
    
  1032.           'onTransitionComplete(page transition, 1000, 2000)',
    
  1033.         ]);
    
  1034.       });
    
  1035.     });
    
  1036.   });
    
  1037. 
    
  1038.   // @gate enableTransitionTracing
    
  1039.   it('should correctly trace interactions for tracing markers', async () => {
    
  1040.     const transitionCallbacks = {
    
  1041.       onTransitionStart: (name, startTime) => {
    
  1042.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1043.       },
    
  1044.       onTransitionComplete: (name, startTime, endTime) => {
    
  1045.         Scheduler.log(
    
  1046.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1047.         );
    
  1048.       },
    
  1049.       onMarkerProgress: (
    
  1050.         transitioName,
    
  1051.         markerName,
    
  1052.         startTime,
    
  1053.         currentTime,
    
  1054.         pending,
    
  1055.       ) => {
    
  1056.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1057.         Scheduler.log(
    
  1058.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1059.         );
    
  1060.       },
    
  1061.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1062.         Scheduler.log(
    
  1063.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1064.         );
    
  1065.       },
    
  1066.     };
    
  1067.     let navigateToPageTwo;
    
  1068.     function App() {
    
  1069.       const [navigate, setNavigate] = useState(false);
    
  1070.       navigateToPageTwo = () => {
    
  1071.         setNavigate(true);
    
  1072.       };
    
  1073. 
    
  1074.       return (
    
  1075.         <div>
    
  1076.           {navigate ? (
    
  1077.             <Suspense
    
  1078.               fallback={<Text text="Loading..." />}
    
  1079.               unstable_name="suspense page">
    
  1080.               <AsyncText text="Page Two" />
    
  1081.               <React.unstable_TracingMarker name="sync marker" />
    
  1082.               <React.unstable_TracingMarker name="async marker">
    
  1083.                 <Suspense
    
  1084.                   fallback={<Text text="Loading..." />}
    
  1085.                   unstable_name="marker suspense">
    
  1086.                   <AsyncText text="Marker Text" />
    
  1087.                 </Suspense>
    
  1088.               </React.unstable_TracingMarker>
    
  1089.             </Suspense>
    
  1090.           ) : (
    
  1091.             <Text text="Page One" />
    
  1092.           )}
    
  1093.         </div>
    
  1094.       );
    
  1095.     }
    
  1096. 
    
  1097.     const root = ReactNoop.createRoot({
    
  1098.       unstable_transitionCallbacks: transitionCallbacks,
    
  1099.     });
    
  1100.     await act(async () => {
    
  1101.       root.render(<App />);
    
  1102.       ReactNoop.expire(1000);
    
  1103.       await advanceTimers(1000);
    
  1104. 
    
  1105.       await waitForAll(['Page One']);
    
  1106.     });
    
  1107. 
    
  1108.     await act(async () => {
    
  1109.       startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  1110. 
    
  1111.       ReactNoop.expire(1000);
    
  1112.       await advanceTimers(1000);
    
  1113. 
    
  1114.       await waitForAll([
    
  1115.         'Suspend [Page Two]',
    
  1116.         'Loading...',
    
  1117.         'onTransitionStart(page transition, 1000)',
    
  1118.       ]);
    
  1119. 
    
  1120.       ReactNoop.expire(1000);
    
  1121.       await advanceTimers(1000);
    
  1122.       await resolveText('Page Two');
    
  1123. 
    
  1124.       await waitForAll([
    
  1125.         'Page Two',
    
  1126.         'Suspend [Marker Text]',
    
  1127.         'Loading...',
    
  1128.         'onMarkerProgress(page transition, async marker, 1000, 3000, [marker suspense])',
    
  1129.         'onMarkerComplete(page transition, sync marker, 1000, 3000)',
    
  1130.       ]);
    
  1131. 
    
  1132.       ReactNoop.expire(1000);
    
  1133.       await advanceTimers(1000);
    
  1134.       await resolveText('Marker Text');
    
  1135. 
    
  1136.       await waitForAll([
    
  1137.         'Marker Text',
    
  1138.         'onMarkerProgress(page transition, async marker, 1000, 4000, [])',
    
  1139.         'onMarkerComplete(page transition, async marker, 1000, 4000)',
    
  1140.         'onTransitionComplete(page transition, 1000, 4000)',
    
  1141.       ]);
    
  1142.     });
    
  1143.   });
    
  1144. 
    
  1145.   // @gate enableTransitionTracing
    
  1146.   it('trace interaction with multiple tracing markers', async () => {
    
  1147.     const transitionCallbacks = {
    
  1148.       onTransitionStart: (name, startTime) => {
    
  1149.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1150.       },
    
  1151.       onTransitionComplete: (name, startTime, endTime) => {
    
  1152.         Scheduler.log(
    
  1153.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1154.         );
    
  1155.       },
    
  1156.       onMarkerProgress: (
    
  1157.         transitioName,
    
  1158.         markerName,
    
  1159.         startTime,
    
  1160.         currentTime,
    
  1161.         pending,
    
  1162.       ) => {
    
  1163.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1164.         Scheduler.log(
    
  1165.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1166.         );
    
  1167.       },
    
  1168.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1169.         Scheduler.log(
    
  1170.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1171.         );
    
  1172.       },
    
  1173.     };
    
  1174. 
    
  1175.     let navigateToPageTwo;
    
  1176.     function App() {
    
  1177.       const [navigate, setNavigate] = useState(false);
    
  1178.       navigateToPageTwo = () => {
    
  1179.         setNavigate(true);
    
  1180.       };
    
  1181. 
    
  1182.       return (
    
  1183.         <div>
    
  1184.           {navigate ? (
    
  1185.             <React.unstable_TracingMarker name="outer marker">
    
  1186.               <Suspense
    
  1187.                 fallback={<Text text="Outer..." />}
    
  1188.                 unstable_name="outer">
    
  1189.                 <AsyncText text="Outer Text" />
    
  1190.                 <Suspense
    
  1191.                   fallback={<Text text="Inner One..." />}
    
  1192.                   unstable_name="inner one">
    
  1193.                   <React.unstable_TracingMarker name="marker one">
    
  1194.                     <AsyncText text="Inner Text One" />
    
  1195.                   </React.unstable_TracingMarker>
    
  1196.                 </Suspense>
    
  1197.                 <Suspense
    
  1198.                   fallback={<Text text="Inner Two..." />}
    
  1199.                   unstable_name="inner two">
    
  1200.                   <React.unstable_TracingMarker name="marker two">
    
  1201.                     <AsyncText text="Inner Text Two" />
    
  1202.                   </React.unstable_TracingMarker>
    
  1203.                 </Suspense>
    
  1204.               </Suspense>
    
  1205.             </React.unstable_TracingMarker>
    
  1206.           ) : (
    
  1207.             <Text text="Page One" />
    
  1208.           )}
    
  1209.         </div>
    
  1210.       );
    
  1211.     }
    
  1212. 
    
  1213.     const root = ReactNoop.createRoot({
    
  1214.       unstable_transitionCallbacks: transitionCallbacks,
    
  1215.     });
    
  1216.     await act(async () => {
    
  1217.       root.render(<App />);
    
  1218.       ReactNoop.expire(1000);
    
  1219.       await advanceTimers(1000);
    
  1220. 
    
  1221.       await waitForAll(['Page One']);
    
  1222.     });
    
  1223. 
    
  1224.     await act(async () => {
    
  1225.       startTransition(() => navigateToPageTwo(), {name: 'page transition'});
    
  1226. 
    
  1227.       ReactNoop.expire(1000);
    
  1228.       await advanceTimers(1000);
    
  1229. 
    
  1230.       await waitForAll([
    
  1231.         'Suspend [Outer Text]',
    
  1232.         'Outer...',
    
  1233.         'onTransitionStart(page transition, 1000)',
    
  1234.         'onMarkerProgress(page transition, outer marker, 1000, 2000, [outer])',
    
  1235.       ]);
    
  1236. 
    
  1237.       ReactNoop.expire(1000);
    
  1238.       await advanceTimers(1000);
    
  1239.       await resolveText('Inner Text Two');
    
  1240.       await waitForAll([]);
    
  1241. 
    
  1242.       ReactNoop.expire(1000);
    
  1243.       await advanceTimers(1000);
    
  1244.       await resolveText('Outer Text');
    
  1245.       await waitForAll([
    
  1246.         'Outer Text',
    
  1247.         'Suspend [Inner Text One]',
    
  1248.         'Inner One...',
    
  1249.         'Inner Text Two',
    
  1250.         'onMarkerProgress(page transition, outer marker, 1000, 4000, [inner one])',
    
  1251.         'onMarkerComplete(page transition, marker two, 1000, 4000)',
    
  1252.       ]);
    
  1253. 
    
  1254.       ReactNoop.expire(1000);
    
  1255.       await advanceTimers(1000);
    
  1256.       await resolveText('Inner Text One');
    
  1257.       await waitForAll([
    
  1258.         'Inner Text One',
    
  1259.         'onMarkerProgress(page transition, outer marker, 1000, 5000, [])',
    
  1260.         'onMarkerComplete(page transition, marker one, 1000, 5000)',
    
  1261.         'onMarkerComplete(page transition, outer marker, 1000, 5000)',
    
  1262.         'onTransitionComplete(page transition, 1000, 5000)',
    
  1263.       ]);
    
  1264.     });
    
  1265.   });
    
  1266. 
    
  1267.   // @gate enableTransitionTracing
    
  1268.   it.skip('warn and calls marker incomplete if name changes before transition completes', async () => {
    
  1269.     const transitionCallbacks = {
    
  1270.       onTransitionStart: (name, startTime) => {
    
  1271.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1272.       },
    
  1273.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  1274.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1275.         Scheduler.log(
    
  1276.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  1277.         );
    
  1278.       },
    
  1279.       onTransitionComplete: (name, startTime, endTime) => {
    
  1280.         Scheduler.log(
    
  1281.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1282.         );
    
  1283.       },
    
  1284.       onMarkerProgress: (
    
  1285.         transitioName,
    
  1286.         markerName,
    
  1287.         startTime,
    
  1288.         currentTime,
    
  1289.         pending,
    
  1290.       ) => {
    
  1291.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1292.         Scheduler.log(
    
  1293.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1294.         );
    
  1295.       },
    
  1296.       onMarkerIncomplete: (
    
  1297.         transitionName,
    
  1298.         markerName,
    
  1299.         startTime,
    
  1300.         deletions,
    
  1301.       ) => {
    
  1302.         Scheduler.log(
    
  1303.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  1304.             deletions,
    
  1305.           )}])`,
    
  1306.         );
    
  1307.       },
    
  1308.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1309.         Scheduler.log(
    
  1310.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1311.         );
    
  1312.       },
    
  1313.     };
    
  1314. 
    
  1315.     function App({navigate, markerName}) {
    
  1316.       return (
    
  1317.         <div>
    
  1318.           {navigate ? (
    
  1319.             <React.unstable_TracingMarker name={markerName}>
    
  1320.               <Suspense fallback={<Text text="Loading..." />}>
    
  1321.                 <AsyncText text="Page Two" />
    
  1322.               </Suspense>
    
  1323.             </React.unstable_TracingMarker>
    
  1324.           ) : (
    
  1325.             <Text text="Page One" />
    
  1326.           )}
    
  1327.         </div>
    
  1328.       );
    
  1329.     }
    
  1330. 
    
  1331.     const root = ReactNoop.createRoot({
    
  1332.       unstable_transitionCallbacks: transitionCallbacks,
    
  1333.     });
    
  1334.     await act(async () => {
    
  1335.       root.render(<App navigate={false} markerName="marker one" />);
    
  1336.       ReactNoop.expire(1000);
    
  1337.       await advanceTimers(1000);
    
  1338.       await waitForAll(['Page One']);
    
  1339. 
    
  1340.       startTransition(
    
  1341.         () => root.render(<App navigate={true} markerName="marker one" />),
    
  1342.         {
    
  1343.           name: 'transition one',
    
  1344.         },
    
  1345.       );
    
  1346.       ReactNoop.expire(1000);
    
  1347.       await advanceTimers(1000);
    
  1348. 
    
  1349.       await waitForAll([
    
  1350.         'Suspend [Page Two]',
    
  1351.         'Loading...',
    
  1352.         'onTransitionStart(transition one, 1000)',
    
  1353.         'onMarkerProgress(transition one, marker one, 1000, 2000, [<null>])',
    
  1354.         'onTransitionProgress(transition one, 1000, 2000, [<null>])',
    
  1355.       ]);
    
  1356. 
    
  1357.       root.render(<App navigate={true} markerName="marker two" />);
    
  1358.       ReactNoop.expire(1000);
    
  1359.       await advanceTimers(1000);
    
  1360.       await expect(
    
  1361.         async () =>
    
  1362.           await waitForAll([
    
  1363.             'Suspend [Page Two]',
    
  1364.             'Loading...',
    
  1365.             'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, newName: marker two, type: marker}])',
    
  1366.           ]),
    
  1367.       ).toErrorDev('');
    
  1368. 
    
  1369.       resolveText('Page Two');
    
  1370.       ReactNoop.expire(1000);
    
  1371.       await advanceTimers(1000);
    
  1372.       await waitForAll([
    
  1373.         'Page Two',
    
  1374.         'onMarkerProgress(transition one, marker one, 1000, 4000, [])',
    
  1375.         'onTransitionProgress(transition one, 1000, 4000, [])',
    
  1376.         'onTransitionComplete(transition one, 1000, 4000)',
    
  1377.       ]);
    
  1378.     });
    
  1379.   });
    
  1380. 
    
  1381.   // @gate enableTransitionTracing
    
  1382.   it('marker incomplete for tree with parent and sibling tracing markers', async () => {
    
  1383.     const transitionCallbacks = {
    
  1384.       onTransitionStart: (name, startTime) => {
    
  1385.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1386.       },
    
  1387.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  1388.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1389.         Scheduler.log(
    
  1390.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  1391.         );
    
  1392.       },
    
  1393.       onTransitionComplete: (name, startTime, endTime) => {
    
  1394.         Scheduler.log(
    
  1395.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1396.         );
    
  1397.       },
    
  1398.       onMarkerProgress: (
    
  1399.         transitioName,
    
  1400.         markerName,
    
  1401.         startTime,
    
  1402.         currentTime,
    
  1403.         pending,
    
  1404.       ) => {
    
  1405.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1406.         Scheduler.log(
    
  1407.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1408.         );
    
  1409.       },
    
  1410.       onMarkerIncomplete: (
    
  1411.         transitionName,
    
  1412.         markerName,
    
  1413.         startTime,
    
  1414.         deletions,
    
  1415.       ) => {
    
  1416.         Scheduler.log(
    
  1417.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  1418.             deletions,
    
  1419.           )}])`,
    
  1420.         );
    
  1421.       },
    
  1422.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1423.         Scheduler.log(
    
  1424.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1425.         );
    
  1426.       },
    
  1427.     };
    
  1428. 
    
  1429.     function App({navigate, showMarker}) {
    
  1430.       return (
    
  1431.         <div>
    
  1432.           {navigate ? (
    
  1433.             <React.unstable_TracingMarker name="parent">
    
  1434.               {showMarker ? (
    
  1435.                 <React.unstable_TracingMarker name="marker one">
    
  1436.                   <Suspense
    
  1437.                     unstable_name="suspense page"
    
  1438.                     fallback={<Text text="Loading..." />}>
    
  1439.                     <AsyncText text="Page Two" />
    
  1440.                   </Suspense>
    
  1441.                 </React.unstable_TracingMarker>
    
  1442.               ) : (
    
  1443.                 <Suspense
    
  1444.                   unstable_name="suspense page"
    
  1445.                   fallback={<Text text="Loading..." />}>
    
  1446.                   <AsyncText text="Page Two" />
    
  1447.                 </Suspense>
    
  1448.               )}
    
  1449.               <React.unstable_TracingMarker name="sibling">
    
  1450.                 <Suspense
    
  1451.                   unstable_name="suspense sibling"
    
  1452.                   fallback={<Text text="Sibling Loading..." />}>
    
  1453.                   <AsyncText text="Sibling Text" />
    
  1454.                 </Suspense>
    
  1455.               </React.unstable_TracingMarker>
    
  1456.             </React.unstable_TracingMarker>
    
  1457.           ) : (
    
  1458.             <Text text="Page One" />
    
  1459.           )}
    
  1460.         </div>
    
  1461.       );
    
  1462.     }
    
  1463. 
    
  1464.     const root = ReactNoop.createRoot({
    
  1465.       unstable_transitionCallbacks: transitionCallbacks,
    
  1466.     });
    
  1467.     await act(async () => {
    
  1468.       root.render(<App navigate={false} showMarker={true} />);
    
  1469.       ReactNoop.expire(1000);
    
  1470.       await advanceTimers(1000);
    
  1471.       await waitForAll(['Page One']);
    
  1472. 
    
  1473.       startTransition(
    
  1474.         () => root.render(<App navigate={true} showMarker={true} />),
    
  1475.         {
    
  1476.           name: 'transition one',
    
  1477.         },
    
  1478.       );
    
  1479.       ReactNoop.expire(1000);
    
  1480.       await advanceTimers(1000);
    
  1481.       await waitForAll([
    
  1482.         'Suspend [Page Two]',
    
  1483.         'Loading...',
    
  1484.         'Suspend [Sibling Text]',
    
  1485.         'Sibling Loading...',
    
  1486.         'onTransitionStart(transition one, 1000)',
    
  1487.         'onMarkerProgress(transition one, parent, 1000, 2000, [suspense page, suspense sibling])',
    
  1488.         'onMarkerProgress(transition one, marker one, 1000, 2000, [suspense page])',
    
  1489.         'onMarkerProgress(transition one, sibling, 1000, 2000, [suspense sibling])',
    
  1490.         'onTransitionProgress(transition one, 1000, 2000, [suspense page, suspense sibling])',
    
  1491.       ]);
    
  1492.       root.render(<App navigate={true} showMarker={false} />);
    
  1493. 
    
  1494.       ReactNoop.expire(1000);
    
  1495.       await advanceTimers(1000);
    
  1496.       await waitForAll([
    
  1497.         'Suspend [Page Two]',
    
  1498.         'Loading...',
    
  1499.         'Suspend [Sibling Text]',
    
  1500.         'Sibling Loading...',
    
  1501.         'onMarkerProgress(transition one, parent, 1000, 3000, [suspense sibling])',
    
  1502.         'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
    
  1503.         'onMarkerIncomplete(transition one, parent, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
    
  1504.       ]);
    
  1505. 
    
  1506.       root.render(<App navigate={true} showMarker={true} />);
    
  1507.       ReactNoop.expire(1000);
    
  1508.       await advanceTimers(1000);
    
  1509.       await waitForAll([
    
  1510.         'Suspend [Page Two]',
    
  1511.         'Loading...',
    
  1512.         'Suspend [Sibling Text]',
    
  1513.         'Sibling Loading...',
    
  1514.       ]);
    
  1515.     });
    
  1516. 
    
  1517.     resolveText('Page Two');
    
  1518.     ReactNoop.expire(1000);
    
  1519.     await advanceTimers(1000);
    
  1520.     await waitForAll(['Page Two']);
    
  1521. 
    
  1522.     resolveText('Sibling Text');
    
  1523.     ReactNoop.expire(1000);
    
  1524.     await advanceTimers(1000);
    
  1525.     await waitForAll([
    
  1526.       'Sibling Text',
    
  1527.       'onMarkerProgress(transition one, parent, 1000, 6000, [])',
    
  1528.       'onMarkerProgress(transition one, sibling, 1000, 6000, [])',
    
  1529.       // Calls markerComplete and transitionComplete for all parents
    
  1530.       'onMarkerComplete(transition one, sibling, 1000, 6000)',
    
  1531.       'onTransitionProgress(transition one, 1000, 6000, [])',
    
  1532.     ]);
    
  1533.   });
    
  1534. 
    
  1535.   // @gate enableTransitionTracing
    
  1536.   it('marker gets deleted', async () => {
    
  1537.     const transitionCallbacks = {
    
  1538.       onTransitionStart: (name, startTime) => {
    
  1539.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1540.       },
    
  1541.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  1542.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1543.         Scheduler.log(
    
  1544.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  1545.         );
    
  1546.       },
    
  1547.       onTransitionComplete: (name, startTime, endTime) => {
    
  1548.         Scheduler.log(
    
  1549.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1550.         );
    
  1551.       },
    
  1552.       onMarkerProgress: (
    
  1553.         transitioName,
    
  1554.         markerName,
    
  1555.         startTime,
    
  1556.         currentTime,
    
  1557.         pending,
    
  1558.       ) => {
    
  1559.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1560.         Scheduler.log(
    
  1561.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1562.         );
    
  1563.       },
    
  1564.       onMarkerIncomplete: (
    
  1565.         transitionName,
    
  1566.         markerName,
    
  1567.         startTime,
    
  1568.         deletions,
    
  1569.       ) => {
    
  1570.         Scheduler.log(
    
  1571.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  1572.             deletions,
    
  1573.           )}])`,
    
  1574.         );
    
  1575.       },
    
  1576.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1577.         Scheduler.log(
    
  1578.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1579.         );
    
  1580.       },
    
  1581.     };
    
  1582. 
    
  1583.     function App({navigate, deleteOne}) {
    
  1584.       return (
    
  1585.         <div>
    
  1586.           {navigate ? (
    
  1587.             <React.unstable_TracingMarker name="parent">
    
  1588.               {!deleteOne ? (
    
  1589.                 <div>
    
  1590.                   <React.unstable_TracingMarker name="one">
    
  1591.                     <Suspense
    
  1592.                       unstable_name="suspense one"
    
  1593.                       fallback={<Text text="Loading One..." />}>
    
  1594.                       <AsyncText text="Page One" />
    
  1595.                     </Suspense>
    
  1596.                   </React.unstable_TracingMarker>
    
  1597.                 </div>
    
  1598.               ) : null}
    
  1599.               <React.unstable_TracingMarker name="two">
    
  1600.                 <Suspense
    
  1601.                   unstable_name="suspense two"
    
  1602.                   fallback={<Text text="Loading Two..." />}>
    
  1603.                   <AsyncText text="Page Two" />
    
  1604.                 </Suspense>
    
  1605.               </React.unstable_TracingMarker>
    
  1606.             </React.unstable_TracingMarker>
    
  1607.           ) : (
    
  1608.             <Text text="Page One" />
    
  1609.           )}
    
  1610.         </div>
    
  1611.       );
    
  1612.     }
    
  1613.     const root = ReactNoop.createRoot({
    
  1614.       unstable_transitionCallbacks: transitionCallbacks,
    
  1615.     });
    
  1616.     await act(async () => {
    
  1617.       root.render(<App navigate={false} deleteOne={false} />);
    
  1618.       ReactNoop.expire(1000);
    
  1619.       await advanceTimers(1000);
    
  1620.       await waitForAll(['Page One']);
    
  1621. 
    
  1622.       startTransition(
    
  1623.         () => root.render(<App navigate={true} deleteOne={false} />),
    
  1624.         {
    
  1625.           name: 'transition',
    
  1626.         },
    
  1627.       );
    
  1628.       ReactNoop.expire(1000);
    
  1629.       await advanceTimers(1000);
    
  1630.       await waitForAll([
    
  1631.         'Suspend [Page One]',
    
  1632.         'Loading One...',
    
  1633.         'Suspend [Page Two]',
    
  1634.         'Loading Two...',
    
  1635.         'onTransitionStart(transition, 1000)',
    
  1636.         'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
    
  1637.         'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
    
  1638.         'onMarkerProgress(transition, two, 1000, 2000, [suspense two])',
    
  1639.         'onTransitionProgress(transition, 1000, 2000, [suspense one, suspense two])',
    
  1640.       ]);
    
  1641. 
    
  1642.       root.render(<App navigate={true} deleteOne={true} />);
    
  1643.       ReactNoop.expire(1000);
    
  1644.       await advanceTimers(1000);
    
  1645.       await waitForAll([
    
  1646.         'Suspend [Page Two]',
    
  1647.         'Loading Two...',
    
  1648.         'onMarkerProgress(transition, parent, 1000, 3000, [suspense two])',
    
  1649.         'onMarkerIncomplete(transition, one, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
    
  1650.         'onMarkerIncomplete(transition, parent, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
    
  1651.       ]);
    
  1652. 
    
  1653.       await resolveText('Page Two');
    
  1654.       ReactNoop.expire(1000);
    
  1655.       await advanceTimers(1000);
    
  1656.       await waitForAll([
    
  1657.         'Page Two',
    
  1658.         // Marker progress will still get called after incomplete but not marker complete
    
  1659.         'onMarkerProgress(transition, parent, 1000, 4000, [])',
    
  1660.         'onMarkerProgress(transition, two, 1000, 4000, [])',
    
  1661.         'onMarkerComplete(transition, two, 1000, 4000)',
    
  1662.         // Transition progress will still get called after incomplete but not transition complete
    
  1663.         'onTransitionProgress(transition, 1000, 4000, [])',
    
  1664.       ]);
    
  1665.     });
    
  1666.   });
    
  1667. 
    
  1668.   // @gate enableTransitionTracing
    
  1669.   it('Suspense boundary added by the transition is deleted', async () => {
    
  1670.     const transitionCallbacks = {
    
  1671.       onTransitionStart: (name, startTime) => {
    
  1672.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1673.       },
    
  1674.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  1675.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1676.         Scheduler.log(
    
  1677.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  1678.         );
    
  1679.       },
    
  1680.       onTransitionComplete: (name, startTime, endTime) => {
    
  1681.         Scheduler.log(
    
  1682.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1683.         );
    
  1684.       },
    
  1685.       onMarkerProgress: (
    
  1686.         transitioName,
    
  1687.         markerName,
    
  1688.         startTime,
    
  1689.         currentTime,
    
  1690.         pending,
    
  1691.       ) => {
    
  1692.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1693.         Scheduler.log(
    
  1694.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1695.         );
    
  1696.       },
    
  1697.       onMarkerIncomplete: (
    
  1698.         transitionName,
    
  1699.         markerName,
    
  1700.         startTime,
    
  1701.         deletions,
    
  1702.       ) => {
    
  1703.         Scheduler.log(
    
  1704.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  1705.             deletions,
    
  1706.           )}])`,
    
  1707.         );
    
  1708.       },
    
  1709.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1710.         Scheduler.log(
    
  1711.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1712.         );
    
  1713.       },
    
  1714.     };
    
  1715. 
    
  1716.     function App({navigate, deleteOne}) {
    
  1717.       return (
    
  1718.         <div>
    
  1719.           {navigate ? (
    
  1720.             <React.unstable_TracingMarker name="parent">
    
  1721.               <React.unstable_TracingMarker name="one">
    
  1722.                 {!deleteOne ? (
    
  1723.                   <Suspense
    
  1724.                     unstable_name="suspense one"
    
  1725.                     fallback={<Text text="Loading One..." />}>
    
  1726.                     <AsyncText text="Page One" />
    
  1727.                     <React.unstable_TracingMarker name="page one" />
    
  1728.                     <Suspense
    
  1729.                       unstable_name="suspense child"
    
  1730.                       fallback={<Text text="Loading Child..." />}>
    
  1731.                       <React.unstable_TracingMarker name="child" />
    
  1732.                       <AsyncText text="Child" />
    
  1733.                     </Suspense>
    
  1734.                   </Suspense>
    
  1735.                 ) : null}
    
  1736.               </React.unstable_TracingMarker>
    
  1737.               <React.unstable_TracingMarker name="two">
    
  1738.                 <Suspense
    
  1739.                   unstable_name="suspense two"
    
  1740.                   fallback={<Text text="Loading Two..." />}>
    
  1741.                   <AsyncText text="Page Two" />
    
  1742.                 </Suspense>
    
  1743.               </React.unstable_TracingMarker>
    
  1744.             </React.unstable_TracingMarker>
    
  1745.           ) : (
    
  1746.             <Text text="Page One" />
    
  1747.           )}
    
  1748.         </div>
    
  1749.       );
    
  1750.     }
    
  1751.     const root = ReactNoop.createRoot({
    
  1752.       unstable_transitionCallbacks: transitionCallbacks,
    
  1753.     });
    
  1754.     await act(async () => {
    
  1755.       root.render(<App navigate={false} deleteOne={false} />);
    
  1756. 
    
  1757.       ReactNoop.expire(1000);
    
  1758.       await advanceTimers(1000);
    
  1759.       await waitForAll(['Page One']);
    
  1760. 
    
  1761.       startTransition(
    
  1762.         () => root.render(<App navigate={true} deleteOne={false} />),
    
  1763.         {
    
  1764.           name: 'transition',
    
  1765.         },
    
  1766.       );
    
  1767.       ReactNoop.expire(1000);
    
  1768.       await advanceTimers(1000);
    
  1769.       await waitForAll([
    
  1770.         'Suspend [Page One]',
    
  1771.         'Loading One...',
    
  1772.         'Suspend [Page Two]',
    
  1773.         'Loading Two...',
    
  1774.         'onTransitionStart(transition, 1000)',
    
  1775.         'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
    
  1776.         'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
    
  1777.         'onMarkerProgress(transition, two, 1000, 2000, [suspense two])',
    
  1778.         'onTransitionProgress(transition, 1000, 2000, [suspense one, suspense two])',
    
  1779.       ]);
    
  1780. 
    
  1781.       await resolveText('Page One');
    
  1782.       ReactNoop.expire(1000);
    
  1783.       await advanceTimers(1000);
    
  1784.       await waitForAll([
    
  1785.         'Page One',
    
  1786.         'Suspend [Child]',
    
  1787.         'Loading Child...',
    
  1788.         'onMarkerProgress(transition, parent, 1000, 3000, [suspense two, suspense child])',
    
  1789.         'onMarkerProgress(transition, one, 1000, 3000, [suspense child])',
    
  1790.         'onMarkerComplete(transition, page one, 1000, 3000)',
    
  1791.         'onTransitionProgress(transition, 1000, 3000, [suspense two, suspense child])',
    
  1792.       ]);
    
  1793. 
    
  1794.       root.render(<App navigate={true} deleteOne={true} />);
    
  1795.       ReactNoop.expire(1000);
    
  1796.       await advanceTimers(1000);
    
  1797.       await waitForAll([
    
  1798.         'Suspend [Page Two]',
    
  1799.         'Loading Two...',
    
  1800.         // "suspense one" has unsuspended so shouldn't be included
    
  1801.         // tracing marker "page one" has completed so shouldn't be included
    
  1802.         // all children of "suspense child" haven't yet been rendered so shouldn't be included
    
  1803.         'onMarkerProgress(transition, one, 1000, 4000, [])',
    
  1804.         'onMarkerProgress(transition, parent, 1000, 4000, [suspense two])',
    
  1805.         'onMarkerIncomplete(transition, one, 1000, [{endTime: 4000, name: suspense child, type: suspense}])',
    
  1806.         'onMarkerIncomplete(transition, parent, 1000, [{endTime: 4000, name: suspense child, type: suspense}])',
    
  1807.       ]);
    
  1808. 
    
  1809.       await resolveText('Page Two');
    
  1810.       ReactNoop.expire(1000);
    
  1811.       await advanceTimers(1000);
    
  1812.       await waitForAll([
    
  1813.         'Page Two',
    
  1814.         'onMarkerProgress(transition, parent, 1000, 5000, [])',
    
  1815.         'onMarkerProgress(transition, two, 1000, 5000, [])',
    
  1816.         'onMarkerComplete(transition, two, 1000, 5000)',
    
  1817.         'onTransitionProgress(transition, 1000, 5000, [])',
    
  1818.       ]);
    
  1819.     });
    
  1820.   });
    
  1821. 
    
  1822.   // @gate enableTransitionTracing
    
  1823.   it('Suspense boundary not added by the transition is deleted ', async () => {
    
  1824.     const transitionCallbacks = {
    
  1825.       onTransitionStart: (name, startTime) => {
    
  1826.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1827.       },
    
  1828.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  1829.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1830.         Scheduler.log(
    
  1831.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  1832.         );
    
  1833.       },
    
  1834.       onTransitionComplete: (name, startTime, endTime) => {
    
  1835.         Scheduler.log(
    
  1836.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1837.         );
    
  1838.       },
    
  1839.       onMarkerProgress: (
    
  1840.         transitioName,
    
  1841.         markerName,
    
  1842.         startTime,
    
  1843.         currentTime,
    
  1844.         pending,
    
  1845.       ) => {
    
  1846.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1847.         Scheduler.log(
    
  1848.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1849.         );
    
  1850.       },
    
  1851.       onMarkerIncomplete: (
    
  1852.         transitionName,
    
  1853.         markerName,
    
  1854.         startTime,
    
  1855.         deletions,
    
  1856.       ) => {
    
  1857.         Scheduler.log(
    
  1858.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  1859.             deletions,
    
  1860.           )}])`,
    
  1861.         );
    
  1862.       },
    
  1863.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1864.         Scheduler.log(
    
  1865.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1866.         );
    
  1867.       },
    
  1868.     };
    
  1869. 
    
  1870.     function App({show}) {
    
  1871.       return (
    
  1872.         <React.unstable_TracingMarker name="parent">
    
  1873.           {show ? (
    
  1874.             <Suspense unstable_name="appended child">
    
  1875.               <AsyncText text="Appended child" />
    
  1876.             </Suspense>
    
  1877.           ) : null}
    
  1878.           <Suspense unstable_name="child">
    
  1879.             <AsyncText text="Child" />
    
  1880.           </Suspense>
    
  1881.         </React.unstable_TracingMarker>
    
  1882.       );
    
  1883.     }
    
  1884. 
    
  1885.     const root = ReactNoop.createRoot({
    
  1886.       unstable_transitionCallbacks: transitionCallbacks,
    
  1887.     });
    
  1888.     await act(async () => {
    
  1889.       startTransition(() => root.render(<App show={false} />), {
    
  1890.         name: 'transition',
    
  1891.       });
    
  1892.       ReactNoop.expire(1000);
    
  1893.       await advanceTimers(1000);
    
  1894. 
    
  1895.       await waitForAll([
    
  1896.         'Suspend [Child]',
    
  1897.         'onTransitionStart(transition, 0)',
    
  1898.         'onMarkerProgress(transition, parent, 0, 1000, [child])',
    
  1899.         'onTransitionProgress(transition, 0, 1000, [child])',
    
  1900.       ]);
    
  1901. 
    
  1902.       root.render(<App show={true} />);
    
  1903.       ReactNoop.expire(1000);
    
  1904.       await advanceTimers(1000);
    
  1905.       // This appended child isn't part of the transition so we
    
  1906.       // don't call any callback
    
  1907.       await waitForAll(['Suspend [Appended child]', 'Suspend [Child]']);
    
  1908. 
    
  1909.       // This deleted child isn't part of the transition so we
    
  1910.       // don't call any callbacks
    
  1911.       root.render(<App show={false} />);
    
  1912.       ReactNoop.expire(1000);
    
  1913.       await advanceTimers(1000);
    
  1914.       await waitForAll(['Suspend [Child]']);
    
  1915. 
    
  1916.       await resolveText('Child');
    
  1917.       ReactNoop.expire(1000);
    
  1918.       await advanceTimers(1000);
    
  1919. 
    
  1920.       await waitForAll([
    
  1921.         'Child',
    
  1922.         'onMarkerProgress(transition, parent, 0, 4000, [])',
    
  1923.         'onMarkerComplete(transition, parent, 0, 4000)',
    
  1924.         'onTransitionProgress(transition, 0, 4000, [])',
    
  1925.         'onTransitionComplete(transition, 0, 4000)',
    
  1926.       ]);
    
  1927.     });
    
  1928.   });
    
  1929. 
    
  1930.   // @gate enableTransitionTracing
    
  1931.   it('marker incomplete gets called properly if child suspense marker is not part of it', async () => {
    
  1932.     const transitionCallbacks = {
    
  1933.       onTransitionStart: (name, startTime) => {
    
  1934.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  1935.       },
    
  1936.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  1937.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1938.         Scheduler.log(
    
  1939.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  1940.         );
    
  1941.       },
    
  1942.       onTransitionComplete: (name, startTime, endTime) => {
    
  1943.         Scheduler.log(
    
  1944.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  1945.         );
    
  1946.       },
    
  1947.       onMarkerProgress: (
    
  1948.         transitioName,
    
  1949.         markerName,
    
  1950.         startTime,
    
  1951.         currentTime,
    
  1952.         pending,
    
  1953.       ) => {
    
  1954.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  1955.         Scheduler.log(
    
  1956.           `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
    
  1957.         );
    
  1958.       },
    
  1959.       onMarkerIncomplete: (
    
  1960.         transitionName,
    
  1961.         markerName,
    
  1962.         startTime,
    
  1963.         deletions,
    
  1964.       ) => {
    
  1965.         Scheduler.log(
    
  1966.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  1967.             deletions,
    
  1968.           )}])`,
    
  1969.         );
    
  1970.       },
    
  1971.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  1972.         Scheduler.log(
    
  1973.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  1974.         );
    
  1975.       },
    
  1976.     };
    
  1977. 
    
  1978.     function App({show, showSuspense}) {
    
  1979.       return (
    
  1980.         <React.unstable_TracingMarker name="parent">
    
  1981.           {show ? (
    
  1982.             <React.unstable_TracingMarker name="appended child">
    
  1983.               {showSuspense ? (
    
  1984.                 <Suspense unstable_name="appended child">
    
  1985.                   <AsyncText text="Appended child" />
    
  1986.                 </Suspense>
    
  1987.               ) : null}
    
  1988.             </React.unstable_TracingMarker>
    
  1989.           ) : null}
    
  1990.           <Suspense unstable_name="child">
    
  1991.             <AsyncText text="Child" />
    
  1992.           </Suspense>
    
  1993.         </React.unstable_TracingMarker>
    
  1994.       );
    
  1995.     }
    
  1996. 
    
  1997.     const root = ReactNoop.createRoot({
    
  1998.       unstable_transitionCallbacks: transitionCallbacks,
    
  1999.     });
    
  2000. 
    
  2001.     await act(async () => {
    
  2002.       startTransition(
    
  2003.         () => root.render(<App show={false} showSuspense={false} />),
    
  2004.         {
    
  2005.           name: 'transition one',
    
  2006.         },
    
  2007.       );
    
  2008. 
    
  2009.       ReactNoop.expire(1000);
    
  2010.       await advanceTimers(1000);
    
  2011.     });
    
  2012. 
    
  2013.     assertLog([
    
  2014.       'Suspend [Child]',
    
  2015.       'onTransitionStart(transition one, 0)',
    
  2016.       'onMarkerProgress(transition one, parent, 0, 1000, [child])',
    
  2017.       'onTransitionProgress(transition one, 0, 1000, [child])',
    
  2018.     ]);
    
  2019. 
    
  2020.     await act(async () => {
    
  2021.       startTransition(
    
  2022.         () => root.render(<App show={true} showSuspense={true} />),
    
  2023.         {
    
  2024.           name: 'transition two',
    
  2025.         },
    
  2026.       );
    
  2027. 
    
  2028.       ReactNoop.expire(1000);
    
  2029.       await advanceTimers(1000);
    
  2030.     });
    
  2031. 
    
  2032.     assertLog([
    
  2033.       'Suspend [Appended child]',
    
  2034.       'Suspend [Child]',
    
  2035.       'onTransitionStart(transition two, 1000)',
    
  2036.       'onMarkerProgress(transition two, appended child, 1000, 2000, [appended child])',
    
  2037.       'onTransitionProgress(transition two, 1000, 2000, [appended child])',
    
  2038.     ]);
    
  2039. 
    
  2040.     await act(async () => {
    
  2041.       root.render(<App show={true} showSuspense={false} />);
    
  2042.       ReactNoop.expire(1000);
    
  2043.       await advanceTimers(1000);
    
  2044.     });
    
  2045. 
    
  2046.     assertLog([
    
  2047.       'Suspend [Child]',
    
  2048.       'onMarkerProgress(transition two, appended child, 1000, 3000, [])',
    
  2049.       'onMarkerIncomplete(transition two, appended child, 1000, [{endTime: 3000, name: appended child, type: suspense}])',
    
  2050.     ]);
    
  2051. 
    
  2052.     await act(async () => {
    
  2053.       resolveText('Child');
    
  2054.       ReactNoop.expire(1000);
    
  2055.       await advanceTimers(1000);
    
  2056.     });
    
  2057. 
    
  2058.     assertLog([
    
  2059.       'Child',
    
  2060.       'onMarkerProgress(transition one, parent, 0, 4000, [])',
    
  2061.       'onMarkerComplete(transition one, parent, 0, 4000)',
    
  2062.       'onTransitionProgress(transition one, 0, 4000, [])',
    
  2063.       'onTransitionComplete(transition one, 0, 4000)',
    
  2064.     ]);
    
  2065.   });
    
  2066. 
    
  2067.   // @gate enableTransitionTracing
    
  2068.   it('warns when marker name changes', async () => {
    
  2069.     const transitionCallbacks = {
    
  2070.       onTransitionStart: (name, startTime) => {
    
  2071.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  2072.       },
    
  2073.       onTransitionComplete: (name, startTime, endTime) => {
    
  2074.         Scheduler.log(
    
  2075.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  2076.         );
    
  2077.       },
    
  2078.       onMarkerIncomplete: (
    
  2079.         transitionName,
    
  2080.         markerName,
    
  2081.         startTime,
    
  2082.         deletions,
    
  2083.       ) => {
    
  2084.         Scheduler.log(
    
  2085.           `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
    
  2086.             deletions,
    
  2087.           )}])`,
    
  2088.         );
    
  2089.       },
    
  2090.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  2091.         Scheduler.log(
    
  2092.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  2093.         );
    
  2094.       },
    
  2095.     };
    
  2096.     function App({markerName, markerKey}) {
    
  2097.       return (
    
  2098.         <React.unstable_TracingMarker name={markerName} key={markerKey}>
    
  2099.           <Text text={markerName} />
    
  2100.         </React.unstable_TracingMarker>
    
  2101.       );
    
  2102.     }
    
  2103. 
    
  2104.     const root = ReactNoop.createRoot({
    
  2105.       unstable_transitionCallbacks: transitionCallbacks,
    
  2106.     });
    
  2107.     await act(async () => {
    
  2108.       startTransition(
    
  2109.         () => root.render(<App markerName="one" markerKey="key" />),
    
  2110.         {
    
  2111.           name: 'transition one',
    
  2112.         },
    
  2113.       );
    
  2114.       ReactNoop.expire(1000);
    
  2115.       await advanceTimers(1000);
    
  2116.       await waitForAll([
    
  2117.         'one',
    
  2118.         'onTransitionStart(transition one, 0)',
    
  2119.         'onMarkerComplete(transition one, one, 0, 1000)',
    
  2120.         'onTransitionComplete(transition one, 0, 1000)',
    
  2121.       ]);
    
  2122.       startTransition(
    
  2123.         () => root.render(<App markerName="two" markerKey="key" />),
    
  2124.         {
    
  2125.           name: 'transition two',
    
  2126.         },
    
  2127.       );
    
  2128.       ReactNoop.expire(1000);
    
  2129.       await advanceTimers(1000);
    
  2130.       await expect(async () => {
    
  2131.         // onMarkerComplete shouldn't be called for transitions with
    
  2132.         // new keys
    
  2133.         await waitForAll([
    
  2134.           'two',
    
  2135.           'onTransitionStart(transition two, 1000)',
    
  2136.           'onTransitionComplete(transition two, 1000, 2000)',
    
  2137.         ]);
    
  2138.       }).toErrorDev(
    
  2139.         'Changing the name of a tracing marker after mount is not supported.',
    
  2140.       );
    
  2141.       startTransition(
    
  2142.         () => root.render(<App markerName="three" markerKey="new key" />),
    
  2143.         {
    
  2144.           name: 'transition three',
    
  2145.         },
    
  2146.       );
    
  2147.       ReactNoop.expire(1000);
    
  2148.       await advanceTimers(1000);
    
  2149.       // This should not warn and onMarkerComplete should be called
    
  2150.       await waitForAll([
    
  2151.         'three',
    
  2152.         'onTransitionStart(transition three, 2000)',
    
  2153.         'onMarkerComplete(transition three, three, 2000, 3000)',
    
  2154.         'onTransitionComplete(transition three, 2000, 3000)',
    
  2155.       ]);
    
  2156.     });
    
  2157.   });
    
  2158. 
    
  2159.   // @gate enableTransitionTracing
    
  2160.   it('offscreen trees should not stop transition from completing', async () => {
    
  2161.     const transitionCallbacks = {
    
  2162.       onTransitionStart: (name, startTime) => {
    
  2163.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  2164.       },
    
  2165.       onTransitionComplete: (name, startTime, endTime) => {
    
  2166.         Scheduler.log(
    
  2167.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  2168.         );
    
  2169.       },
    
  2170.       onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
    
  2171.         Scheduler.log(
    
  2172.           `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
    
  2173.         );
    
  2174.       },
    
  2175.     };
    
  2176. 
    
  2177.     function App() {
    
  2178.       return (
    
  2179.         <React.unstable_TracingMarker name="marker">
    
  2180.           <Suspense fallback={<Text text="Loading..." />}>
    
  2181.             <AsyncText text="Text" />
    
  2182.           </Suspense>
    
  2183.           <Activity mode="hidden">
    
  2184.             <Suspense fallback={<Text text="Hidden Loading..." />}>
    
  2185.               <AsyncText text="Hidden Text" />
    
  2186.             </Suspense>
    
  2187.           </Activity>
    
  2188.         </React.unstable_TracingMarker>
    
  2189.       );
    
  2190.     }
    
  2191. 
    
  2192.     const root = ReactNoop.createRoot({
    
  2193.       unstable_transitionCallbacks: transitionCallbacks,
    
  2194.     });
    
  2195.     await act(() => {
    
  2196.       startTransition(() => root.render(<App />), {name: 'transition'});
    
  2197.       ReactNoop.expire(1000);
    
  2198.       advanceTimers(1000);
    
  2199.     });
    
  2200.     assertLog([
    
  2201.       'Suspend [Text]',
    
  2202.       'Loading...',
    
  2203.       'Suspend [Hidden Text]',
    
  2204.       'Hidden Loading...',
    
  2205.       'onTransitionStart(transition, 0)',
    
  2206.     ]);
    
  2207. 
    
  2208.     await act(() => {
    
  2209.       resolveText('Text');
    
  2210.       ReactNoop.expire(1000);
    
  2211.       advanceTimers(1000);
    
  2212.     });
    
  2213.     assertLog([
    
  2214.       'Text',
    
  2215.       'onMarkerComplete(transition, marker, 0, 2000)',
    
  2216.       'onTransitionComplete(transition, 0, 2000)',
    
  2217.     ]);
    
  2218. 
    
  2219.     await act(() => {
    
  2220.       resolveText('Hidden Text');
    
  2221.       ReactNoop.expire(1000);
    
  2222.       advanceTimers(1000);
    
  2223.     });
    
  2224.     assertLog(['Hidden Text']);
    
  2225.   });
    
  2226. 
    
  2227.   // @gate enableTransitionTracing
    
  2228.   it('discrete events', async () => {
    
  2229.     const transitionCallbacks = {
    
  2230.       onTransitionStart: (name, startTime) => {
    
  2231.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  2232.       },
    
  2233.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  2234.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  2235.         Scheduler.log(
    
  2236.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  2237.         );
    
  2238.       },
    
  2239.       onTransitionComplete: (name, startTime, endTime) => {
    
  2240.         Scheduler.log(
    
  2241.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  2242.         );
    
  2243.       },
    
  2244.     };
    
  2245. 
    
  2246.     function App() {
    
  2247.       return (
    
  2248.         <Suspense
    
  2249.           fallback={<Text text="Loading..." />}
    
  2250.           unstable_name="suspense page">
    
  2251.           <AsyncText text="Page Two" />
    
  2252.         </Suspense>
    
  2253.       );
    
  2254.     }
    
  2255. 
    
  2256.     const root = ReactNoop.createRoot({
    
  2257.       unstable_transitionCallbacks: transitionCallbacks,
    
  2258.     });
    
  2259. 
    
  2260.     await act(async () => {
    
  2261.       ReactNoop.discreteUpdates(() =>
    
  2262.         startTransition(() => root.render(<App />), {name: 'page transition'}),
    
  2263.       );
    
  2264.       ReactNoop.expire(1000);
    
  2265.       await advanceTimers(1000);
    
  2266.     });
    
  2267. 
    
  2268.     assertLog([
    
  2269.       'Suspend [Page Two]',
    
  2270.       'Loading...',
    
  2271.       'onTransitionStart(page transition, 0)',
    
  2272.       'onTransitionProgress(page transition, 0, 1000, [suspense page])',
    
  2273.     ]);
    
  2274.     await act(async () => {
    
  2275.       ReactNoop.discreteUpdates(() => resolveText('Page Two'));
    
  2276.       ReactNoop.expire(1000);
    
  2277.       await advanceTimers(1000);
    
  2278.     });
    
  2279. 
    
  2280.     assertLog([
    
  2281.       'Page Two',
    
  2282.       'onTransitionProgress(page transition, 0, 2000, [])',
    
  2283.       'onTransitionComplete(page transition, 0, 2000)',
    
  2284.     ]);
    
  2285.   });
    
  2286. 
    
  2287.   // @gate enableTransitionTracing
    
  2288.   it('multiple commits happen before a paint', async () => {
    
  2289.     const transitionCallbacks = {
    
  2290.       onTransitionStart: (name, startTime) => {
    
  2291.         Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
    
  2292.       },
    
  2293.       onTransitionProgress: (name, startTime, endTime, pending) => {
    
  2294.         const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  2295.         Scheduler.log(
    
  2296.           `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
    
  2297.         );
    
  2298.       },
    
  2299.       onTransitionComplete: (name, startTime, endTime) => {
    
  2300.         Scheduler.log(
    
  2301.           `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
    
  2302.         );
    
  2303.       },
    
  2304.     };
    
  2305. 
    
  2306.     function App() {
    
  2307.       const [, setRerender] = useState(false);
    
  2308.       React.useLayoutEffect(() => {
    
  2309.         resolveText('Text');
    
  2310.         setRerender(true);
    
  2311.       });
    
  2312.       return (
    
  2313.         <>
    
  2314.           <Suspense unstable_name="one" fallback={<Text text="Loading..." />}>
    
  2315.             <AsyncText text="Text" />
    
  2316.           </Suspense>
    
  2317.           <Suspense
    
  2318.             unstable_name="two"
    
  2319.             fallback={<Text text="Loading Two..." />}>
    
  2320.             <AsyncText text="Text Two" />
    
  2321.           </Suspense>
    
  2322.         </>
    
  2323.       );
    
  2324.     }
    
  2325. 
    
  2326.     const root = ReactNoop.createRoot({
    
  2327.       unstable_transitionCallbacks: transitionCallbacks,
    
  2328.     });
    
  2329. 
    
  2330.     await act(() => {
    
  2331.       startTransition(() => root.render(<App />), {name: 'transition'});
    
  2332.       ReactNoop.expire(1000);
    
  2333.       advanceTimers(1000);
    
  2334.     });
    
  2335. 
    
  2336.     assertLog([
    
  2337.       'Suspend [Text]',
    
  2338.       'Loading...',
    
  2339.       'Suspend [Text Two]',
    
  2340.       'Loading Two...',
    
  2341.       'Text',
    
  2342.       'Suspend [Text Two]',
    
  2343.       'Loading Two...',
    
  2344.       'onTransitionStart(transition, 0)',
    
  2345.       'onTransitionProgress(transition, 0, 1000, [two])',
    
  2346.     ]);
    
  2347. 
    
  2348.     await act(() => {
    
  2349.       resolveText('Text Two');
    
  2350.       ReactNoop.expire(1000);
    
  2351.       advanceTimers(1000);
    
  2352.     });
    
  2353.     assertLog([
    
  2354.       'Text Two',
    
  2355.       'onTransitionProgress(transition, 0, 2000, [])',
    
  2356.       'onTransitionComplete(transition, 0, 2000)',
    
  2357.     ]);
    
  2358.   });
    
  2359. 
    
  2360.   // @gate enableTransitionTracing
    
  2361.   it('transition callbacks work for multiple roots', async () => {
    
  2362.     const getTransitionCallbacks = transitionName => {
    
  2363.       return {
    
  2364.         onTransitionStart: (name, startTime) => {
    
  2365.           Scheduler.log(
    
  2366.             `onTransitionStart(${name}, ${startTime}) /${transitionName}/`,
    
  2367.           );
    
  2368.         },
    
  2369.         onTransitionProgress: (name, startTime, endTime, pending) => {
    
  2370.           const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
    
  2371.           Scheduler.log(
    
  2372.             `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}]) /${transitionName}/`,
    
  2373.           );
    
  2374.         },
    
  2375.         onTransitionComplete: (name, startTime, endTime) => {
    
  2376.           Scheduler.log(
    
  2377.             `onTransitionComplete(${name}, ${startTime}, ${endTime}) /${transitionName}/`,
    
  2378.           );
    
  2379.         },
    
  2380.       };
    
  2381.     };
    
  2382. 
    
  2383.     function App({name}) {
    
  2384.       return (
    
  2385.         <>
    
  2386.           <Suspense
    
  2387.             unstable_name={name}
    
  2388.             fallback={<Text text={`Loading ${name}...`} />}>
    
  2389.             <AsyncText text={`Text ${name}`} />
    
  2390.           </Suspense>
    
  2391.         </>
    
  2392.       );
    
  2393.     }
    
  2394. 
    
  2395.     const rootOne = ReactNoop.createRoot({
    
  2396.       unstable_transitionCallbacks: getTransitionCallbacks('root one'),
    
  2397.     });
    
  2398. 
    
  2399.     const rootTwo = ReactNoop.createRoot({
    
  2400.       unstable_transitionCallbacks: getTransitionCallbacks('root two'),
    
  2401.     });
    
  2402. 
    
  2403.     await act(() => {
    
  2404.       startTransition(() => rootOne.render(<App name="one" />), {
    
  2405.         name: 'transition one',
    
  2406.       });
    
  2407.       startTransition(() => rootTwo.render(<App name="two" />), {
    
  2408.         name: 'transition two',
    
  2409.       });
    
  2410.       ReactNoop.expire(1000);
    
  2411.       advanceTimers(1000);
    
  2412.     });
    
  2413. 
    
  2414.     assertLog([
    
  2415.       'Suspend [Text one]',
    
  2416.       'Loading one...',
    
  2417.       'Suspend [Text two]',
    
  2418.       'Loading two...',
    
  2419.       'onTransitionStart(transition one, 0) /root one/',
    
  2420.       'onTransitionProgress(transition one, 0, 1000, [one]) /root one/',
    
  2421.       'onTransitionStart(transition two, 0) /root two/',
    
  2422.       'onTransitionProgress(transition two, 0, 1000, [two]) /root two/',
    
  2423.     ]);
    
  2424. 
    
  2425.     await act(() => {
    
  2426.       caches[0].resolve('Text one');
    
  2427.       ReactNoop.expire(1000);
    
  2428.       advanceTimers(1000);
    
  2429.     });
    
  2430. 
    
  2431.     assertLog([
    
  2432.       'Text one',
    
  2433.       'onTransitionProgress(transition one, 0, 2000, []) /root one/',
    
  2434.       'onTransitionComplete(transition one, 0, 2000) /root one/',
    
  2435.     ]);
    
  2436. 
    
  2437.     await act(() => {
    
  2438.       resolveText('Text two');
    
  2439.       ReactNoop.expire(1000);
    
  2440.       advanceTimers(1000);
    
  2441.     });
    
  2442. 
    
  2443.     assertLog([
    
  2444.       'Text two',
    
  2445.       'onTransitionProgress(transition two, 0, 3000, []) /root two/',
    
  2446.       'onTransitionComplete(transition two, 0, 3000) /root two/',
    
  2447.     ]);
    
  2448.   });
    
  2449. });