1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. let React;
    
  13. 
    
  14. let ReactDOM;
    
  15. let ReactDOMClient;
    
  16. let Scheduler;
    
  17. let act;
    
  18. let waitForAll;
    
  19. let waitFor;
    
  20. let waitForMicrotasks;
    
  21. let assertLog;
    
  22. 
    
  23. const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
    
  24.   HTMLInputElement.prototype,
    
  25.   'value',
    
  26. ).set;
    
  27. 
    
  28. describe('ReactDOMFiberAsync', () => {
    
  29.   let container;
    
  30. 
    
  31.   beforeEach(() => {
    
  32.     container = document.createElement('div');
    
  33.     React = require('react');
    
  34.     ReactDOM = require('react-dom');
    
  35.     ReactDOMClient = require('react-dom/client');
    
  36.     act = require('internal-test-utils').act;
    
  37.     Scheduler = require('scheduler');
    
  38. 
    
  39.     const InternalTestUtils = require('internal-test-utils');
    
  40.     waitForAll = InternalTestUtils.waitForAll;
    
  41.     waitFor = InternalTestUtils.waitFor;
    
  42.     waitForMicrotasks = InternalTestUtils.waitForMicrotasks;
    
  43.     assertLog = InternalTestUtils.assertLog;
    
  44. 
    
  45.     document.body.appendChild(container);
    
  46.     window.event = undefined;
    
  47.   });
    
  48. 
    
  49.   afterEach(() => {
    
  50.     document.body.removeChild(container);
    
  51.   });
    
  52. 
    
  53.   it('renders synchronously by default', () => {
    
  54.     const ops = [];
    
  55.     ReactDOM.render(<div>Hi</div>, container, () => {
    
  56.       ops.push(container.textContent);
    
  57.     });
    
  58.     ReactDOM.render(<div>Bye</div>, container, () => {
    
  59.       ops.push(container.textContent);
    
  60.     });
    
  61.     expect(ops).toEqual(['Hi', 'Bye']);
    
  62.   });
    
  63. 
    
  64.   it('flushSync batches sync updates and flushes them at the end of the batch', () => {
    
  65.     const ops = [];
    
  66.     let instance;
    
  67. 
    
  68.     class Component extends React.Component {
    
  69.       state = {text: ''};
    
  70.       push(val) {
    
  71.         this.setState(state => ({text: state.text + val}));
    
  72.       }
    
  73.       componentDidUpdate() {
    
  74.         ops.push(this.state.text);
    
  75.       }
    
  76.       render() {
    
  77.         instance = this;
    
  78.         return <span>{this.state.text}</span>;
    
  79.       }
    
  80.     }
    
  81. 
    
  82.     ReactDOM.render(<Component />, container);
    
  83. 
    
  84.     instance.push('A');
    
  85.     expect(ops).toEqual(['A']);
    
  86.     expect(container.textContent).toEqual('A');
    
  87. 
    
  88.     ReactDOM.flushSync(() => {
    
  89.       instance.push('B');
    
  90.       instance.push('C');
    
  91.       // Not flushed yet
    
  92.       expect(container.textContent).toEqual('A');
    
  93.       expect(ops).toEqual(['A']);
    
  94.     });
    
  95.     expect(container.textContent).toEqual('ABC');
    
  96.     expect(ops).toEqual(['A', 'ABC']);
    
  97.     instance.push('D');
    
  98.     expect(container.textContent).toEqual('ABCD');
    
  99.     expect(ops).toEqual(['A', 'ABC', 'ABCD']);
    
  100.   });
    
  101. 
    
  102.   it('flushSync flushes updates even if nested inside another flushSync', () => {
    
  103.     const ops = [];
    
  104.     let instance;
    
  105. 
    
  106.     class Component extends React.Component {
    
  107.       state = {text: ''};
    
  108.       push(val) {
    
  109.         this.setState(state => ({text: state.text + val}));
    
  110.       }
    
  111.       componentDidUpdate() {
    
  112.         ops.push(this.state.text);
    
  113.       }
    
  114.       render() {
    
  115.         instance = this;
    
  116.         return <span>{this.state.text}</span>;
    
  117.       }
    
  118.     }
    
  119. 
    
  120.     ReactDOM.render(<Component />, container);
    
  121. 
    
  122.     instance.push('A');
    
  123.     expect(ops).toEqual(['A']);
    
  124.     expect(container.textContent).toEqual('A');
    
  125. 
    
  126.     ReactDOM.flushSync(() => {
    
  127.       instance.push('B');
    
  128.       instance.push('C');
    
  129.       // Not flushed yet
    
  130.       expect(container.textContent).toEqual('A');
    
  131.       expect(ops).toEqual(['A']);
    
  132. 
    
  133.       ReactDOM.flushSync(() => {
    
  134.         instance.push('D');
    
  135.       });
    
  136.       // The nested flushSync caused everything to flush.
    
  137.       expect(container.textContent).toEqual('ABCD');
    
  138.       expect(ops).toEqual(['A', 'ABCD']);
    
  139.     });
    
  140.     expect(container.textContent).toEqual('ABCD');
    
  141.     expect(ops).toEqual(['A', 'ABCD']);
    
  142.   });
    
  143. 
    
  144.   it('flushSync logs an error if already performing work', () => {
    
  145.     class Component extends React.Component {
    
  146.       componentDidUpdate() {
    
  147.         ReactDOM.flushSync();
    
  148.       }
    
  149.       render() {
    
  150.         return null;
    
  151.       }
    
  152.     }
    
  153. 
    
  154.     // Initial mount
    
  155.     ReactDOM.render(<Component />, container);
    
  156.     // Update
    
  157.     expect(() => ReactDOM.render(<Component />, container)).toErrorDev(
    
  158.       'flushSync was called from inside a lifecycle method',
    
  159.     );
    
  160.   });
    
  161. 
    
  162.   describe('concurrent mode', () => {
    
  163.     it('does not perform deferred updates synchronously', async () => {
    
  164.       const inputRef = React.createRef();
    
  165.       const asyncValueRef = React.createRef();
    
  166.       const syncValueRef = React.createRef();
    
  167. 
    
  168.       class Counter extends React.Component {
    
  169.         state = {asyncValue: '', syncValue: ''};
    
  170. 
    
  171.         handleChange = e => {
    
  172.           const nextValue = e.target.value;
    
  173.           React.startTransition(() => {
    
  174.             this.setState({
    
  175.               asyncValue: nextValue,
    
  176.             });
    
  177.             // It should not be flushed yet.
    
  178.             expect(asyncValueRef.current.textContent).toBe('');
    
  179.           });
    
  180.           this.setState({
    
  181.             syncValue: nextValue,
    
  182.           });
    
  183.         };
    
  184. 
    
  185.         render() {
    
  186.           return (
    
  187.             <div>
    
  188.               <input
    
  189.                 ref={inputRef}
    
  190.                 onChange={this.handleChange}
    
  191.                 defaultValue=""
    
  192.               />
    
  193.               <p ref={asyncValueRef}>{this.state.asyncValue}</p>
    
  194.               <p ref={syncValueRef}>{this.state.syncValue}</p>
    
  195.             </div>
    
  196.           );
    
  197.         }
    
  198.       }
    
  199.       const root = ReactDOMClient.createRoot(container);
    
  200.       await act(() => root.render(<Counter />));
    
  201.       expect(asyncValueRef.current.textContent).toBe('');
    
  202.       expect(syncValueRef.current.textContent).toBe('');
    
  203. 
    
  204.       await act(() => {
    
  205.         setUntrackedInputValue.call(inputRef.current, 'hello');
    
  206.         inputRef.current.dispatchEvent(
    
  207.           new MouseEvent('input', {bubbles: true}),
    
  208.         );
    
  209.         // Should only flush non-deferred update.
    
  210.         expect(asyncValueRef.current.textContent).toBe('');
    
  211.         expect(syncValueRef.current.textContent).toBe('hello');
    
  212.       });
    
  213. 
    
  214.       // Should flush both updates now.
    
  215.       expect(asyncValueRef.current.textContent).toBe('hello');
    
  216.       expect(syncValueRef.current.textContent).toBe('hello');
    
  217.     });
    
  218. 
    
  219.     it('top-level updates are concurrent', async () => {
    
  220.       const root = ReactDOMClient.createRoot(container);
    
  221.       await act(() => {
    
  222.         root.render(<div>Hi</div>);
    
  223.         expect(container.textContent).toEqual('');
    
  224.       });
    
  225.       expect(container.textContent).toEqual('Hi');
    
  226. 
    
  227.       await act(() => {
    
  228.         root.render(<div>Bye</div>);
    
  229.         expect(container.textContent).toEqual('Hi');
    
  230.       });
    
  231.       expect(container.textContent).toEqual('Bye');
    
  232.     });
    
  233. 
    
  234.     it('deep updates (setState) are concurrent', async () => {
    
  235.       let instance;
    
  236.       class Component extends React.Component {
    
  237.         state = {step: 0};
    
  238.         render() {
    
  239.           instance = this;
    
  240.           return <div>{this.state.step}</div>;
    
  241.         }
    
  242.       }
    
  243. 
    
  244.       const root = ReactDOMClient.createRoot(container);
    
  245. 
    
  246.       await act(() => {
    
  247.         root.render(<Component />);
    
  248.         expect(container.textContent).toEqual('');
    
  249.       });
    
  250.       expect(container.textContent).toEqual('0');
    
  251. 
    
  252.       await act(() => {
    
  253.         instance.setState({step: 1});
    
  254.         expect(container.textContent).toEqual('0');
    
  255.       });
    
  256.       expect(container.textContent).toEqual('1');
    
  257.     });
    
  258. 
    
  259.     it('flushSync flushes updates before end of the tick', async () => {
    
  260.       let instance;
    
  261. 
    
  262.       class Component extends React.Component {
    
  263.         state = {text: ''};
    
  264.         push(val) {
    
  265.           this.setState(state => ({text: state.text + val}));
    
  266.         }
    
  267.         componentDidUpdate() {
    
  268.           Scheduler.log(this.state.text);
    
  269.         }
    
  270.         render() {
    
  271.           instance = this;
    
  272.           return <span>{this.state.text}</span>;
    
  273.         }
    
  274.       }
    
  275. 
    
  276.       const root = ReactDOMClient.createRoot(container);
    
  277.       await act(() => root.render(<Component />));
    
  278. 
    
  279.       // Updates are async by default
    
  280.       instance.push('A');
    
  281.       assertLog([]);
    
  282.       expect(container.textContent).toEqual('');
    
  283. 
    
  284.       ReactDOM.flushSync(() => {
    
  285.         instance.push('B');
    
  286.         instance.push('C');
    
  287.         // Not flushed yet
    
  288.         expect(container.textContent).toEqual('');
    
  289.         assertLog([]);
    
  290.       });
    
  291.       // Only the active updates have flushed
    
  292.       if (gate(flags => flags.enableUnifiedSyncLane)) {
    
  293.         expect(container.textContent).toEqual('ABC');
    
  294.         assertLog(['ABC']);
    
  295.       } else {
    
  296.         expect(container.textContent).toEqual('BC');
    
  297.         assertLog(['BC']);
    
  298.       }
    
  299. 
    
  300.       await act(() => {
    
  301.         instance.push('D');
    
  302.         if (gate(flags => flags.enableUnifiedSyncLane)) {
    
  303.           expect(container.textContent).toEqual('ABC');
    
  304.         } else {
    
  305.           expect(container.textContent).toEqual('BC');
    
  306.         }
    
  307.         assertLog([]);
    
  308.       });
    
  309.       assertLog(['ABCD']);
    
  310.       expect(container.textContent).toEqual('ABCD');
    
  311.     });
    
  312. 
    
  313.     it('ignores discrete events on a pending removed element', async () => {
    
  314.       const disableButtonRef = React.createRef();
    
  315.       const submitButtonRef = React.createRef();
    
  316. 
    
  317.       function Form() {
    
  318.         const [active, setActive] = React.useState(true);
    
  319.         function disableForm() {
    
  320.           setActive(false);
    
  321.         }
    
  322. 
    
  323.         return (
    
  324.           <div>
    
  325.             <button onClick={disableForm} ref={disableButtonRef}>
    
  326.               Disable
    
  327.             </button>
    
  328.             {active ? <button ref={submitButtonRef}>Submit</button> : null}
    
  329.           </div>
    
  330.         );
    
  331.       }
    
  332. 
    
  333.       const root = ReactDOMClient.createRoot(container);
    
  334.       await act(() => {
    
  335.         root.render(<Form />);
    
  336.       });
    
  337. 
    
  338.       const disableButton = disableButtonRef.current;
    
  339.       expect(disableButton.tagName).toBe('BUTTON');
    
  340. 
    
  341.       const submitButton = submitButtonRef.current;
    
  342.       expect(submitButton.tagName).toBe('BUTTON');
    
  343. 
    
  344.       // Dispatch a click event on the Disable-button.
    
  345.       const firstEvent = document.createEvent('Event');
    
  346.       firstEvent.initEvent('click', true, true);
    
  347.       disableButton.dispatchEvent(firstEvent);
    
  348. 
    
  349.       // The click event is flushed synchronously, even in concurrent mode.
    
  350.       expect(submitButton.current).toBe(undefined);
    
  351.     });
    
  352. 
    
  353.     it('ignores discrete events on a pending removed event listener', async () => {
    
  354.       const disableButtonRef = React.createRef();
    
  355.       const submitButtonRef = React.createRef();
    
  356. 
    
  357.       let formSubmitted = false;
    
  358. 
    
  359.       function Form() {
    
  360.         const [active, setActive] = React.useState(true);
    
  361.         function disableForm() {
    
  362.           setActive(false);
    
  363.         }
    
  364.         function submitForm() {
    
  365.           formSubmitted = true; // This should not get invoked
    
  366.         }
    
  367.         function disabledSubmitForm() {
    
  368.           // The form is disabled.
    
  369.         }
    
  370.         return (
    
  371.           <div>
    
  372.             <button onClick={disableForm} ref={disableButtonRef}>
    
  373.               Disable
    
  374.             </button>
    
  375.             <button
    
  376.               onClick={active ? submitForm : disabledSubmitForm}
    
  377.               ref={submitButtonRef}>
    
  378.               Submit
    
  379.             </button>
    
  380.           </div>
    
  381.         );
    
  382.       }
    
  383. 
    
  384.       const root = ReactDOMClient.createRoot(container);
    
  385.       await act(() => {
    
  386.         root.render(<Form />);
    
  387.       });
    
  388. 
    
  389.       const disableButton = disableButtonRef.current;
    
  390.       expect(disableButton.tagName).toBe('BUTTON');
    
  391. 
    
  392.       // Dispatch a click event on the Disable-button.
    
  393.       const firstEvent = document.createEvent('Event');
    
  394.       firstEvent.initEvent('click', true, true);
    
  395.       await act(() => {
    
  396.         disableButton.dispatchEvent(firstEvent);
    
  397.       });
    
  398. 
    
  399.       // There should now be a pending update to disable the form.
    
  400. 
    
  401.       // This should not have flushed yet since it's in concurrent mode.
    
  402.       const submitButton = submitButtonRef.current;
    
  403.       expect(submitButton.tagName).toBe('BUTTON');
    
  404. 
    
  405.       // In the meantime, we can dispatch a new client event on the submit button.
    
  406.       const secondEvent = document.createEvent('Event');
    
  407.       secondEvent.initEvent('click', true, true);
    
  408.       // This should force the pending update to flush which disables the submit button before the event is invoked.
    
  409.       await act(() => {
    
  410.         submitButton.dispatchEvent(secondEvent);
    
  411.       });
    
  412. 
    
  413.       // Therefore the form should never have been submitted.
    
  414.       expect(formSubmitted).toBe(false);
    
  415.     });
    
  416. 
    
  417.     it('uses the newest discrete events on a pending changed event listener', async () => {
    
  418.       const enableButtonRef = React.createRef();
    
  419.       const submitButtonRef = React.createRef();
    
  420. 
    
  421.       let formSubmitted = false;
    
  422. 
    
  423.       function Form() {
    
  424.         const [active, setActive] = React.useState(false);
    
  425.         function enableForm() {
    
  426.           setActive(true);
    
  427.         }
    
  428.         function submitForm() {
    
  429.           formSubmitted = true; // This should not get invoked
    
  430.         }
    
  431.         return (
    
  432.           <div>
    
  433.             <button onClick={enableForm} ref={enableButtonRef}>
    
  434.               Enable
    
  435.             </button>
    
  436.             <button onClick={active ? submitForm : null} ref={submitButtonRef}>
    
  437.               Submit
    
  438.             </button>
    
  439.           </div>
    
  440.         );
    
  441.       }
    
  442. 
    
  443.       const root = ReactDOMClient.createRoot(container);
    
  444.       await act(() => {
    
  445.         root.render(<Form />);
    
  446.       });
    
  447. 
    
  448.       const enableButton = enableButtonRef.current;
    
  449.       expect(enableButton.tagName).toBe('BUTTON');
    
  450. 
    
  451.       // Dispatch a click event on the Enable-button.
    
  452.       const firstEvent = document.createEvent('Event');
    
  453.       firstEvent.initEvent('click', true, true);
    
  454.       await act(() => {
    
  455.         enableButton.dispatchEvent(firstEvent);
    
  456.       });
    
  457. 
    
  458.       // There should now be a pending update to enable the form.
    
  459. 
    
  460.       // This should not have flushed yet since it's in concurrent mode.
    
  461.       const submitButton = submitButtonRef.current;
    
  462.       expect(submitButton.tagName).toBe('BUTTON');
    
  463. 
    
  464.       // In the meantime, we can dispatch a new client event on the submit button.
    
  465.       const secondEvent = document.createEvent('Event');
    
  466.       secondEvent.initEvent('click', true, true);
    
  467.       // This should force the pending update to flush which enables the submit button before the event is invoked.
    
  468.       await act(() => {
    
  469.         submitButton.dispatchEvent(secondEvent);
    
  470.       });
    
  471. 
    
  472.       // Therefore the form should have been submitted.
    
  473.       expect(formSubmitted).toBe(true);
    
  474.     });
    
  475.   });
    
  476. 
    
  477.   it('regression test: does not drop passive effects across roots (#17066)', async () => {
    
  478.     const {useState, useEffect} = React;
    
  479. 
    
  480.     function App({label}) {
    
  481.       const [step, setStep] = useState(0);
    
  482.       useEffect(() => {
    
  483.         if (step < 3) {
    
  484.           setStep(step + 1);
    
  485.         }
    
  486.       }, [step]);
    
  487. 
    
  488.       // The component should keep re-rendering itself until `step` is 3.
    
  489.       return step === 3 ? 'Finished' : 'Unresolved';
    
  490.     }
    
  491. 
    
  492.     const containerA = document.createElement('div');
    
  493.     const containerB = document.createElement('div');
    
  494.     const containerC = document.createElement('div');
    
  495. 
    
  496.     await act(() => {
    
  497.       ReactDOM.render(<App label="A" />, containerA);
    
  498.       ReactDOM.render(<App label="B" />, containerB);
    
  499.       ReactDOM.render(<App label="C" />, containerC);
    
  500.     });
    
  501. 
    
  502.     expect(containerA.textContent).toEqual('Finished');
    
  503.     expect(containerB.textContent).toEqual('Finished');
    
  504.     expect(containerC.textContent).toEqual('Finished');
    
  505.   });
    
  506. 
    
  507.   it('updates flush without yielding in the next event', async () => {
    
  508.     const root = ReactDOMClient.createRoot(container);
    
  509. 
    
  510.     function Text(props) {
    
  511.       Scheduler.log(props.text);
    
  512.       return props.text;
    
  513.     }
    
  514. 
    
  515.     root.render(
    
  516.       <>
    
  517.         <Text text="A" />
    
  518.         <Text text="B" />
    
  519.         <Text text="C" />
    
  520.       </>,
    
  521.     );
    
  522. 
    
  523.     // Nothing should have rendered yet
    
  524.     expect(container.textContent).toEqual('');
    
  525. 
    
  526.     // Everything should render immediately in the next event
    
  527.     await waitForAll(['A', 'B', 'C']);
    
  528.     expect(container.textContent).toEqual('ABC');
    
  529.   });
    
  530. 
    
  531.   it('unmounted roots should never clear newer root content from a container', async () => {
    
  532.     const ref = React.createRef();
    
  533. 
    
  534.     function OldApp() {
    
  535.       const [value, setValue] = React.useState('old');
    
  536.       function hideOnClick() {
    
  537.         // Schedule a discrete update.
    
  538.         setValue('update');
    
  539.         // Synchronously unmount this root.
    
  540.         ReactDOM.flushSync(() => oldRoot.unmount());
    
  541.       }
    
  542.       return (
    
  543.         <button onClick={hideOnClick} ref={ref}>
    
  544.           {value}
    
  545.         </button>
    
  546.       );
    
  547.     }
    
  548. 
    
  549.     function NewApp() {
    
  550.       return <button ref={ref}>new</button>;
    
  551.     }
    
  552. 
    
  553.     const oldRoot = ReactDOMClient.createRoot(container);
    
  554.     await act(() => {
    
  555.       oldRoot.render(<OldApp />);
    
  556.     });
    
  557. 
    
  558.     // Invoke discrete event.
    
  559.     ref.current.click();
    
  560. 
    
  561.     // The root should now be unmounted.
    
  562.     expect(container.textContent).toBe('');
    
  563. 
    
  564.     // We can now render a new one.
    
  565.     const newRoot = ReactDOMClient.createRoot(container);
    
  566.     ReactDOM.flushSync(() => {
    
  567.       newRoot.render(<NewApp />);
    
  568.     });
    
  569.     ref.current.click();
    
  570. 
    
  571.     expect(container.textContent).toBe('new');
    
  572.   });
    
  573. 
    
  574.   it('should synchronously render the transition lane scheduled in a popState', async () => {
    
  575.     function App() {
    
  576.       const [syncState, setSyncState] = React.useState(false);
    
  577.       const [hasNavigated, setHasNavigated] = React.useState(false);
    
  578.       function onPopstate() {
    
  579.         Scheduler.log(`popState`);
    
  580.         React.startTransition(() => {
    
  581.           setHasNavigated(true);
    
  582.         });
    
  583.         setSyncState(true);
    
  584.       }
    
  585.       React.useEffect(() => {
    
  586.         window.addEventListener('popstate', onPopstate);
    
  587.         return () => {
    
  588.           window.removeEventListener('popstate', onPopstate);
    
  589.         };
    
  590.       }, []);
    
  591.       Scheduler.log(`render:${hasNavigated}/${syncState}`);
    
  592.       return null;
    
  593.     }
    
  594.     const root = ReactDOMClient.createRoot(container);
    
  595.     await act(async () => {
    
  596.       root.render(<App />);
    
  597.     });
    
  598.     assertLog(['render:false/false']);
    
  599. 
    
  600.     await act(async () => {
    
  601.       const popStateEvent = new Event('popstate');
    
  602.       // Jest is not emulating window.event correctly in the microtask
    
  603.       window.event = popStateEvent;
    
  604.       window.dispatchEvent(popStateEvent);
    
  605.       queueMicrotask(() => {
    
  606.         window.event = undefined;
    
  607.       });
    
  608.     });
    
  609. 
    
  610.     assertLog(['popState', 'render:true/true']);
    
  611.     await act(() => {
    
  612.       root.unmount();
    
  613.     });
    
  614.   });
    
  615. 
    
  616.   it('Should not flush transition lanes if there is no transition scheduled in popState', async () => {
    
  617.     let setHasNavigated;
    
  618.     function App() {
    
  619.       const [syncState, setSyncState] = React.useState(false);
    
  620.       const [hasNavigated, _setHasNavigated] = React.useState(false);
    
  621.       setHasNavigated = _setHasNavigated;
    
  622.       function onPopstate() {
    
  623.         setSyncState(true);
    
  624.       }
    
  625. 
    
  626.       React.useEffect(() => {
    
  627.         window.addEventListener('popstate', onPopstate);
    
  628.         return () => {
    
  629.           window.removeEventListener('popstate', onPopstate);
    
  630.         };
    
  631.       }, []);
    
  632. 
    
  633.       Scheduler.log(`render:${hasNavigated}/${syncState}`);
    
  634.       return null;
    
  635.     }
    
  636.     const root = ReactDOMClient.createRoot(container);
    
  637.     await act(async () => {
    
  638.       root.render(<App />);
    
  639.     });
    
  640.     assertLog(['render:false/false']);
    
  641. 
    
  642.     React.startTransition(() => {
    
  643.       setHasNavigated(true);
    
  644.     });
    
  645.     await act(async () => {
    
  646.       const popStateEvent = new Event('popstate');
    
  647.       // Jest is not emulating window.event correctly in the microtask
    
  648.       window.event = popStateEvent;
    
  649.       window.dispatchEvent(popStateEvent);
    
  650.       queueMicrotask(() => {
    
  651.         window.event = undefined;
    
  652.       });
    
  653.     });
    
  654.     assertLog(['render:false/true', 'render:true/true']);
    
  655.     await act(() => {
    
  656.       root.unmount();
    
  657.     });
    
  658.   });
    
  659. 
    
  660.   it('transition lane in popState should be allowed to suspend', async () => {
    
  661.     let resolvePromise;
    
  662.     const promise = new Promise(res => {
    
  663.       resolvePromise = res;
    
  664.     });
    
  665. 
    
  666.     function Text({text}) {
    
  667.       Scheduler.log(text);
    
  668.       return text;
    
  669.     }
    
  670. 
    
  671.     function App() {
    
  672.       const [pathname, setPathname] = React.useState('/path/a');
    
  673. 
    
  674.       if (pathname !== '/path/a') {
    
  675.         try {
    
  676.           React.use(promise);
    
  677.         } catch (e) {
    
  678.           Scheduler.log(`Suspend! [${pathname}]`);
    
  679.           throw e;
    
  680.         }
    
  681.       }
    
  682. 
    
  683.       React.useEffect(() => {
    
  684.         function onPopstate() {
    
  685.           React.startTransition(() => {
    
  686.             setPathname('/path/b');
    
  687.           });
    
  688.         }
    
  689.         window.addEventListener('popstate', onPopstate);
    
  690.         return () => window.removeEventListener('popstate', onPopstate);
    
  691.       }, []);
    
  692. 
    
  693.       return (
    
  694.         <>
    
  695.           <Text text="Before" />
    
  696.           <div>
    
  697.             <Text text={pathname} />
    
  698.           </div>
    
  699.           <Text text="After" />
    
  700.         </>
    
  701.       );
    
  702.     }
    
  703. 
    
  704.     const root = ReactDOMClient.createRoot(container);
    
  705.     await act(async () => {
    
  706.       root.render(<App />);
    
  707.     });
    
  708.     assertLog(['Before', '/path/a', 'After']);
    
  709. 
    
  710.     const div = container.getElementsByTagName('div')[0];
    
  711.     expect(div.textContent).toBe('/path/a');
    
  712. 
    
  713.     // Simulate a popstate event
    
  714.     await act(async () => {
    
  715.       const popStateEvent = new Event('popstate');
    
  716. 
    
  717.       // Simulate a popstate event
    
  718.       window.event = popStateEvent;
    
  719.       window.dispatchEvent(popStateEvent);
    
  720.       await waitForMicrotasks();
    
  721.       window.event = undefined;
    
  722. 
    
  723.       // The transition lane should have been attempted synchronously (in
    
  724.       // a microtask)
    
  725.       assertLog(['Suspend! [/path/b]']);
    
  726.       // Because it suspended, it remains on the current path
    
  727.       expect(div.textContent).toBe('/path/a');
    
  728.     });
    
  729.     assertLog(['Suspend! [/path/b]']);
    
  730. 
    
  731.     await act(async () => {
    
  732.       resolvePromise();
    
  733. 
    
  734.       // Since the transition previously suspended, there's no need for this
    
  735.       // transition to be rendered synchronously on susbequent attempts; if we
    
  736.       // fail to commit synchronously the first time, the scroll restoration
    
  737.       // state won't be restored anyway.
    
  738.       //
    
  739.       // Yield in between each child to prove that it's concurrent.
    
  740.       await waitForMicrotasks();
    
  741.       assertLog([]);
    
  742. 
    
  743.       await waitFor(['Before']);
    
  744.       await waitFor(['/path/b']);
    
  745.       await waitFor(['After']);
    
  746.     });
    
  747.     assertLog([]);
    
  748.     expect(div.textContent).toBe('/path/b');
    
  749.     await act(() => {
    
  750.       root.unmount();
    
  751.     });
    
  752.   });
    
  753. 
    
  754.   it('regression: infinite deferral loop caused by unstable useDeferredValue input', async () => {
    
  755.     function Text({text}) {
    
  756.       Scheduler.log(text);
    
  757.       return text;
    
  758.     }
    
  759. 
    
  760.     let i = 0;
    
  761.     function App() {
    
  762.       const [pathname, setPathname] = React.useState('/path/a');
    
  763.       // This is an unstable input, so it will always cause a deferred render.
    
  764.       const {value: deferredPathname} = React.useDeferredValue({
    
  765.         value: pathname,
    
  766.       });
    
  767.       if (i++ > 100) {
    
  768.         throw new Error('Infinite loop detected');
    
  769.       }
    
  770.       React.useEffect(() => {
    
  771.         function onPopstate() {
    
  772.           React.startTransition(() => {
    
  773.             setPathname('/path/b');
    
  774.           });
    
  775.         }
    
  776.         window.addEventListener('popstate', onPopstate);
    
  777.         return () => window.removeEventListener('popstate', onPopstate);
    
  778.       }, []);
    
  779. 
    
  780.       return <Text text={deferredPathname} />;
    
  781.     }
    
  782. 
    
  783.     const root = ReactDOMClient.createRoot(container);
    
  784.     await act(() => {
    
  785.       root.render(<App />);
    
  786.     });
    
  787.     assertLog(['/path/a']);
    
  788.     expect(container.textContent).toBe('/path/a');
    
  789. 
    
  790.     // Simulate a popstate event
    
  791.     await act(async () => {
    
  792.       const popStateEvent = new Event('popstate');
    
  793. 
    
  794.       // Simulate a popstate event
    
  795.       window.event = popStateEvent;
    
  796.       window.dispatchEvent(popStateEvent);
    
  797.       await waitForMicrotasks();
    
  798.       window.event = undefined;
    
  799. 
    
  800.       // The transition lane is attempted synchronously (in a microtask).
    
  801.       // Because the input to useDeferredValue is referentially unstable, it
    
  802.       // will spawn a deferred task at transition priority. However, even
    
  803.       // though it was spawned during a transition event, the spawned task
    
  804.       // not also be upgraded to sync.
    
  805.       assertLog(['/path/a']);
    
  806.     });
    
  807.     assertLog(['/path/b']);
    
  808.     expect(container.textContent).toBe('/path/b');
    
  809.     await act(() => {
    
  810.       root.unmount();
    
  811.     });
    
  812.   });
    
  813. });