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. // sanity tests to make sure act() works without a mocked scheduler
    
  11. 
    
  12. let React;
    
  13. let ReactDOM;
    
  14. let act;
    
  15. let container;
    
  16. let yields;
    
  17. 
    
  18. function clearLog() {
    
  19.   try {
    
  20.     return yields;
    
  21.   } finally {
    
  22.     yields = [];
    
  23.   }
    
  24. }
    
  25. 
    
  26. function render(el, dom) {
    
  27.   ReactDOM.render(el, dom);
    
  28. }
    
  29. 
    
  30. function unmount(dom) {
    
  31.   ReactDOM.unmountComponentAtNode(dom);
    
  32. }
    
  33. 
    
  34. beforeEach(() => {
    
  35.   jest.resetModules();
    
  36.   jest.unmock('scheduler');
    
  37.   yields = [];
    
  38.   React = require('react');
    
  39.   ReactDOM = require('react-dom');
    
  40.   act = require('react-dom/test-utils').act;
    
  41.   container = document.createElement('div');
    
  42.   document.body.appendChild(container);
    
  43. });
    
  44. 
    
  45. afterEach(() => {
    
  46.   unmount(container);
    
  47.   document.body.removeChild(container);
    
  48. });
    
  49. 
    
  50. // @gate __DEV__
    
  51. it('can use act to flush effects', () => {
    
  52.   function App() {
    
  53.     React.useEffect(() => {
    
  54.       yields.push(100);
    
  55.     });
    
  56.     return null;
    
  57.   }
    
  58. 
    
  59.   act(() => {
    
  60.     render(<App />, container);
    
  61.   });
    
  62. 
    
  63.   expect(clearLog()).toEqual([100]);
    
  64. });
    
  65. 
    
  66. // @gate __DEV__
    
  67. it('flushes effects on every call', () => {
    
  68.   function App() {
    
  69.     const [ctr, setCtr] = React.useState(0);
    
  70.     React.useEffect(() => {
    
  71.       yields.push(ctr);
    
  72.     });
    
  73.     return (
    
  74.       <button id="button" onClick={() => setCtr(x => x + 1)}>
    
  75.         {ctr}
    
  76.       </button>
    
  77.     );
    
  78.   }
    
  79. 
    
  80.   act(() => {
    
  81.     render(<App />, container);
    
  82.   });
    
  83. 
    
  84.   expect(clearLog()).toEqual([0]);
    
  85. 
    
  86.   const button = container.querySelector('#button');
    
  87.   function click() {
    
  88.     button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
    
  89.   }
    
  90. 
    
  91.   act(() => {
    
  92.     click();
    
  93.     click();
    
  94.     click();
    
  95.   });
    
  96.   // it consolidates the 3 updates, then fires the effect
    
  97.   expect(clearLog()).toEqual([3]);
    
  98.   act(click);
    
  99.   expect(clearLog()).toEqual([4]);
    
  100.   act(click);
    
  101.   expect(clearLog()).toEqual([5]);
    
  102.   expect(button.innerHTML).toEqual('5');
    
  103. });
    
  104. 
    
  105. // @gate __DEV__
    
  106. it("should keep flushing effects until they're done", () => {
    
  107.   function App() {
    
  108.     const [ctr, setCtr] = React.useState(0);
    
  109.     React.useEffect(() => {
    
  110.       if (ctr < 5) {
    
  111.         setCtr(x => x + 1);
    
  112.       }
    
  113.     });
    
  114.     return ctr;
    
  115.   }
    
  116. 
    
  117.   act(() => {
    
  118.     render(<App />, container);
    
  119.   });
    
  120. 
    
  121.   expect(container.innerHTML).toEqual('5');
    
  122. });
    
  123. 
    
  124. // @gate __DEV__
    
  125. it('should flush effects only on exiting the outermost act', () => {
    
  126.   function App() {
    
  127.     React.useEffect(() => {
    
  128.       yields.push(0);
    
  129.     });
    
  130.     return null;
    
  131.   }
    
  132.   // let's nest a couple of act() calls
    
  133.   act(() => {
    
  134.     act(() => {
    
  135.       render(<App />, container);
    
  136.     });
    
  137.     // the effect wouldn't have yielded yet because
    
  138.     // we're still inside an act() scope
    
  139.     expect(clearLog()).toEqual([]);
    
  140.   });
    
  141.   // but after exiting the last one, effects get flushed
    
  142.   expect(clearLog()).toEqual([0]);
    
  143. });
    
  144. 
    
  145. // @gate __DEV__
    
  146. it('can handle cascading promises', async () => {
    
  147.   // this component triggers an effect, that waits a tick,
    
  148.   // then sets state. repeats this 5 times.
    
  149.   function App() {
    
  150.     const [state, setState] = React.useState(0);
    
  151.     async function ticker() {
    
  152.       await null;
    
  153.       setState(x => x + 1);
    
  154.     }
    
  155.     React.useEffect(() => {
    
  156.       yields.push(state);
    
  157.       ticker();
    
  158.     }, [Math.min(state, 4)]);
    
  159.     return state;
    
  160.   }
    
  161. 
    
  162.   await act(async () => {
    
  163.     render(<App />, container);
    
  164.   });
    
  165.   // all 5 ticks present and accounted for
    
  166.   expect(clearLog()).toEqual([0, 1, 2, 3, 4]);
    
  167.   expect(container.innerHTML).toBe('5');
    
  168. });