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 ReactDOMClient;
    
  15. let act;
    
  16. let assertLog;
    
  17. let Scheduler;
    
  18. 
    
  19. describe('ReactDOMSafariMicrotaskBug-test', () => {
    
  20.   let container;
    
  21.   let overrideQueueMicrotask;
    
  22.   let flushFakeMicrotasks;
    
  23. 
    
  24.   beforeEach(() => {
    
  25.     // In Safari, microtasks don't always run on clean stack.
    
  26.     // This setup crudely approximates it.
    
  27.     // In reality, the sync flush happens when an iframe is added to the page.
    
  28.     // https://github.com/facebook/react/issues/22459
    
  29.     const originalQueueMicrotask = queueMicrotask;
    
  30.     overrideQueueMicrotask = false;
    
  31.     const fakeMicrotaskQueue = [];
    
  32.     global.queueMicrotask = cb => {
    
  33.       if (overrideQueueMicrotask) {
    
  34.         fakeMicrotaskQueue.push(cb);
    
  35.       } else {
    
  36.         originalQueueMicrotask(cb);
    
  37.       }
    
  38.     };
    
  39.     flushFakeMicrotasks = () => {
    
  40.       while (fakeMicrotaskQueue.length > 0) {
    
  41.         const cb = fakeMicrotaskQueue.shift();
    
  42.         cb();
    
  43.       }
    
  44.     };
    
  45. 
    
  46.     jest.resetModules();
    
  47.     container = document.createElement('div');
    
  48.     React = require('react');
    
  49.     ReactDOMClient = require('react-dom/client');
    
  50.     act = require('internal-test-utils').act;
    
  51.     assertLog = require('internal-test-utils').assertLog;
    
  52.     Scheduler = require('scheduler');
    
  53. 
    
  54.     document.body.appendChild(container);
    
  55.   });
    
  56. 
    
  57.   afterEach(() => {
    
  58.     document.body.removeChild(container);
    
  59.   });
    
  60. 
    
  61.   it('should deal with premature microtask in commit phase', async () => {
    
  62.     let ran = false;
    
  63.     function Foo() {
    
  64.       const [state, setState] = React.useState(0);
    
  65.       return (
    
  66.         <div
    
  67.           ref={() => {
    
  68.             overrideQueueMicrotask = true;
    
  69.             if (!ran) {
    
  70.               ran = true;
    
  71.               setState(1);
    
  72.               flushFakeMicrotasks();
    
  73.               Scheduler.log(
    
  74.                 'Content at end of ref callback: ' + container.textContent,
    
  75.               );
    
  76.             }
    
  77.           }}>
    
  78.           {state}
    
  79.         </div>
    
  80.       );
    
  81.     }
    
  82.     const root = ReactDOMClient.createRoot(container);
    
  83.     await act(() => {
    
  84.       root.render(<Foo />);
    
  85.     });
    
  86.     assertLog(['Content at end of ref callback: 0']);
    
  87.     expect(container.textContent).toBe('1');
    
  88.   });
    
  89. 
    
  90.   it('should deal with premature microtask in event handler', async () => {
    
  91.     function Foo() {
    
  92.       const [state, setState] = React.useState(0);
    
  93.       return (
    
  94.         <button
    
  95.           onClick={() => {
    
  96.             overrideQueueMicrotask = true;
    
  97.             setState(1);
    
  98.             flushFakeMicrotasks();
    
  99.             Scheduler.log(
    
  100.               'Content at end of click handler: ' + container.textContent,
    
  101.             );
    
  102.           }}>
    
  103.           {state}
    
  104.         </button>
    
  105.       );
    
  106.     }
    
  107.     const root = ReactDOMClient.createRoot(container);
    
  108.     await act(() => {
    
  109.       root.render(<Foo />);
    
  110.     });
    
  111.     expect(container.textContent).toBe('0');
    
  112.     await act(() => {
    
  113.       container.firstChild.dispatchEvent(
    
  114.         new MouseEvent('click', {bubbles: true}),
    
  115.       );
    
  116.     });
    
  117.     // This causes the update to flush earlier than usual. This isn't the ideal
    
  118.     // behavior but we use this test to document it. The bug is Safari's, not
    
  119.     // ours, so we just do our best to not crash even though the behavior isn't
    
  120.     // completely correct.
    
  121.     assertLog(['Content at end of click handler: 1']);
    
  122.     expect(container.textContent).toBe('1');
    
  123.   });
    
  124. });