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. let ReactDOMServer;
    
  14. let Suspense;
    
  15. 
    
  16. describe('ReactDOMServerFB', () => {
    
  17.   beforeEach(() => {
    
  18.     jest.resetModules();
    
  19.     React = require('react');
    
  20.     ReactDOMServer = require('../ReactDOMServerFB');
    
  21.     Suspense = React.Suspense;
    
  22.   });
    
  23. 
    
  24.   const theError = new Error('This is an error');
    
  25.   function Throw() {
    
  26.     throw theError;
    
  27.   }
    
  28.   const theInfinitePromise = new Promise(() => {});
    
  29.   function InfiniteSuspend() {
    
  30.     throw theInfinitePromise;
    
  31.   }
    
  32. 
    
  33.   function readResult(stream) {
    
  34.     let result = '';
    
  35.     while (!ReactDOMServer.hasFinished(stream)) {
    
  36.       result += ReactDOMServer.renderNextChunk(stream);
    
  37.     }
    
  38.     return result;
    
  39.   }
    
  40. 
    
  41.   it('should be able to render basic HTML', async () => {
    
  42.     const stream = ReactDOMServer.renderToStream(<div>hello world</div>, {
    
  43.       onError(x) {
    
  44.         console.error(x);
    
  45.       },
    
  46.     });
    
  47.     const result = readResult(stream);
    
  48.     expect(result).toMatchInlineSnapshot(`"<div>hello world</div>"`);
    
  49.   });
    
  50. 
    
  51.   it('should emit bootstrap script src at the end', () => {
    
  52.     const stream = ReactDOMServer.renderToStream(<div>hello world</div>, {
    
  53.       bootstrapScriptContent: 'INIT();',
    
  54.       bootstrapScripts: ['init.js'],
    
  55.       bootstrapModules: ['init.mjs'],
    
  56.       onError(x) {
    
  57.         console.error(x);
    
  58.       },
    
  59.     });
    
  60.     const result = readResult(stream);
    
  61.     expect(result).toMatchInlineSnapshot(
    
  62.       `"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
    
  63.     );
    
  64.   });
    
  65. 
    
  66.   it('emits all HTML as one unit if we wait until the end to start', async () => {
    
  67.     let hasLoaded = false;
    
  68.     let resolve;
    
  69.     const promise = new Promise(r => (resolve = r));
    
  70.     function Wait() {
    
  71.       if (!hasLoaded) {
    
  72.         throw promise;
    
  73.       }
    
  74.       return 'Done';
    
  75.     }
    
  76.     const stream = ReactDOMServer.renderToStream(
    
  77.       <div>
    
  78.         <Suspense fallback="Loading">
    
  79.           <Wait />
    
  80.         </Suspense>
    
  81.       </div>,
    
  82.       {
    
  83.         onError(x) {
    
  84.           console.error(x);
    
  85.         },
    
  86.       },
    
  87.     );
    
  88.     await jest.runAllTimers();
    
  89.     // Resolve the loading.
    
  90.     hasLoaded = true;
    
  91.     await resolve();
    
  92. 
    
  93.     await jest.runAllTimers();
    
  94. 
    
  95.     const result = readResult(stream);
    
  96.     expect(result).toMatchInlineSnapshot(`"<div><!--$-->Done<!--/$--></div>"`);
    
  97.   });
    
  98. 
    
  99.   it('should throw an error when an error is thrown at the root', () => {
    
  100.     const reportedErrors = [];
    
  101.     const stream = ReactDOMServer.renderToStream(
    
  102.       <div>
    
  103.         <Throw />
    
  104.       </div>,
    
  105.       {
    
  106.         onError(x) {
    
  107.           reportedErrors.push(x);
    
  108.         },
    
  109.       },
    
  110.     );
    
  111. 
    
  112.     let caughtError = null;
    
  113.     let result = '';
    
  114.     try {
    
  115.       result = readResult(stream);
    
  116.     } catch (x) {
    
  117.       caughtError = x;
    
  118.     }
    
  119.     expect(caughtError).toBe(theError);
    
  120.     expect(result).toBe('');
    
  121.     expect(reportedErrors).toEqual([theError]);
    
  122.   });
    
  123. 
    
  124.   it('should throw an error when an error is thrown inside a fallback', () => {
    
  125.     const reportedErrors = [];
    
  126.     const stream = ReactDOMServer.renderToStream(
    
  127.       <div>
    
  128.         <Suspense fallback={<Throw />}>
    
  129.           <InfiniteSuspend />
    
  130.         </Suspense>
    
  131.       </div>,
    
  132.       {
    
  133.         onError(x) {
    
  134.           reportedErrors.push(x);
    
  135.         },
    
  136.       },
    
  137.     );
    
  138. 
    
  139.     let caughtError = null;
    
  140.     let result = '';
    
  141.     try {
    
  142.       result = readResult(stream);
    
  143.     } catch (x) {
    
  144.       caughtError = x;
    
  145.     }
    
  146.     expect(caughtError).toBe(theError);
    
  147.     expect(result).toBe('');
    
  148.     expect(reportedErrors).toEqual([theError]);
    
  149.   });
    
  150. 
    
  151.   it('should not throw an error when an error is thrown inside suspense boundary', async () => {
    
  152.     const reportedErrors = [];
    
  153.     const stream = ReactDOMServer.renderToStream(
    
  154.       <div>
    
  155.         <Suspense fallback={<div>Loading</div>}>
    
  156.           <Throw />
    
  157.         </Suspense>
    
  158.       </div>,
    
  159.       {
    
  160.         onError(x) {
    
  161.           reportedErrors.push(x);
    
  162.         },
    
  163.       },
    
  164.     );
    
  165. 
    
  166.     const result = readResult(stream);
    
  167.     expect(result).toContain('Loading');
    
  168.     expect(reportedErrors).toEqual([theError]);
    
  169.   });
    
  170. 
    
  171.   it('should be able to complete by aborting even if the promise never resolves', () => {
    
  172.     const errors = [];
    
  173.     const stream = ReactDOMServer.renderToStream(
    
  174.       <div>
    
  175.         <Suspense fallback={<div>Loading</div>}>
    
  176.           <InfiniteSuspend />
    
  177.         </Suspense>
    
  178.       </div>,
    
  179.       {
    
  180.         onError(x) {
    
  181.           errors.push(x.message);
    
  182.         },
    
  183.       },
    
  184.     );
    
  185. 
    
  186.     const partial = ReactDOMServer.renderNextChunk(stream);
    
  187.     expect(partial).toContain('Loading');
    
  188. 
    
  189.     ReactDOMServer.abortStream(stream);
    
  190. 
    
  191.     const remaining = readResult(stream);
    
  192.     expect(remaining).toEqual('');
    
  193. 
    
  194.     expect(errors).toEqual([
    
  195.       'The render was aborted by the server without a reason.',
    
  196.     ]);
    
  197.   });
    
  198. 
    
  199.   it('should allow setting an abort reason', () => {
    
  200.     const errors = [];
    
  201.     const stream = ReactDOMServer.renderToStream(
    
  202.       <div>
    
  203.         <Suspense fallback={<div>Loading</div>}>
    
  204.           <InfiniteSuspend />
    
  205.         </Suspense>
    
  206.       </div>,
    
  207.       {
    
  208.         onError(error) {
    
  209.           errors.push(error);
    
  210.         },
    
  211.       },
    
  212.     );
    
  213.     ReactDOMServer.abortStream(stream, theError);
    
  214.     expect(errors).toEqual([theError]);
    
  215.   });
    
  216. });