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. 'use strict';
    
  11. 
    
  12. let React;
    
  13. let ReactNoop;
    
  14. let waitForAll;
    
  15. let act;
    
  16. 
    
  17. describe('ReactSuspense', () => {
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20. 
    
  21.     React = require('react');
    
  22.     ReactNoop = require('react-noop-renderer');
    
  23. 
    
  24.     const InternalTestUtils = require('internal-test-utils');
    
  25.     waitForAll = InternalTestUtils.waitForAll;
    
  26.     act = InternalTestUtils.act;
    
  27.   });
    
  28. 
    
  29.   function createThenable() {
    
  30.     let completed = false;
    
  31.     let resolve;
    
  32.     const promise = new Promise(res => {
    
  33.       resolve = () => {
    
  34.         completed = true;
    
  35.         res();
    
  36.       };
    
  37.     });
    
  38.     const PromiseComp = () => {
    
  39.       if (!completed) {
    
  40.         throw promise;
    
  41.       }
    
  42.       return 'Done';
    
  43.     };
    
  44.     return {promise, resolve, PromiseComp};
    
  45.   }
    
  46. 
    
  47.   // Warning don't fire in production, so this test passes in prod even if
    
  48.   // the suspenseCallback feature is not enabled
    
  49.   // @gate www || !__DEV__
    
  50.   it('check type', async () => {
    
  51.     const {PromiseComp} = createThenable();
    
  52. 
    
  53.     const elementBadType = (
    
  54.       <React.Suspense suspenseCallback={1} fallback={'Waiting'}>
    
  55.         <PromiseComp />
    
  56.       </React.Suspense>
    
  57.     );
    
  58. 
    
  59.     ReactNoop.render(elementBadType);
    
  60.     await expect(async () => await waitForAll([])).toErrorDev([
    
  61.       'Warning: Unexpected type for suspenseCallback.',
    
  62.     ]);
    
  63. 
    
  64.     const elementMissingCallback = (
    
  65.       <React.Suspense fallback={'Waiting'}>
    
  66.         <PromiseComp />
    
  67.       </React.Suspense>
    
  68.     );
    
  69. 
    
  70.     ReactNoop.render(elementMissingCallback);
    
  71.     await expect(async () => await waitForAll([])).toErrorDev([]);
    
  72.   });
    
  73. 
    
  74.   // @gate www
    
  75.   it('1 then 0 suspense callback', async () => {
    
  76.     const {promise, resolve, PromiseComp} = createThenable();
    
  77. 
    
  78.     let ops = [];
    
  79.     const suspenseCallback = thenables => {
    
  80.       ops.push(thenables);
    
  81.     };
    
  82. 
    
  83.     const element = (
    
  84.       <React.Suspense suspenseCallback={suspenseCallback} fallback={'Waiting'}>
    
  85.         <PromiseComp />
    
  86.       </React.Suspense>
    
  87.     );
    
  88. 
    
  89.     ReactNoop.render(element);
    
  90.     await waitForAll([]);
    
  91.     expect(ReactNoop).toMatchRenderedOutput('Waiting');
    
  92.     expect(ops).toEqual([new Set([promise])]);
    
  93.     ops = [];
    
  94. 
    
  95.     await act(() => resolve());
    
  96.     await waitForAll([]);
    
  97.     expect(ReactNoop).toMatchRenderedOutput('Done');
    
  98.     expect(ops).toEqual([]);
    
  99.   });
    
  100. 
    
  101.   // @gate www
    
  102.   it('2 then 1 then 0 suspense callback', async () => {
    
  103.     const {
    
  104.       promise: promise1,
    
  105.       resolve: resolve1,
    
  106.       PromiseComp: PromiseComp1,
    
  107.     } = createThenable();
    
  108.     const {
    
  109.       promise: promise2,
    
  110.       resolve: resolve2,
    
  111.       PromiseComp: PromiseComp2,
    
  112.     } = createThenable();
    
  113. 
    
  114.     let ops = [];
    
  115.     const suspenseCallback1 = thenables => {
    
  116.       ops.push(thenables);
    
  117.     };
    
  118. 
    
  119.     const element = (
    
  120.       <React.Suspense
    
  121.         suspenseCallback={suspenseCallback1}
    
  122.         fallback={'Waiting Tier 1'}>
    
  123.         <PromiseComp1 />
    
  124.         <PromiseComp2 />
    
  125.       </React.Suspense>
    
  126.     );
    
  127. 
    
  128.     ReactNoop.render(element);
    
  129.     await waitForAll([]);
    
  130.     expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
    
  131.     expect(ops).toEqual([new Set([promise1])]);
    
  132.     ops = [];
    
  133. 
    
  134.     await act(() => resolve1());
    
  135.     ReactNoop.render(element);
    
  136.     await waitForAll([]);
    
  137.     expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
    
  138.     expect(ops).toEqual([new Set([promise2])]);
    
  139.     ops = [];
    
  140. 
    
  141.     await act(() => resolve2());
    
  142.     ReactNoop.render(element);
    
  143.     await waitForAll([]);
    
  144.     expect(ReactNoop).toMatchRenderedOutput('DoneDone');
    
  145.     expect(ops).toEqual([]);
    
  146.   });
    
  147. 
    
  148.   // @gate www
    
  149.   it('nested suspense promises are reported only for their tier', async () => {
    
  150.     const {promise, PromiseComp} = createThenable();
    
  151. 
    
  152.     const ops1 = [];
    
  153.     const suspenseCallback1 = thenables => {
    
  154.       ops1.push(thenables);
    
  155.     };
    
  156.     const ops2 = [];
    
  157.     const suspenseCallback2 = thenables => {
    
  158.       ops2.push(thenables);
    
  159.     };
    
  160. 
    
  161.     const element = (
    
  162.       <React.Suspense
    
  163.         suspenseCallback={suspenseCallback1}
    
  164.         fallback={'Waiting Tier 1'}>
    
  165.         <React.Suspense
    
  166.           suspenseCallback={suspenseCallback2}
    
  167.           fallback={'Waiting Tier 2'}>
    
  168.           <PromiseComp />
    
  169.         </React.Suspense>
    
  170.       </React.Suspense>
    
  171.     );
    
  172. 
    
  173.     ReactNoop.render(element);
    
  174.     await waitForAll([]);
    
  175.     expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2');
    
  176.     expect(ops1).toEqual([]);
    
  177.     expect(ops2).toEqual([new Set([promise])]);
    
  178.   });
    
  179. 
    
  180.   // @gate www
    
  181.   it('competing suspense promises', async () => {
    
  182.     const {
    
  183.       promise: promise1,
    
  184.       resolve: resolve1,
    
  185.       PromiseComp: PromiseComp1,
    
  186.     } = createThenable();
    
  187.     const {
    
  188.       promise: promise2,
    
  189.       resolve: resolve2,
    
  190.       PromiseComp: PromiseComp2,
    
  191.     } = createThenable();
    
  192. 
    
  193.     let ops1 = [];
    
  194.     const suspenseCallback1 = thenables => {
    
  195.       ops1.push(thenables);
    
  196.     };
    
  197.     let ops2 = [];
    
  198.     const suspenseCallback2 = thenables => {
    
  199.       ops2.push(thenables);
    
  200.     };
    
  201. 
    
  202.     const element = (
    
  203.       <React.Suspense
    
  204.         suspenseCallback={suspenseCallback1}
    
  205.         fallback={'Waiting Tier 1'}>
    
  206.         <React.Suspense
    
  207.           suspenseCallback={suspenseCallback2}
    
  208.           fallback={'Waiting Tier 2'}>
    
  209.           <PromiseComp2 />
    
  210.         </React.Suspense>
    
  211.         <PromiseComp1 />
    
  212.       </React.Suspense>
    
  213.     );
    
  214. 
    
  215.     ReactNoop.render(element);
    
  216.     await waitForAll([]);
    
  217.     expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
    
  218.     expect(ops1).toEqual([new Set([promise1])]);
    
  219.     expect(ops2).toEqual([]);
    
  220.     ops1 = [];
    
  221.     ops2 = [];
    
  222. 
    
  223.     await act(() => resolve1());
    
  224.     expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2Done');
    
  225.     expect(ops1).toEqual([]);
    
  226.     expect(ops2).toEqual([new Set([promise2])]);
    
  227.     ops1 = [];
    
  228.     ops2 = [];
    
  229. 
    
  230.     await act(() => resolve2());
    
  231.     expect(ReactNoop).toMatchRenderedOutput('DoneDone');
    
  232.     expect(ops1).toEqual([]);
    
  233.     expect(ops2).toEqual([]);
    
  234.   });
    
  235. });