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 ./scripts/jest/ReactDOMServerIntegrationEnvironment
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. import {
    
  13.   insertNodesAndExecuteScripts,
    
  14.   mergeOptions,
    
  15.   stripExternalRuntimeInNodes,
    
  16.   withLoadingReadyState,
    
  17.   getVisibleChildren,
    
  18. } from '../test-utils/FizzTestUtils';
    
  19. 
    
  20. let JSDOM;
    
  21. let Stream;
    
  22. let Scheduler;
    
  23. let React;
    
  24. let ReactDOM;
    
  25. let ReactDOMClient;
    
  26. let ReactDOMFizzServer;
    
  27. let ReactDOMFizzStatic;
    
  28. let Suspense;
    
  29. let SuspenseList;
    
  30. let useSyncExternalStore;
    
  31. let useSyncExternalStoreWithSelector;
    
  32. let use;
    
  33. let useFormState;
    
  34. let PropTypes;
    
  35. let textCache;
    
  36. let writable;
    
  37. let CSPnonce = null;
    
  38. let container;
    
  39. let buffer = '';
    
  40. let hasErrored = false;
    
  41. let fatalError = undefined;
    
  42. let renderOptions;
    
  43. let waitFor;
    
  44. let waitForAll;
    
  45. let assertLog;
    
  46. let waitForPaint;
    
  47. let clientAct;
    
  48. let streamingContainer;
    
  49. 
    
  50. describe('ReactDOMFizzServer', () => {
    
  51.   beforeEach(() => {
    
  52.     jest.resetModules();
    
  53.     JSDOM = require('jsdom').JSDOM;
    
  54. 
    
  55.     const jsdom = new JSDOM(
    
  56.       '<!DOCTYPE html><html><head></head><body><div id="container">',
    
  57.       {
    
  58.         runScripts: 'dangerously',
    
  59.       },
    
  60.     );
    
  61.     // We mock matchMedia. for simplicity it only matches 'all' or '' and misses everything else
    
  62.     Object.defineProperty(jsdom.window, 'matchMedia', {
    
  63.       writable: true,
    
  64.       value: jest.fn().mockImplementation(query => ({
    
  65.         matches: query === 'all' || query === '',
    
  66.         media: query,
    
  67.       })),
    
  68.     });
    
  69.     streamingContainer = null;
    
  70.     global.window = jsdom.window;
    
  71.     global.document = global.window.document;
    
  72.     global.navigator = global.window.navigator;
    
  73.     global.Node = global.window.Node;
    
  74.     global.addEventListener = global.window.addEventListener;
    
  75.     global.MutationObserver = global.window.MutationObserver;
    
  76.     container = document.getElementById('container');
    
  77. 
    
  78.     Scheduler = require('scheduler');
    
  79.     React = require('react');
    
  80.     ReactDOM = require('react-dom');
    
  81.     ReactDOMClient = require('react-dom/client');
    
  82.     ReactDOMFizzServer = require('react-dom/server');
    
  83.     if (__EXPERIMENTAL__) {
    
  84.       ReactDOMFizzStatic = require('react-dom/static');
    
  85.     }
    
  86.     Stream = require('stream');
    
  87.     Suspense = React.Suspense;
    
  88.     use = React.use;
    
  89.     if (gate(flags => flags.enableSuspenseList)) {
    
  90.       SuspenseList = React.unstable_SuspenseList;
    
  91.     }
    
  92.     useFormState = ReactDOM.useFormState;
    
  93. 
    
  94.     PropTypes = require('prop-types');
    
  95. 
    
  96.     const InternalTestUtils = require('internal-test-utils');
    
  97.     waitForAll = InternalTestUtils.waitForAll;
    
  98.     waitFor = InternalTestUtils.waitFor;
    
  99.     waitForPaint = InternalTestUtils.waitForPaint;
    
  100.     assertLog = InternalTestUtils.assertLog;
    
  101.     clientAct = InternalTestUtils.act;
    
  102. 
    
  103.     if (gate(flags => flags.source)) {
    
  104.       // The `with-selector` module composes the main `use-sync-external-store`
    
  105.       // entrypoint. In the compiled artifacts, this is resolved to the `shim`
    
  106.       // implementation by our build config, but when running the tests against
    
  107.       // the source files, we need to tell Jest how to resolve it. Because this
    
  108.       // is a source module, this mock has no affect on the build tests.
    
  109.       jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
    
  110.         jest.requireActual('react'),
    
  111.       );
    
  112.     }
    
  113.     useSyncExternalStore = React.useSyncExternalStore;
    
  114.     useSyncExternalStoreWithSelector =
    
  115.       require('use-sync-external-store/with-selector').useSyncExternalStoreWithSelector;
    
  116. 
    
  117.     textCache = new Map();
    
  118. 
    
  119.     buffer = '';
    
  120.     hasErrored = false;
    
  121. 
    
  122.     writable = new Stream.PassThrough();
    
  123.     writable.setEncoding('utf8');
    
  124.     writable.on('data', chunk => {
    
  125.       buffer += chunk;
    
  126.     });
    
  127.     writable.on('error', error => {
    
  128.       hasErrored = true;
    
  129.       fatalError = error;
    
  130.     });
    
  131. 
    
  132.     renderOptions = {};
    
  133.     if (gate(flags => flags.enableFizzExternalRuntime)) {
    
  134.       renderOptions.unstable_externalRuntimeSrc =
    
  135.         'react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js';
    
  136.     }
    
  137.   });
    
  138. 
    
  139.   function expectErrors(errorsArr, toBeDevArr, toBeProdArr) {
    
  140.     const mappedErrows = errorsArr.map(({error, errorInfo}) => {
    
  141.       const stack = errorInfo && errorInfo.componentStack;
    
  142.       const digest = error.digest;
    
  143.       if (stack) {
    
  144.         return [error.message, digest, normalizeCodeLocInfo(stack)];
    
  145.       } else if (digest) {
    
  146.         return [error.message, digest];
    
  147.       }
    
  148.       return error.message;
    
  149.     });
    
  150.     if (__DEV__) {
    
  151.       expect(mappedErrows).toEqual(toBeDevArr);
    
  152.     } else {
    
  153.       expect(mappedErrows).toEqual(toBeProdArr);
    
  154.     }
    
  155.   }
    
  156. 
    
  157.   function componentStack(components) {
    
  158.     return components
    
  159.       .map(component => `\n    in ${component} (at **)`)
    
  160.       .join('');
    
  161.   }
    
  162. 
    
  163.   const bodyStartMatch = /<body(?:>| .*?>)/;
    
  164.   const headStartMatch = /<head(?:>| .*?>)/;
    
  165. 
    
  166.   async function act(callback) {
    
  167.     await callback();
    
  168.     // Await one turn around the event loop.
    
  169.     // This assumes that we'll flush everything we have so far.
    
  170.     await new Promise(resolve => {
    
  171.       setImmediate(resolve);
    
  172.     });
    
  173.     if (hasErrored) {
    
  174.       throw fatalError;
    
  175.     }
    
  176.     // JSDOM doesn't support stream HTML parser so we need to give it a proper fragment.
    
  177.     // We also want to execute any scripts that are embedded.
    
  178.     // We assume that we have now received a proper fragment of HTML.
    
  179.     let bufferedContent = buffer;
    
  180.     buffer = '';
    
  181. 
    
  182.     if (!bufferedContent) {
    
  183.       return;
    
  184.     }
    
  185. 
    
  186.     await withLoadingReadyState(async () => {
    
  187.       const bodyMatch = bufferedContent.match(bodyStartMatch);
    
  188.       const headMatch = bufferedContent.match(headStartMatch);
    
  189. 
    
  190.       if (streamingContainer === null) {
    
  191.         // This is the first streamed content. We decide here where to insert it. If we get <html>, <head>, or <body>
    
  192.         // we abandon the pre-built document and start from scratch. If we get anything else we assume it goes into the
    
  193.         // container. This is not really production behavior because you can't correctly stream into a deep div effectively
    
  194.         // but it's pragmatic for tests.
    
  195. 
    
  196.         if (
    
  197.           bufferedContent.startsWith('<head>') ||
    
  198.           bufferedContent.startsWith('<head ') ||
    
  199.           bufferedContent.startsWith('<body>') ||
    
  200.           bufferedContent.startsWith('<body ')
    
  201.         ) {
    
  202.           // wrap in doctype to normalize the parsing process
    
  203.           bufferedContent = '<!DOCTYPE html><html>' + bufferedContent;
    
  204.         } else if (
    
  205.           bufferedContent.startsWith('<html>') ||
    
  206.           bufferedContent.startsWith('<html ')
    
  207.         ) {
    
  208.           throw new Error(
    
  209.             'Recieved <html> without a <!DOCTYPE html> which is almost certainly a bug in React',
    
  210.           );
    
  211.         }
    
  212. 
    
  213.         if (bufferedContent.startsWith('<!DOCTYPE html>')) {
    
  214.           // we can just use the whole document
    
  215.           const tempDom = new JSDOM(bufferedContent);
    
  216. 
    
  217.           // Wipe existing head and body content
    
  218.           document.head.innerHTML = '';
    
  219.           document.body.innerHTML = '';
    
  220. 
    
  221.           // Copy the <html> attributes over
    
  222.           const tempHtmlNode = tempDom.window.document.documentElement;
    
  223.           for (let i = 0; i < tempHtmlNode.attributes.length; i++) {
    
  224.             const attr = tempHtmlNode.attributes[i];
    
  225.             document.documentElement.setAttribute(attr.name, attr.value);
    
  226.           }
    
  227. 
    
  228.           if (headMatch) {
    
  229.             // We parsed a head open tag. we need to copy head attributes and insert future
    
  230.             // content into <head>
    
  231.             streamingContainer = document.head;
    
  232.             const tempHeadNode = tempDom.window.document.head;
    
  233.             for (let i = 0; i < tempHeadNode.attributes.length; i++) {
    
  234.               const attr = tempHeadNode.attributes[i];
    
  235.               document.head.setAttribute(attr.name, attr.value);
    
  236.             }
    
  237.             const source = document.createElement('head');
    
  238.             source.innerHTML = tempHeadNode.innerHTML;
    
  239.             await insertNodesAndExecuteScripts(source, document.head, CSPnonce);
    
  240.           }
    
  241. 
    
  242.           if (bodyMatch) {
    
  243.             // We parsed a body open tag. we need to copy head attributes and insert future
    
  244.             // content into <body>
    
  245.             streamingContainer = document.body;
    
  246.             const tempBodyNode = tempDom.window.document.body;
    
  247.             for (let i = 0; i < tempBodyNode.attributes.length; i++) {
    
  248.               const attr = tempBodyNode.attributes[i];
    
  249.               document.body.setAttribute(attr.name, attr.value);
    
  250.             }
    
  251.             const source = document.createElement('body');
    
  252.             source.innerHTML = tempBodyNode.innerHTML;
    
  253.             await insertNodesAndExecuteScripts(source, document.body, CSPnonce);
    
  254.           }
    
  255. 
    
  256.           if (!headMatch && !bodyMatch) {
    
  257.             throw new Error('expected <head> or <body> after <html>');
    
  258.           }
    
  259.         } else {
    
  260.           // we assume we are streaming into the default container'
    
  261.           streamingContainer = container;
    
  262.           const div = document.createElement('div');
    
  263.           div.innerHTML = bufferedContent;
    
  264.           await insertNodesAndExecuteScripts(div, container, CSPnonce);
    
  265.         }
    
  266.       } else if (streamingContainer === document.head) {
    
  267.         bufferedContent = '<!DOCTYPE html><html><head>' + bufferedContent;
    
  268.         const tempDom = new JSDOM(bufferedContent);
    
  269. 
    
  270.         const tempHeadNode = tempDom.window.document.head;
    
  271.         const source = document.createElement('head');
    
  272.         source.innerHTML = tempHeadNode.innerHTML;
    
  273.         await insertNodesAndExecuteScripts(source, document.head, CSPnonce);
    
  274. 
    
  275.         if (bodyMatch) {
    
  276.           streamingContainer = document.body;
    
  277. 
    
  278.           const tempBodyNode = tempDom.window.document.body;
    
  279.           for (let i = 0; i < tempBodyNode.attributes.length; i++) {
    
  280.             const attr = tempBodyNode.attributes[i];
    
  281.             document.body.setAttribute(attr.name, attr.value);
    
  282.           }
    
  283.           const bodySource = document.createElement('body');
    
  284.           bodySource.innerHTML = tempBodyNode.innerHTML;
    
  285.           await insertNodesAndExecuteScripts(
    
  286.             bodySource,
    
  287.             document.body,
    
  288.             CSPnonce,
    
  289.           );
    
  290.         }
    
  291.       } else {
    
  292.         const div = document.createElement('div');
    
  293.         div.innerHTML = bufferedContent;
    
  294.         await insertNodesAndExecuteScripts(div, streamingContainer, CSPnonce);
    
  295.       }
    
  296.     }, document);
    
  297.   }
    
  298. 
    
  299.   function resolveText(text) {
    
  300.     const record = textCache.get(text);
    
  301.     if (record === undefined) {
    
  302.       const newRecord = {
    
  303.         status: 'resolved',
    
  304.         value: text,
    
  305.       };
    
  306.       textCache.set(text, newRecord);
    
  307.     } else if (record.status === 'pending') {
    
  308.       const thenable = record.value;
    
  309.       record.status = 'resolved';
    
  310.       record.value = text;
    
  311.       thenable.pings.forEach(t => t());
    
  312.     }
    
  313.   }
    
  314. 
    
  315.   function rejectText(text, error) {
    
  316.     const record = textCache.get(text);
    
  317.     if (record === undefined) {
    
  318.       const newRecord = {
    
  319.         status: 'rejected',
    
  320.         value: error,
    
  321.       };
    
  322.       textCache.set(text, newRecord);
    
  323.     } else if (record.status === 'pending') {
    
  324.       const thenable = record.value;
    
  325.       record.status = 'rejected';
    
  326.       record.value = error;
    
  327.       thenable.pings.forEach(t => t());
    
  328.     }
    
  329.   }
    
  330. 
    
  331.   function readText(text) {
    
  332.     const record = textCache.get(text);
    
  333.     if (record !== undefined) {
    
  334.       switch (record.status) {
    
  335.         case 'pending':
    
  336.           throw record.value;
    
  337.         case 'rejected':
    
  338.           throw record.value;
    
  339.         case 'resolved':
    
  340.           return record.value;
    
  341.       }
    
  342.     } else {
    
  343.       const thenable = {
    
  344.         pings: [],
    
  345.         then(resolve) {
    
  346.           if (newRecord.status === 'pending') {
    
  347.             thenable.pings.push(resolve);
    
  348.           } else {
    
  349.             Promise.resolve().then(() => resolve(newRecord.value));
    
  350.           }
    
  351.         },
    
  352.       };
    
  353. 
    
  354.       const newRecord = {
    
  355.         status: 'pending',
    
  356.         value: thenable,
    
  357.       };
    
  358.       textCache.set(text, newRecord);
    
  359. 
    
  360.       throw thenable;
    
  361.     }
    
  362.   }
    
  363. 
    
  364.   function Text({text}) {
    
  365.     return text;
    
  366.   }
    
  367. 
    
  368.   function AsyncText({text}) {
    
  369.     return readText(text);
    
  370.   }
    
  371. 
    
  372.   function AsyncTextWrapped({as, text}) {
    
  373.     const As = as;
    
  374.     return <As>{readText(text)}</As>;
    
  375.   }
    
  376.   function renderToPipeableStream(jsx, options) {
    
  377.     // Merge options with renderOptions, which may contain featureFlag specific behavior
    
  378.     return ReactDOMFizzServer.renderToPipeableStream(
    
  379.       jsx,
    
  380.       mergeOptions(options, renderOptions),
    
  381.     );
    
  382.   }
    
  383. 
    
  384.   it('should asynchronously load a lazy component', async () => {
    
  385.     const originalConsoleError = console.error;
    
  386.     const mockError = jest.fn();
    
  387.     console.error = (...args) => {
    
  388.       if (args.length > 1) {
    
  389.         if (typeof args[1] === 'object') {
    
  390.           mockError(args[0].split('\n')[0]);
    
  391.           return;
    
  392.         }
    
  393.       }
    
  394.       mockError(...args.map(normalizeCodeLocInfo));
    
  395.     };
    
  396. 
    
  397.     let resolveA;
    
  398.     const LazyA = React.lazy(() => {
    
  399.       return new Promise(r => {
    
  400.         resolveA = r;
    
  401.       });
    
  402.     });
    
  403. 
    
  404.     let resolveB;
    
  405.     const LazyB = React.lazy(() => {
    
  406.       return new Promise(r => {
    
  407.         resolveB = r;
    
  408.       });
    
  409.     });
    
  410. 
    
  411.     function TextWithPunctuation({text, punctuation}) {
    
  412.       return <Text text={text + punctuation} />;
    
  413.     }
    
  414.     // This tests that default props of the inner element is resolved.
    
  415.     TextWithPunctuation.defaultProps = {
    
  416.       punctuation: '!',
    
  417.     };
    
  418. 
    
  419.     try {
    
  420.       await act(() => {
    
  421.         const {pipe} = renderToPipeableStream(
    
  422.           <div>
    
  423.             <div>
    
  424.               <Suspense fallback={<Text text="Loading..." />}>
    
  425.                 <LazyA text="Hello" />
    
  426.               </Suspense>
    
  427.             </div>
    
  428.             <div>
    
  429.               <Suspense fallback={<Text text="Loading..." />}>
    
  430.                 <LazyB text="world" />
    
  431.               </Suspense>
    
  432.             </div>
    
  433.           </div>,
    
  434.         );
    
  435.         pipe(writable);
    
  436.       });
    
  437. 
    
  438.       expect(getVisibleChildren(container)).toEqual(
    
  439.         <div>
    
  440.           <div>Loading...</div>
    
  441.           <div>Loading...</div>
    
  442.         </div>,
    
  443.       );
    
  444.       await act(() => {
    
  445.         resolveA({default: Text});
    
  446.       });
    
  447.       expect(getVisibleChildren(container)).toEqual(
    
  448.         <div>
    
  449.           <div>Hello</div>
    
  450.           <div>Loading...</div>
    
  451.         </div>,
    
  452.       );
    
  453.       await act(() => {
    
  454.         resolveB({default: TextWithPunctuation});
    
  455.       });
    
  456.       expect(getVisibleChildren(container)).toEqual(
    
  457.         <div>
    
  458.           <div>Hello</div>
    
  459.           <div>world!</div>
    
  460.         </div>,
    
  461.       );
    
  462. 
    
  463.       if (__DEV__) {
    
  464.         expect(mockError).toHaveBeenCalledWith(
    
  465.           'Warning: %s: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.%s',
    
  466.           'TextWithPunctuation',
    
  467.           '\n    in TextWithPunctuation (at **)\n' +
    
  468.             '    in Lazy (at **)\n' +
    
  469.             '    in Suspense (at **)\n' +
    
  470.             '    in div (at **)\n' +
    
  471.             '    in div (at **)',
    
  472.         );
    
  473.       } else {
    
  474.         expect(mockError).not.toHaveBeenCalled();
    
  475.       }
    
  476.     } finally {
    
  477.       console.error = originalConsoleError;
    
  478.     }
    
  479.   });
    
  480. 
    
  481.   it('#23331: does not warn about hydration mismatches if something suspended in an earlier sibling', async () => {
    
  482.     const makeApp = () => {
    
  483.       let resolve;
    
  484.       const imports = new Promise(r => {
    
  485.         resolve = () => r({default: () => <span id="async">async</span>});
    
  486.       });
    
  487.       const Lazy = React.lazy(() => imports);
    
  488. 
    
  489.       const App = () => (
    
  490.         <div>
    
  491.           <Suspense fallback={<span>Loading...</span>}>
    
  492.             <Lazy />
    
  493.             <span id="after">after</span>
    
  494.           </Suspense>
    
  495.         </div>
    
  496.       );
    
  497. 
    
  498.       return [App, resolve];
    
  499.     };
    
  500. 
    
  501.     // Server-side
    
  502.     const [App, resolve] = makeApp();
    
  503.     await act(() => {
    
  504.       const {pipe} = renderToPipeableStream(<App />);
    
  505.       pipe(writable);
    
  506.     });
    
  507.     expect(getVisibleChildren(container)).toEqual(
    
  508.       <div>
    
  509.         <span>Loading...</span>
    
  510.       </div>,
    
  511.     );
    
  512.     await act(() => {
    
  513.       resolve();
    
  514.     });
    
  515.     expect(getVisibleChildren(container)).toEqual(
    
  516.       <div>
    
  517.         <span id="async">async</span>
    
  518.         <span id="after">after</span>
    
  519.       </div>,
    
  520.     );
    
  521. 
    
  522.     // Client-side
    
  523.     const [HydrateApp, hydrateResolve] = makeApp();
    
  524.     await act(() => {
    
  525.       ReactDOMClient.hydrateRoot(container, <HydrateApp />);
    
  526.     });
    
  527. 
    
  528.     expect(getVisibleChildren(container)).toEqual(
    
  529.       <div>
    
  530.         <span id="async">async</span>
    
  531.         <span id="after">after</span>
    
  532.       </div>,
    
  533.     );
    
  534. 
    
  535.     await act(() => {
    
  536.       hydrateResolve();
    
  537.     });
    
  538.     expect(getVisibleChildren(container)).toEqual(
    
  539.       <div>
    
  540.         <span id="async">async</span>
    
  541.         <span id="after">after</span>
    
  542.       </div>,
    
  543.     );
    
  544.   });
    
  545. 
    
  546.   it('should support nonce for bootstrap and runtime scripts', async () => {
    
  547.     CSPnonce = 'R4nd0m';
    
  548.     try {
    
  549.       let resolve;
    
  550.       const Lazy = React.lazy(() => {
    
  551.         return new Promise(r => {
    
  552.           resolve = r;
    
  553.         });
    
  554.       });
    
  555. 
    
  556.       await act(() => {
    
  557.         const {pipe} = renderToPipeableStream(
    
  558.           <div>
    
  559.             <Suspense fallback={<Text text="Loading..." />}>
    
  560.               <Lazy text="Hello" />
    
  561.             </Suspense>
    
  562.           </div>,
    
  563.           {
    
  564.             nonce: 'R4nd0m',
    
  565.             bootstrapScriptContent: 'function noop(){}',
    
  566.             bootstrapScripts: [
    
  567.               'init.js',
    
  568.               {src: 'init2.js', integrity: 'init2hash'},
    
  569.             ],
    
  570.             bootstrapModules: [
    
  571.               'init.mjs',
    
  572.               {src: 'init2.mjs', integrity: 'init2hash'},
    
  573.             ],
    
  574.           },
    
  575.         );
    
  576.         pipe(writable);
    
  577.       });
    
  578. 
    
  579.       expect(getVisibleChildren(container)).toEqual([
    
  580.         <link
    
  581.           rel="preload"
    
  582.           fetchpriority="low"
    
  583.           href="init.js"
    
  584.           as="script"
    
  585.           nonce={CSPnonce}
    
  586.         />,
    
  587.         <link
    
  588.           rel="preload"
    
  589.           fetchpriority="low"
    
  590.           href="init2.js"
    
  591.           as="script"
    
  592.           nonce={CSPnonce}
    
  593.           integrity="init2hash"
    
  594.         />,
    
  595.         <link
    
  596.           rel="modulepreload"
    
  597.           fetchpriority="low"
    
  598.           href="init.mjs"
    
  599.           nonce={CSPnonce}
    
  600.         />,
    
  601.         <link
    
  602.           rel="modulepreload"
    
  603.           fetchpriority="low"
    
  604.           href="init2.mjs"
    
  605.           nonce={CSPnonce}
    
  606.           integrity="init2hash"
    
  607.         />,
    
  608.         <div>Loading...</div>,
    
  609.       ]);
    
  610. 
    
  611.       // check that there are 6 scripts with a matching nonce:
    
  612.       // The runtime script, an inline bootstrap script, two bootstrap scripts and two bootstrap modules
    
  613.       expect(
    
  614.         Array.from(container.getElementsByTagName('script')).filter(
    
  615.           node => node.getAttribute('nonce') === CSPnonce,
    
  616.         ).length,
    
  617.       ).toEqual(6);
    
  618. 
    
  619.       await act(() => {
    
  620.         resolve({default: Text});
    
  621.       });
    
  622.       expect(getVisibleChildren(container)).toEqual([
    
  623.         <link
    
  624.           rel="preload"
    
  625.           fetchpriority="low"
    
  626.           href="init.js"
    
  627.           as="script"
    
  628.           nonce={CSPnonce}
    
  629.         />,
    
  630.         <link
    
  631.           rel="preload"
    
  632.           fetchpriority="low"
    
  633.           href="init2.js"
    
  634.           as="script"
    
  635.           nonce={CSPnonce}
    
  636.           integrity="init2hash"
    
  637.         />,
    
  638.         <link
    
  639.           rel="modulepreload"
    
  640.           fetchpriority="low"
    
  641.           href="init.mjs"
    
  642.           nonce={CSPnonce}
    
  643.         />,
    
  644.         <link
    
  645.           rel="modulepreload"
    
  646.           fetchpriority="low"
    
  647.           href="init2.mjs"
    
  648.           nonce={CSPnonce}
    
  649.           integrity="init2hash"
    
  650.         />,
    
  651.         <div>Hello</div>,
    
  652.       ]);
    
  653.     } finally {
    
  654.       CSPnonce = null;
    
  655.     }
    
  656.   });
    
  657. 
    
  658.   it('should not automatically add nonce to rendered scripts', async () => {
    
  659.     CSPnonce = 'R4nd0m';
    
  660.     try {
    
  661.       await act(async () => {
    
  662.         const {pipe} = renderToPipeableStream(
    
  663.           <html>
    
  664.             <body>
    
  665.               <script nonce={CSPnonce}>{'try { foo() } catch (e) {} ;'}</script>
    
  666.               <script nonce={CSPnonce} src="foo" async={true} />
    
  667.               <script src="bar" />
    
  668.               <script src="baz" integrity="qux" async={true} />
    
  669.               <script type="module" src="quux" async={true} />
    
  670.               <script type="module" src="corge" async={true} />
    
  671.               <script
    
  672.                 type="module"
    
  673.                 src="grault"
    
  674.                 integrity="garply"
    
  675.                 async={true}
    
  676.               />
    
  677.             </body>
    
  678.           </html>,
    
  679.           {
    
  680.             nonce: CSPnonce,
    
  681.           },
    
  682.         );
    
  683.         pipe(writable);
    
  684.       });
    
  685. 
    
  686.       expect(
    
  687.         stripExternalRuntimeInNodes(
    
  688.           document.getElementsByTagName('script'),
    
  689.           renderOptions.unstable_externalRuntimeSrc,
    
  690.         ).map(n => n.outerHTML),
    
  691.       ).toEqual([
    
  692.         `<script nonce="${CSPnonce}" src="foo" async=""></script>`,
    
  693.         `<script src="baz" integrity="qux" async=""></script>`,
    
  694.         `<script type="module" src="quux" async=""></script>`,
    
  695.         `<script type="module" src="corge" async=""></script>`,
    
  696.         `<script type="module" src="grault" integrity="garply" async=""></script>`,
    
  697.         `<script nonce="${CSPnonce}">try { foo() } catch (e) {} ;</script>`,
    
  698.         `<script src="bar"></script>`,
    
  699.       ]);
    
  700.     } finally {
    
  701.       CSPnonce = null;
    
  702.     }
    
  703.   });
    
  704. 
    
  705.   it('should client render a boundary if a lazy component rejects', async () => {
    
  706.     let rejectComponent;
    
  707.     const LazyComponent = React.lazy(() => {
    
  708.       return new Promise((resolve, reject) => {
    
  709.         rejectComponent = reject;
    
  710.       });
    
  711.     });
    
  712. 
    
  713.     function App({isClient}) {
    
  714.       return (
    
  715.         <div>
    
  716.           <Suspense fallback={<Text text="Loading..." />}>
    
  717.             {isClient ? <Text text="Hello" /> : <LazyComponent text="Hello" />}
    
  718.           </Suspense>
    
  719.         </div>
    
  720.       );
    
  721.     }
    
  722. 
    
  723.     let bootstrapped = false;
    
  724.     const errors = [];
    
  725.     window.__INIT__ = function () {
    
  726.       bootstrapped = true;
    
  727.       // Attempt to hydrate the content.
    
  728.       ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  729.         onRecoverableError(error, errorInfo) {
    
  730.           errors.push({error, errorInfo});
    
  731.         },
    
  732.       });
    
  733.     };
    
  734. 
    
  735.     const theError = new Error('Test');
    
  736.     const loggedErrors = [];
    
  737.     function onError(x) {
    
  738.       loggedErrors.push(x);
    
  739.       return 'Hash of (' + x.message + ')';
    
  740.     }
    
  741.     const expectedDigest = onError(theError);
    
  742.     loggedErrors.length = 0;
    
  743. 
    
  744.     await act(() => {
    
  745.       const {pipe} = renderToPipeableStream(<App isClient={false} />, {
    
  746.         bootstrapScriptContent: '__INIT__();',
    
  747.         onError,
    
  748.       });
    
  749.       pipe(writable);
    
  750.     });
    
  751.     expect(loggedErrors).toEqual([]);
    
  752.     expect(bootstrapped).toBe(true);
    
  753. 
    
  754.     await waitForAll([]);
    
  755. 
    
  756.     // We're still loading because we're waiting for the server to stream more content.
    
  757.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  758. 
    
  759.     expect(loggedErrors).toEqual([]);
    
  760. 
    
  761.     await act(() => {
    
  762.       rejectComponent(theError);
    
  763.     });
    
  764. 
    
  765.     expect(loggedErrors).toEqual([theError]);
    
  766. 
    
  767.     // We haven't ran the client hydration yet.
    
  768.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  769. 
    
  770.     // Now we can client render it instead.
    
  771.     await waitForAll([]);
    
  772.     expectErrors(
    
  773.       errors,
    
  774.       [
    
  775.         [
    
  776.           theError.message,
    
  777.           expectedDigest,
    
  778.           componentStack(['Lazy', 'Suspense', 'div', 'App']),
    
  779.         ],
    
  780.       ],
    
  781.       [
    
  782.         [
    
  783.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  784.           expectedDigest,
    
  785.         ],
    
  786.       ],
    
  787.     );
    
  788. 
    
  789.     // The client rendered HTML is now in place.
    
  790.     expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  791. 
    
  792.     expect(loggedErrors).toEqual([theError]);
    
  793.   });
    
  794. 
    
  795.   it('should asynchronously load a lazy element', async () => {
    
  796.     let resolveElement;
    
  797.     const lazyElement = React.lazy(() => {
    
  798.       return new Promise(r => {
    
  799.         resolveElement = r;
    
  800.       });
    
  801.     });
    
  802. 
    
  803.     await act(() => {
    
  804.       const {pipe} = renderToPipeableStream(
    
  805.         <div>
    
  806.           <Suspense fallback={<Text text="Loading..." />}>
    
  807.             {lazyElement}
    
  808.           </Suspense>
    
  809.         </div>,
    
  810.       );
    
  811.       pipe(writable);
    
  812.     });
    
  813.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  814.     // Because there is no content inside the Suspense boundary that could've
    
  815.     // been written, we expect to not see any additional partial data flushed
    
  816.     // yet.
    
  817.     expect(
    
  818.       stripExternalRuntimeInNodes(
    
  819.         container.childNodes,
    
  820.         renderOptions.unstable_externalRuntimeSrc,
    
  821.       ).length,
    
  822.     ).toBe(1);
    
  823.     await act(() => {
    
  824.       resolveElement({default: <Text text="Hello" />});
    
  825.     });
    
  826.     expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  827.   });
    
  828. 
    
  829.   it('should client render a boundary if a lazy element rejects', async () => {
    
  830.     let rejectElement;
    
  831.     const element = <Text text="Hello" />;
    
  832.     const lazyElement = React.lazy(() => {
    
  833.       return new Promise((resolve, reject) => {
    
  834.         rejectElement = reject;
    
  835.       });
    
  836.     });
    
  837. 
    
  838.     const theError = new Error('Test');
    
  839.     const loggedErrors = [];
    
  840.     function onError(x) {
    
  841.       loggedErrors.push(x);
    
  842.       return 'hash of (' + x.message + ')';
    
  843.     }
    
  844.     const expectedDigest = onError(theError);
    
  845.     loggedErrors.length = 0;
    
  846. 
    
  847.     function App({isClient}) {
    
  848.       return (
    
  849.         <div>
    
  850.           <Suspense fallback={<Text text="Loading..." />}>
    
  851.             {isClient ? element : lazyElement}
    
  852.           </Suspense>
    
  853.         </div>
    
  854.       );
    
  855.     }
    
  856. 
    
  857.     await act(() => {
    
  858.       const {pipe} = renderToPipeableStream(
    
  859.         <App isClient={false} />,
    
  860. 
    
  861.         {
    
  862.           onError,
    
  863.         },
    
  864.       );
    
  865.       pipe(writable);
    
  866.     });
    
  867.     expect(loggedErrors).toEqual([]);
    
  868. 
    
  869.     const errors = [];
    
  870.     // Attempt to hydrate the content.
    
  871.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  872.       onRecoverableError(error, errorInfo) {
    
  873.         errors.push({error, errorInfo});
    
  874.       },
    
  875.     });
    
  876.     await waitForAll([]);
    
  877. 
    
  878.     // We're still loading because we're waiting for the server to stream more content.
    
  879.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  880. 
    
  881.     expect(loggedErrors).toEqual([]);
    
  882. 
    
  883.     await act(() => {
    
  884.       rejectElement(theError);
    
  885.     });
    
  886. 
    
  887.     expect(loggedErrors).toEqual([theError]);
    
  888. 
    
  889.     // We haven't ran the client hydration yet.
    
  890.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  891. 
    
  892.     // Now we can client render it instead.
    
  893.     await waitForAll([]);
    
  894. 
    
  895.     expectErrors(
    
  896.       errors,
    
  897.       [
    
  898.         [
    
  899.           theError.message,
    
  900.           expectedDigest,
    
  901.           componentStack(['Suspense', 'div', 'App']),
    
  902.         ],
    
  903.       ],
    
  904.       [
    
  905.         [
    
  906.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  907.           expectedDigest,
    
  908.         ],
    
  909.       ],
    
  910.     );
    
  911. 
    
  912.     // The client rendered HTML is now in place.
    
  913.     // expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  914. 
    
  915.     expect(loggedErrors).toEqual([theError]);
    
  916.   });
    
  917. 
    
  918.   it('Errors in boundaries should be sent to the client and reported on client render - Error before flushing', async () => {
    
  919.     function Indirection({level, children}) {
    
  920.       if (level > 0) {
    
  921.         return <Indirection level={level - 1}>{children}</Indirection>;
    
  922.       }
    
  923.       return children;
    
  924.     }
    
  925. 
    
  926.     const theError = new Error('uh oh');
    
  927. 
    
  928.     function Erroring({isClient}) {
    
  929.       if (isClient) {
    
  930.         return 'Hello World';
    
  931.       }
    
  932.       throw theError;
    
  933.     }
    
  934. 
    
  935.     function App({isClient}) {
    
  936.       return (
    
  937.         <div>
    
  938.           <Suspense fallback={<span>loading...</span>}>
    
  939.             <Erroring isClient={isClient} />
    
  940.           </Suspense>
    
  941.         </div>
    
  942.       );
    
  943.     }
    
  944. 
    
  945.     const loggedErrors = [];
    
  946.     function onError(x) {
    
  947.       loggedErrors.push(x);
    
  948.       return 'hash(' + x.message + ')';
    
  949.     }
    
  950.     const expectedDigest = onError(theError);
    
  951.     loggedErrors.length = 0;
    
  952. 
    
  953.     await act(() => {
    
  954.       const {pipe} = renderToPipeableStream(
    
  955.         <App />,
    
  956. 
    
  957.         {
    
  958.           onError,
    
  959.         },
    
  960.       );
    
  961.       pipe(writable);
    
  962.     });
    
  963.     expect(loggedErrors).toEqual([theError]);
    
  964. 
    
  965.     const errors = [];
    
  966.     // Attempt to hydrate the content.
    
  967.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  968.       onRecoverableError(error, errorInfo) {
    
  969.         errors.push({error, errorInfo});
    
  970.       },
    
  971.     });
    
  972.     await waitForAll([]);
    
  973. 
    
  974.     expect(getVisibleChildren(container)).toEqual(<div>Hello World</div>);
    
  975. 
    
  976.     expectErrors(
    
  977.       errors,
    
  978.       [
    
  979.         [
    
  980.           theError.message,
    
  981.           expectedDigest,
    
  982.           componentStack(['Erroring', 'Suspense', 'div', 'App']),
    
  983.         ],
    
  984.       ],
    
  985.       [
    
  986.         [
    
  987.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  988.           expectedDigest,
    
  989.         ],
    
  990.       ],
    
  991.     );
    
  992.   });
    
  993. 
    
  994.   it('Errors in boundaries should be sent to the client and reported on client render - Error after flushing', async () => {
    
  995.     let rejectComponent;
    
  996.     const LazyComponent = React.lazy(() => {
    
  997.       return new Promise((resolve, reject) => {
    
  998.         rejectComponent = reject;
    
  999.       });
    
  1000.     });
    
  1001. 
    
  1002.     function App({isClient}) {
    
  1003.       return (
    
  1004.         <div>
    
  1005.           <Suspense fallback={<Text text="Loading..." />}>
    
  1006.             {isClient ? <Text text="Hello" /> : <LazyComponent text="Hello" />}
    
  1007.           </Suspense>
    
  1008.         </div>
    
  1009.       );
    
  1010.     }
    
  1011. 
    
  1012.     const loggedErrors = [];
    
  1013.     const theError = new Error('uh oh');
    
  1014.     function onError(x) {
    
  1015.       loggedErrors.push(x);
    
  1016.       return 'hash(' + x.message + ')';
    
  1017.     }
    
  1018.     const expectedDigest = onError(theError);
    
  1019.     loggedErrors.length = 0;
    
  1020. 
    
  1021.     await act(() => {
    
  1022.       const {pipe} = renderToPipeableStream(
    
  1023.         <App />,
    
  1024. 
    
  1025.         {
    
  1026.           onError,
    
  1027.         },
    
  1028.       );
    
  1029.       pipe(writable);
    
  1030.     });
    
  1031.     expect(loggedErrors).toEqual([]);
    
  1032. 
    
  1033.     const errors = [];
    
  1034.     // Attempt to hydrate the content.
    
  1035.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  1036.       onRecoverableError(error, errorInfo) {
    
  1037.         errors.push({error, errorInfo});
    
  1038.       },
    
  1039.     });
    
  1040.     await waitForAll([]);
    
  1041. 
    
  1042.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1043. 
    
  1044.     await act(() => {
    
  1045.       rejectComponent(theError);
    
  1046.     });
    
  1047. 
    
  1048.     expect(loggedErrors).toEqual([theError]);
    
  1049.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1050. 
    
  1051.     // Now we can client render it instead.
    
  1052.     await waitForAll([]);
    
  1053. 
    
  1054.     expectErrors(
    
  1055.       errors,
    
  1056.       [
    
  1057.         [
    
  1058.           theError.message,
    
  1059.           expectedDigest,
    
  1060.           componentStack(['Lazy', 'Suspense', 'div', 'App']),
    
  1061.         ],
    
  1062.       ],
    
  1063.       [
    
  1064.         [
    
  1065.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  1066.           expectedDigest,
    
  1067.         ],
    
  1068.       ],
    
  1069.     );
    
  1070. 
    
  1071.     // The client rendered HTML is now in place.
    
  1072.     expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  1073.     expect(loggedErrors).toEqual([theError]);
    
  1074.   });
    
  1075. 
    
  1076.   it('should asynchronously load the suspense boundary', async () => {
    
  1077.     await act(() => {
    
  1078.       const {pipe} = renderToPipeableStream(
    
  1079.         <div>
    
  1080.           <Suspense fallback={<Text text="Loading..." />}>
    
  1081.             <AsyncText text="Hello World" />
    
  1082.           </Suspense>
    
  1083.         </div>,
    
  1084.       );
    
  1085.       pipe(writable);
    
  1086.     });
    
  1087.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1088.     await act(() => {
    
  1089.       resolveText('Hello World');
    
  1090.     });
    
  1091.     expect(getVisibleChildren(container)).toEqual(<div>Hello World</div>);
    
  1092.   });
    
  1093. 
    
  1094.   it('waits for pending content to come in from the server and then hydrates it', async () => {
    
  1095.     const ref = React.createRef();
    
  1096. 
    
  1097.     function App() {
    
  1098.       return (
    
  1099.         <div>
    
  1100.           <Suspense fallback="Loading...">
    
  1101.             <h1 ref={ref}>
    
  1102.               <AsyncText text="Hello" />
    
  1103.             </h1>
    
  1104.           </Suspense>
    
  1105.         </div>
    
  1106.       );
    
  1107.     }
    
  1108. 
    
  1109.     let bootstrapped = false;
    
  1110.     window.__INIT__ = function () {
    
  1111.       bootstrapped = true;
    
  1112.       // Attempt to hydrate the content.
    
  1113.       ReactDOMClient.hydrateRoot(container, <App />);
    
  1114.     };
    
  1115. 
    
  1116.     await act(() => {
    
  1117.       const {pipe} = renderToPipeableStream(<App />, {
    
  1118.         bootstrapScriptContent: '__INIT__();',
    
  1119.       });
    
  1120.       pipe(writable);
    
  1121.     });
    
  1122. 
    
  1123.     // We're still showing a fallback.
    
  1124.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1125. 
    
  1126.     // We already bootstrapped.
    
  1127.     expect(bootstrapped).toBe(true);
    
  1128. 
    
  1129.     // Attempt to hydrate the content.
    
  1130.     await waitForAll([]);
    
  1131. 
    
  1132.     // We're still loading because we're waiting for the server to stream more content.
    
  1133.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1134. 
    
  1135.     // The server now updates the content in place in the fallback.
    
  1136.     await act(() => {
    
  1137.       resolveText('Hello');
    
  1138.     });
    
  1139. 
    
  1140.     // The final HTML is now in place.
    
  1141.     expect(getVisibleChildren(container)).toEqual(
    
  1142.       <div>
    
  1143.         <h1>Hello</h1>
    
  1144.       </div>,
    
  1145.     );
    
  1146.     const h1 = container.getElementsByTagName('h1')[0];
    
  1147. 
    
  1148.     // But it is not yet hydrated.
    
  1149.     expect(ref.current).toBe(null);
    
  1150. 
    
  1151.     await waitForAll([]);
    
  1152. 
    
  1153.     // Now it's hydrated.
    
  1154.     expect(ref.current).toBe(h1);
    
  1155.   });
    
  1156. 
    
  1157.   it('handles an error on the client if the server ends up erroring', async () => {
    
  1158.     const ref = React.createRef();
    
  1159. 
    
  1160.     class ErrorBoundary extends React.Component {
    
  1161.       state = {error: null};
    
  1162.       static getDerivedStateFromError(error) {
    
  1163.         return {error};
    
  1164.       }
    
  1165.       render() {
    
  1166.         if (this.state.error) {
    
  1167.           return <b ref={ref}>{this.state.error.message}</b>;
    
  1168.         }
    
  1169.         return this.props.children;
    
  1170.       }
    
  1171.     }
    
  1172. 
    
  1173.     function App() {
    
  1174.       return (
    
  1175.         <ErrorBoundary>
    
  1176.           <div>
    
  1177.             <Suspense fallback="Loading...">
    
  1178.               <span ref={ref}>
    
  1179.                 <AsyncText text="This Errors" />
    
  1180.               </span>
    
  1181.             </Suspense>
    
  1182.           </div>
    
  1183.         </ErrorBoundary>
    
  1184.       );
    
  1185.     }
    
  1186. 
    
  1187.     const loggedErrors = [];
    
  1188. 
    
  1189.     // We originally suspend the boundary and start streaming the loading state.
    
  1190.     await act(() => {
    
  1191.       const {pipe} = renderToPipeableStream(
    
  1192.         <App />,
    
  1193. 
    
  1194.         {
    
  1195.           onError(x) {
    
  1196.             loggedErrors.push(x);
    
  1197.           },
    
  1198.         },
    
  1199.       );
    
  1200.       pipe(writable);
    
  1201.     });
    
  1202. 
    
  1203.     // We're still showing a fallback.
    
  1204.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1205. 
    
  1206.     expect(loggedErrors).toEqual([]);
    
  1207. 
    
  1208.     // Attempt to hydrate the content.
    
  1209.     ReactDOMClient.hydrateRoot(container, <App />);
    
  1210.     await waitForAll([]);
    
  1211. 
    
  1212.     // We're still loading because we're waiting for the server to stream more content.
    
  1213.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1214. 
    
  1215.     const theError = new Error('Error Message');
    
  1216.     await act(() => {
    
  1217.       rejectText('This Errors', theError);
    
  1218.     });
    
  1219. 
    
  1220.     expect(loggedErrors).toEqual([theError]);
    
  1221. 
    
  1222.     // The server errored, but we still haven't hydrated. We don't know if the
    
  1223.     // client will succeed yet, so we still show the loading state.
    
  1224.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1225.     expect(ref.current).toBe(null);
    
  1226. 
    
  1227.     // Flush the hydration.
    
  1228.     await waitForAll([]);
    
  1229. 
    
  1230.     // Hydrating should've generated an error and replaced the suspense boundary.
    
  1231.     expect(getVisibleChildren(container)).toEqual(<b>Error Message</b>);
    
  1232. 
    
  1233.     const b = container.getElementsByTagName('b')[0];
    
  1234.     expect(ref.current).toBe(b);
    
  1235.   });
    
  1236. 
    
  1237.   // @gate enableSuspenseList
    
  1238.   it('shows inserted items before pending in a SuspenseList as fallbacks while hydrating', async () => {
    
  1239.     const ref = React.createRef();
    
  1240. 
    
  1241.     // These are hoisted to avoid them from rerendering.
    
  1242.     const a = (
    
  1243.       <Suspense fallback="Loading A">
    
  1244.         <span ref={ref}>
    
  1245.           <AsyncText text="A" />
    
  1246.         </span>
    
  1247.       </Suspense>
    
  1248.     );
    
  1249.     const b = (
    
  1250.       <Suspense fallback="Loading B">
    
  1251.         <span>
    
  1252.           <Text text="B" />
    
  1253.         </span>
    
  1254.       </Suspense>
    
  1255.     );
    
  1256. 
    
  1257.     function App({showMore}) {
    
  1258.       return (
    
  1259.         <SuspenseList revealOrder="forwards">
    
  1260.           {a}
    
  1261.           {b}
    
  1262.           {showMore ? (
    
  1263.             <Suspense fallback="Loading C">
    
  1264.               <span>C</span>
    
  1265.             </Suspense>
    
  1266.           ) : null}
    
  1267.         </SuspenseList>
    
  1268.       );
    
  1269.     }
    
  1270. 
    
  1271.     // We originally suspend the boundary and start streaming the loading state.
    
  1272.     await act(() => {
    
  1273.       const {pipe} = renderToPipeableStream(<App showMore={false} />);
    
  1274.       pipe(writable);
    
  1275.     });
    
  1276. 
    
  1277.     const root = ReactDOMClient.hydrateRoot(
    
  1278.       container,
    
  1279.       <App showMore={false} />,
    
  1280.     );
    
  1281.     await waitForAll([]);
    
  1282. 
    
  1283.     // We're not hydrated yet.
    
  1284.     expect(ref.current).toBe(null);
    
  1285.     expect(getVisibleChildren(container)).toEqual([
    
  1286.       'Loading A',
    
  1287.       // TODO: This is incorrect. It should be "Loading B" but Fizz SuspenseList
    
  1288.       // isn't implemented fully yet.
    
  1289.       <span>B</span>,
    
  1290.     ]);
    
  1291. 
    
  1292.     // Add more rows before we've hydrated the first two.
    
  1293.     root.render(<App showMore={true} />);
    
  1294.     await waitForAll([]);
    
  1295. 
    
  1296.     // We're not hydrated yet.
    
  1297.     expect(ref.current).toBe(null);
    
  1298. 
    
  1299.     // We haven't resolved yet.
    
  1300.     expect(getVisibleChildren(container)).toEqual([
    
  1301.       'Loading A',
    
  1302.       // TODO: This is incorrect. It should be "Loading B" but Fizz SuspenseList
    
  1303.       // isn't implemented fully yet.
    
  1304.       <span>B</span>,
    
  1305.       'Loading C',
    
  1306.     ]);
    
  1307. 
    
  1308.     await act(async () => {
    
  1309.       await resolveText('A');
    
  1310.     });
    
  1311. 
    
  1312.     await waitForAll([]);
    
  1313. 
    
  1314.     expect(getVisibleChildren(container)).toEqual([
    
  1315.       <span>A</span>,
    
  1316.       <span>B</span>,
    
  1317.       <span>C</span>,
    
  1318.     ]);
    
  1319. 
    
  1320.     const span = container.getElementsByTagName('span')[0];
    
  1321.     expect(ref.current).toBe(span);
    
  1322.   });
    
  1323. 
    
  1324.   it('client renders a boundary if it does not resolve before aborting', async () => {
    
  1325.     function App() {
    
  1326.       return (
    
  1327.         <div>
    
  1328.           <Suspense fallback="Loading...">
    
  1329.             <h1>
    
  1330.               <AsyncText text="Hello" />
    
  1331.             </h1>
    
  1332.           </Suspense>
    
  1333.         </div>
    
  1334.       );
    
  1335.     }
    
  1336. 
    
  1337.     const loggedErrors = [];
    
  1338.     const expectedDigest = 'Hash for Abort';
    
  1339.     function onError(error) {
    
  1340.       loggedErrors.push(error);
    
  1341.       return expectedDigest;
    
  1342.     }
    
  1343. 
    
  1344.     let controls;
    
  1345.     await act(() => {
    
  1346.       controls = renderToPipeableStream(<App />, {onError});
    
  1347.       controls.pipe(writable);
    
  1348.     });
    
  1349. 
    
  1350.     // We're still showing a fallback.
    
  1351. 
    
  1352.     const errors = [];
    
  1353.     // Attempt to hydrate the content.
    
  1354.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  1355.       onRecoverableError(error, errorInfo) {
    
  1356.         errors.push({error, errorInfo});
    
  1357.       },
    
  1358.     });
    
  1359.     await waitForAll([]);
    
  1360. 
    
  1361.     // We're still loading because we're waiting for the server to stream more content.
    
  1362.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1363. 
    
  1364.     // We abort the server response.
    
  1365.     await act(() => {
    
  1366.       controls.abort();
    
  1367.     });
    
  1368. 
    
  1369.     // We still can't render it on the client.
    
  1370.     await waitForAll([]);
    
  1371.     expectErrors(
    
  1372.       errors,
    
  1373.       [
    
  1374.         [
    
  1375.           'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.',
    
  1376.           expectedDigest,
    
  1377.           componentStack(['h1', 'Suspense', 'div', 'App']),
    
  1378.         ],
    
  1379.       ],
    
  1380.       [
    
  1381.         [
    
  1382.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  1383.           expectedDigest,
    
  1384.         ],
    
  1385.       ],
    
  1386.     );
    
  1387.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  1388. 
    
  1389.     // We now resolve it on the client.
    
  1390.     await clientAct(() => resolveText('Hello'));
    
  1391.     assertLog([]);
    
  1392. 
    
  1393.     // The client rendered HTML is now in place.
    
  1394.     expect(getVisibleChildren(container)).toEqual(
    
  1395.       <div>
    
  1396.         <h1>Hello</h1>
    
  1397.       </div>,
    
  1398.     );
    
  1399.   });
    
  1400. 
    
  1401.   it('should allow for two containers to be written to the same document', async () => {
    
  1402.     // We create two passthrough streams for each container to write into.
    
  1403.     // Notably we don't implement a end() call for these. Because we don't want to
    
  1404.     // close the underlying stream just because one of the streams is done. Instead
    
  1405.     // we manually close when both are done.
    
  1406.     const writableA = new Stream.Writable();
    
  1407.     writableA._write = (chunk, encoding, next) => {
    
  1408.       writable.write(chunk, encoding, next);
    
  1409.     };
    
  1410.     const writableB = new Stream.Writable();
    
  1411.     writableB._write = (chunk, encoding, next) => {
    
  1412.       writable.write(chunk, encoding, next);
    
  1413.     };
    
  1414. 
    
  1415.     await act(() => {
    
  1416.       const {pipe} = renderToPipeableStream(
    
  1417.         // We use two nested boundaries to flush out coverage of an old reentrancy bug.
    
  1418.         <Suspense fallback="Loading...">
    
  1419.           <Suspense fallback={<Text text="Loading A..." />}>
    
  1420.             <>
    
  1421.               <Text text="This will show A: " />
    
  1422.               <div>
    
  1423.                 <AsyncText text="A" />
    
  1424.               </div>
    
  1425.             </>
    
  1426.           </Suspense>
    
  1427.         </Suspense>,
    
  1428.         {
    
  1429.           identifierPrefix: 'A_',
    
  1430.           onShellReady() {
    
  1431.             writableA.write('<div id="container-A">');
    
  1432.             pipe(writableA);
    
  1433.             writableA.write('</div>');
    
  1434.           },
    
  1435.         },
    
  1436.       );
    
  1437.     });
    
  1438. 
    
  1439.     await act(() => {
    
  1440.       const {pipe} = renderToPipeableStream(
    
  1441.         <Suspense fallback={<Text text="Loading B..." />}>
    
  1442.           <Text text="This will show B: " />
    
  1443.           <div>
    
  1444.             <AsyncText text="B" />
    
  1445.           </div>
    
  1446.         </Suspense>,
    
  1447.         {
    
  1448.           identifierPrefix: 'B_',
    
  1449.           onShellReady() {
    
  1450.             writableB.write('<div id="container-B">');
    
  1451.             pipe(writableB);
    
  1452.             writableB.write('</div>');
    
  1453.           },
    
  1454.         },
    
  1455.       );
    
  1456.     });
    
  1457. 
    
  1458.     expect(getVisibleChildren(container)).toEqual([
    
  1459.       <div id="container-A">Loading A...</div>,
    
  1460.       <div id="container-B">Loading B...</div>,
    
  1461.     ]);
    
  1462. 
    
  1463.     await act(() => {
    
  1464.       resolveText('B');
    
  1465.     });
    
  1466. 
    
  1467.     expect(getVisibleChildren(container)).toEqual([
    
  1468.       <div id="container-A">Loading A...</div>,
    
  1469.       <div id="container-B">
    
  1470.         This will show B: <div>B</div>
    
  1471.       </div>,
    
  1472.     ]);
    
  1473. 
    
  1474.     await act(() => {
    
  1475.       resolveText('A');
    
  1476.     });
    
  1477. 
    
  1478.     // We're done writing both streams now.
    
  1479.     writable.end();
    
  1480. 
    
  1481.     expect(getVisibleChildren(container)).toEqual([
    
  1482.       <div id="container-A">
    
  1483.         This will show A: <div>A</div>
    
  1484.       </div>,
    
  1485.       <div id="container-B">
    
  1486.         This will show B: <div>B</div>
    
  1487.       </div>,
    
  1488.     ]);
    
  1489.   });
    
  1490. 
    
  1491.   it('can resolve async content in esoteric parents', async () => {
    
  1492.     function AsyncOption({text}) {
    
  1493.       return <option>{readText(text)}</option>;
    
  1494.     }
    
  1495. 
    
  1496.     function AsyncCol({className}) {
    
  1497.       return <col className={readText(className)} />;
    
  1498.     }
    
  1499. 
    
  1500.     function AsyncPath({id}) {
    
  1501.       return <path id={readText(id)} />;
    
  1502.     }
    
  1503. 
    
  1504.     function AsyncMi({id}) {
    
  1505.       return <mi id={readText(id)} />;
    
  1506.     }
    
  1507. 
    
  1508.     function App() {
    
  1509.       return (
    
  1510.         <div>
    
  1511.           <select>
    
  1512.             <Suspense fallback="Loading...">
    
  1513.               <AsyncOption text="Hello" />
    
  1514.             </Suspense>
    
  1515.           </select>
    
  1516.           <Suspense fallback="Loading...">
    
  1517.             <table>
    
  1518.               <colgroup>
    
  1519.                 <AsyncCol className="World" />
    
  1520.               </colgroup>
    
  1521.             </table>
    
  1522.             <svg>
    
  1523.               <g>
    
  1524.                 <AsyncPath id="my-path" />
    
  1525.               </g>
    
  1526.             </svg>
    
  1527.             <math>
    
  1528.               <AsyncMi id="my-mi" />
    
  1529.             </math>
    
  1530.           </Suspense>
    
  1531.         </div>
    
  1532.       );
    
  1533.     }
    
  1534. 
    
  1535.     await act(() => {
    
  1536.       const {pipe} = renderToPipeableStream(<App />);
    
  1537.       pipe(writable);
    
  1538.     });
    
  1539. 
    
  1540.     expect(getVisibleChildren(container)).toEqual(
    
  1541.       <div>
    
  1542.         <select>Loading...</select>Loading...
    
  1543.       </div>,
    
  1544.     );
    
  1545. 
    
  1546.     await act(() => {
    
  1547.       resolveText('Hello');
    
  1548.     });
    
  1549. 
    
  1550.     await act(() => {
    
  1551.       resolveText('World');
    
  1552.     });
    
  1553. 
    
  1554.     await act(() => {
    
  1555.       resolveText('my-path');
    
  1556.       resolveText('my-mi');
    
  1557.     });
    
  1558. 
    
  1559.     expect(getVisibleChildren(container)).toEqual(
    
  1560.       <div>
    
  1561.         <select>
    
  1562.           <option>Hello</option>
    
  1563.         </select>
    
  1564.         <table>
    
  1565.           <colgroup>
    
  1566.             <col class="World" />
    
  1567.           </colgroup>
    
  1568.         </table>
    
  1569.         <svg>
    
  1570.           <g>
    
  1571.             <path id="my-path" />
    
  1572.           </g>
    
  1573.         </svg>
    
  1574.         <math>
    
  1575.           <mi id="my-mi" />
    
  1576.         </math>
    
  1577.       </div>,
    
  1578.     );
    
  1579. 
    
  1580.     expect(container.querySelector('#my-path').namespaceURI).toBe(
    
  1581.       'http://www.w3.org/2000/svg',
    
  1582.     );
    
  1583.     expect(container.querySelector('#my-mi').namespaceURI).toBe(
    
  1584.       'http://www.w3.org/1998/Math/MathML',
    
  1585.     );
    
  1586.   });
    
  1587. 
    
  1588.   it('can resolve async content in table parents', async () => {
    
  1589.     function AsyncTableBody({className, children}) {
    
  1590.       return <tbody className={readText(className)}>{children}</tbody>;
    
  1591.     }
    
  1592. 
    
  1593.     function AsyncTableRow({className, children}) {
    
  1594.       return <tr className={readText(className)}>{children}</tr>;
    
  1595.     }
    
  1596. 
    
  1597.     function AsyncTableCell({text}) {
    
  1598.       return <td>{readText(text)}</td>;
    
  1599.     }
    
  1600. 
    
  1601.     function App() {
    
  1602.       return (
    
  1603.         <table>
    
  1604.           <Suspense
    
  1605.             fallback={
    
  1606.               <tbody>
    
  1607.                 <tr>
    
  1608.                   <td>Loading...</td>
    
  1609.                 </tr>
    
  1610.               </tbody>
    
  1611.             }>
    
  1612.             <AsyncTableBody className="A">
    
  1613.               <AsyncTableRow className="B">
    
  1614.                 <AsyncTableCell text="C" />
    
  1615.               </AsyncTableRow>
    
  1616.             </AsyncTableBody>
    
  1617.           </Suspense>
    
  1618.         </table>
    
  1619.       );
    
  1620.     }
    
  1621. 
    
  1622.     await act(() => {
    
  1623.       const {pipe} = renderToPipeableStream(<App />);
    
  1624.       pipe(writable);
    
  1625.     });
    
  1626. 
    
  1627.     expect(getVisibleChildren(container)).toEqual(
    
  1628.       <table>
    
  1629.         <tbody>
    
  1630.           <tr>
    
  1631.             <td>Loading...</td>
    
  1632.           </tr>
    
  1633.         </tbody>
    
  1634.       </table>,
    
  1635.     );
    
  1636. 
    
  1637.     await act(() => {
    
  1638.       resolveText('A');
    
  1639.     });
    
  1640. 
    
  1641.     await act(() => {
    
  1642.       resolveText('B');
    
  1643.     });
    
  1644. 
    
  1645.     await act(() => {
    
  1646.       resolveText('C');
    
  1647.     });
    
  1648. 
    
  1649.     expect(getVisibleChildren(container)).toEqual(
    
  1650.       <table>
    
  1651.         <tbody class="A">
    
  1652.           <tr class="B">
    
  1653.             <td>C</td>
    
  1654.           </tr>
    
  1655.         </tbody>
    
  1656.       </table>,
    
  1657.     );
    
  1658.   });
    
  1659. 
    
  1660.   it('can stream into an SVG container', async () => {
    
  1661.     function AsyncPath({id}) {
    
  1662.       return <path id={readText(id)} />;
    
  1663.     }
    
  1664. 
    
  1665.     function App() {
    
  1666.       return (
    
  1667.         <g>
    
  1668.           <Suspense fallback={<text>Loading...</text>}>
    
  1669.             <AsyncPath id="my-path" />
    
  1670.           </Suspense>
    
  1671.         </g>
    
  1672.       );
    
  1673.     }
    
  1674. 
    
  1675.     await act(() => {
    
  1676.       const {pipe} = renderToPipeableStream(
    
  1677.         <App />,
    
  1678. 
    
  1679.         {
    
  1680.           namespaceURI: 'http://www.w3.org/2000/svg',
    
  1681.           onShellReady() {
    
  1682.             writable.write('<svg>');
    
  1683.             pipe(writable);
    
  1684.             writable.write('</svg>');
    
  1685.           },
    
  1686.         },
    
  1687.       );
    
  1688.     });
    
  1689. 
    
  1690.     expect(getVisibleChildren(container)).toEqual(
    
  1691.       <svg>
    
  1692.         <g>
    
  1693.           <text>Loading...</text>
    
  1694.         </g>
    
  1695.       </svg>,
    
  1696.     );
    
  1697. 
    
  1698.     await act(() => {
    
  1699.       resolveText('my-path');
    
  1700.     });
    
  1701. 
    
  1702.     expect(getVisibleChildren(container)).toEqual(
    
  1703.       <svg>
    
  1704.         <g>
    
  1705.           <path id="my-path" />
    
  1706.         </g>
    
  1707.       </svg>,
    
  1708.     );
    
  1709. 
    
  1710.     expect(container.querySelector('#my-path').namespaceURI).toBe(
    
  1711.       'http://www.w3.org/2000/svg',
    
  1712.     );
    
  1713.   });
    
  1714. 
    
  1715.   function normalizeCodeLocInfo(str) {
    
  1716.     return (
    
  1717.       str &&
    
  1718.       String(str).replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
    
  1719.         return '\n    in ' + name + ' (at **)';
    
  1720.       })
    
  1721.     );
    
  1722.   }
    
  1723. 
    
  1724.   it('should include a component stack across suspended boundaries', async () => {
    
  1725.     function B() {
    
  1726.       const children = [readText('Hello'), readText('World')];
    
  1727.       // Intentionally trigger a key warning here.
    
  1728.       return (
    
  1729.         <div>
    
  1730.           {children.map(t => (
    
  1731.             <span>{t}</span>
    
  1732.           ))}
    
  1733.         </div>
    
  1734.       );
    
  1735.     }
    
  1736.     function C() {
    
  1737.       return (
    
  1738.         <inCorrectTag>
    
  1739.           <Text text="Loading" />
    
  1740.         </inCorrectTag>
    
  1741.       );
    
  1742.     }
    
  1743.     function A() {
    
  1744.       return (
    
  1745.         <div>
    
  1746.           <Suspense fallback={<C />}>
    
  1747.             <B />
    
  1748.           </Suspense>
    
  1749.         </div>
    
  1750.       );
    
  1751.     }
    
  1752. 
    
  1753.     // We can't use the toErrorDev helper here because this is an async act.
    
  1754.     const originalConsoleError = console.error;
    
  1755.     const mockError = jest.fn();
    
  1756.     console.error = (...args) => {
    
  1757.       mockError(...args.map(normalizeCodeLocInfo));
    
  1758.     };
    
  1759. 
    
  1760.     try {
    
  1761.       await act(() => {
    
  1762.         const {pipe} = renderToPipeableStream(<A />);
    
  1763.         pipe(writable);
    
  1764.       });
    
  1765. 
    
  1766.       expect(getVisibleChildren(container)).toEqual(
    
  1767.         <div>
    
  1768.           <incorrecttag>Loading</incorrecttag>
    
  1769.         </div>,
    
  1770.       );
    
  1771. 
    
  1772.       if (__DEV__) {
    
  1773.         expect(mockError).toHaveBeenCalledWith(
    
  1774.           'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s',
    
  1775.           'inCorrectTag',
    
  1776.           '\n' +
    
  1777.             '    in inCorrectTag (at **)\n' +
    
  1778.             '    in C (at **)\n' +
    
  1779.             '    in Suspense (at **)\n' +
    
  1780.             '    in div (at **)\n' +
    
  1781.             '    in A (at **)',
    
  1782.         );
    
  1783.         mockError.mockClear();
    
  1784.       } else {
    
  1785.         expect(mockError).not.toHaveBeenCalled();
    
  1786.       }
    
  1787. 
    
  1788.       await act(() => {
    
  1789.         resolveText('Hello');
    
  1790.         resolveText('World');
    
  1791.       });
    
  1792. 
    
  1793.       if (__DEV__) {
    
  1794.         expect(mockError).toHaveBeenCalledWith(
    
  1795.           'Warning: Each child in a list should have a unique "key" prop.%s%s' +
    
  1796.             ' See https://reactjs.org/link/warning-keys for more information.%s',
    
  1797.           '\n\nCheck the top-level render call using <div>.',
    
  1798.           '',
    
  1799.           '\n' +
    
  1800.             '    in span (at **)\n' +
    
  1801.             '    in B (at **)\n' +
    
  1802.             '    in Suspense (at **)\n' +
    
  1803.             '    in div (at **)\n' +
    
  1804.             '    in A (at **)',
    
  1805.         );
    
  1806.       } else {
    
  1807.         expect(mockError).not.toHaveBeenCalled();
    
  1808.       }
    
  1809. 
    
  1810.       expect(getVisibleChildren(container)).toEqual(
    
  1811.         <div>
    
  1812.           <div>
    
  1813.             <span>Hello</span>
    
  1814.             <span>World</span>
    
  1815.           </div>
    
  1816.         </div>,
    
  1817.       );
    
  1818.     } finally {
    
  1819.       console.error = originalConsoleError;
    
  1820.     }
    
  1821.   });
    
  1822. 
    
  1823.   // @gate !disableLegacyContext
    
  1824.   it('should can suspend in a class component with legacy context', async () => {
    
  1825.     class TestProvider extends React.Component {
    
  1826.       static childContextTypes = {
    
  1827.         test: PropTypes.string,
    
  1828.       };
    
  1829.       state = {ctxToSet: null};
    
  1830.       static getDerivedStateFromProps(props, state) {
    
  1831.         return {ctxToSet: props.ctx};
    
  1832.       }
    
  1833.       getChildContext() {
    
  1834.         return {
    
  1835.           test: this.state.ctxToSet,
    
  1836.         };
    
  1837.       }
    
  1838.       render() {
    
  1839.         return this.props.children;
    
  1840.       }
    
  1841.     }
    
  1842. 
    
  1843.     class TestConsumer extends React.Component {
    
  1844.       static contextTypes = {
    
  1845.         test: PropTypes.string,
    
  1846.       };
    
  1847.       render() {
    
  1848.         const child = (
    
  1849.           <b>
    
  1850.             <Text text={this.context.test} />
    
  1851.           </b>
    
  1852.         );
    
  1853.         if (this.props.prefix) {
    
  1854.           return [readText(this.props.prefix), child];
    
  1855.         }
    
  1856.         return child;
    
  1857.       }
    
  1858.     }
    
  1859. 
    
  1860.     await act(() => {
    
  1861.       const {pipe} = renderToPipeableStream(
    
  1862.         <TestProvider ctx="A">
    
  1863.           <div>
    
  1864.             <Suspense fallback={[<Text text="Loading: " />, <TestConsumer />]}>
    
  1865.               <TestProvider ctx="B">
    
  1866.                 <TestConsumer prefix="Hello: " />
    
  1867.               </TestProvider>
    
  1868.               <TestConsumer />
    
  1869.             </Suspense>
    
  1870.           </div>
    
  1871.         </TestProvider>,
    
  1872.       );
    
  1873.       pipe(writable);
    
  1874.     });
    
  1875.     expect(getVisibleChildren(container)).toEqual(
    
  1876.       <div>
    
  1877.         Loading: <b>A</b>
    
  1878.       </div>,
    
  1879.     );
    
  1880.     await act(() => {
    
  1881.       resolveText('Hello: ');
    
  1882.     });
    
  1883.     expect(getVisibleChildren(container)).toEqual(
    
  1884.       <div>
    
  1885.         Hello: <b>B</b>
    
  1886.         <b>A</b>
    
  1887.       </div>,
    
  1888.     );
    
  1889.   });
    
  1890. 
    
  1891.   it('should resume the context from where it left off', async () => {
    
  1892.     const ContextA = React.createContext('A0');
    
  1893.     const ContextB = React.createContext('B0');
    
  1894. 
    
  1895.     function PrintA() {
    
  1896.       return (
    
  1897.         <ContextA.Consumer>{value => <Text text={value} />}</ContextA.Consumer>
    
  1898.       );
    
  1899.     }
    
  1900. 
    
  1901.     class PrintB extends React.Component {
    
  1902.       static contextType = ContextB;
    
  1903.       render() {
    
  1904.         return <Text text={this.context} />;
    
  1905.       }
    
  1906.     }
    
  1907. 
    
  1908.     function AsyncParent({text, children}) {
    
  1909.       return (
    
  1910.         <>
    
  1911.           <AsyncText text={text} />
    
  1912.           <b>{children}</b>
    
  1913.         </>
    
  1914.       );
    
  1915.     }
    
  1916. 
    
  1917.     await act(() => {
    
  1918.       const {pipe} = renderToPipeableStream(
    
  1919.         <div>
    
  1920.           <PrintA />
    
  1921.           <div>
    
  1922.             <ContextA.Provider value="A0.1">
    
  1923.               <Suspense fallback={<Text text="Loading..." />}>
    
  1924.                 <AsyncParent text="Child:">
    
  1925.                   <PrintA />
    
  1926.                 </AsyncParent>
    
  1927.                 <PrintB />
    
  1928.               </Suspense>
    
  1929.             </ContextA.Provider>
    
  1930.           </div>
    
  1931.           <PrintA />
    
  1932.         </div>,
    
  1933.       );
    
  1934.       pipe(writable);
    
  1935.     });
    
  1936.     expect(getVisibleChildren(container)).toEqual(
    
  1937.       <div>
    
  1938.         A0<div>Loading...</div>A0
    
  1939.       </div>,
    
  1940.     );
    
  1941.     await act(() => {
    
  1942.       resolveText('Child:');
    
  1943.     });
    
  1944.     expect(getVisibleChildren(container)).toEqual(
    
  1945.       <div>
    
  1946.         A0
    
  1947.         <div>
    
  1948.           Child:<b>A0.1</b>B0
    
  1949.         </div>
    
  1950.         A0
    
  1951.       </div>,
    
  1952.     );
    
  1953.   });
    
  1954. 
    
  1955.   it('should recover the outer context when an error happens inside a provider', async () => {
    
  1956.     const ContextA = React.createContext('A0');
    
  1957.     const ContextB = React.createContext('B0');
    
  1958. 
    
  1959.     function PrintA() {
    
  1960.       return (
    
  1961.         <ContextA.Consumer>{value => <Text text={value} />}</ContextA.Consumer>
    
  1962.       );
    
  1963.     }
    
  1964. 
    
  1965.     class PrintB extends React.Component {
    
  1966.       static contextType = ContextB;
    
  1967.       render() {
    
  1968.         return <Text text={this.context} />;
    
  1969.       }
    
  1970.     }
    
  1971. 
    
  1972.     function Throws() {
    
  1973.       const value = React.useContext(ContextA);
    
  1974.       throw new Error(value);
    
  1975.     }
    
  1976. 
    
  1977.     const loggedErrors = [];
    
  1978.     await act(() => {
    
  1979.       const {pipe} = renderToPipeableStream(
    
  1980.         <div>
    
  1981.           <PrintA />
    
  1982.           <div>
    
  1983.             <ContextA.Provider value="A0.1">
    
  1984.               <Suspense
    
  1985.                 fallback={
    
  1986.                   <b>
    
  1987.                     <Text text="Loading..." />
    
  1988.                   </b>
    
  1989.                 }>
    
  1990.                 <ContextA.Provider value="A0.1.1">
    
  1991.                   <Throws />
    
  1992.                 </ContextA.Provider>
    
  1993.               </Suspense>
    
  1994.               <PrintB />
    
  1995.             </ContextA.Provider>
    
  1996.           </div>
    
  1997.           <PrintA />
    
  1998.         </div>,
    
  1999. 
    
  2000.         {
    
  2001.           onError(x) {
    
  2002.             loggedErrors.push(x);
    
  2003.           },
    
  2004.         },
    
  2005.       );
    
  2006.       pipe(writable);
    
  2007.     });
    
  2008.     expect(loggedErrors.length).toBe(1);
    
  2009.     expect(loggedErrors[0].message).toEqual('A0.1.1');
    
  2010.     expect(getVisibleChildren(container)).toEqual(
    
  2011.       <div>
    
  2012.         A0
    
  2013.         <div>
    
  2014.           <b>Loading...</b>B0
    
  2015.         </div>
    
  2016.         A0
    
  2017.       </div>,
    
  2018.     );
    
  2019.   });
    
  2020. 
    
  2021.   it('client renders a boundary if it errors before finishing the fallback', async () => {
    
  2022.     function App({isClient}) {
    
  2023.       return (
    
  2024.         <Suspense fallback="Loading root...">
    
  2025.           <div>
    
  2026.             <Suspense fallback={<AsyncText text="Loading..." />}>
    
  2027.               <h1>
    
  2028.                 {isClient ? <Text text="Hello" /> : <AsyncText text="Hello" />}
    
  2029.               </h1>
    
  2030.             </Suspense>
    
  2031.           </div>
    
  2032.         </Suspense>
    
  2033.       );
    
  2034.     }
    
  2035. 
    
  2036.     const theError = new Error('Test');
    
  2037.     const loggedErrors = [];
    
  2038.     function onError(x) {
    
  2039.       loggedErrors.push(x);
    
  2040.       return `hash of (${x.message})`;
    
  2041.     }
    
  2042.     const expectedDigest = onError(theError);
    
  2043.     loggedErrors.length = 0;
    
  2044. 
    
  2045.     let controls;
    
  2046.     await act(() => {
    
  2047.       controls = renderToPipeableStream(
    
  2048.         <App isClient={false} />,
    
  2049. 
    
  2050.         {
    
  2051.           onError,
    
  2052.         },
    
  2053.       );
    
  2054.       controls.pipe(writable);
    
  2055.     });
    
  2056. 
    
  2057.     // We're still showing a fallback.
    
  2058. 
    
  2059.     const errors = [];
    
  2060.     // Attempt to hydrate the content.
    
  2061.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  2062.       onRecoverableError(error, errorInfo) {
    
  2063.         errors.push({error, errorInfo});
    
  2064.       },
    
  2065.     });
    
  2066.     await waitForAll([]);
    
  2067. 
    
  2068.     // We're still loading because we're waiting for the server to stream more content.
    
  2069.     expect(getVisibleChildren(container)).toEqual('Loading root...');
    
  2070. 
    
  2071.     expect(loggedErrors).toEqual([]);
    
  2072. 
    
  2073.     // Error the content, but we don't have a fallback yet.
    
  2074.     await act(() => {
    
  2075.       rejectText('Hello', theError);
    
  2076.     });
    
  2077. 
    
  2078.     expect(loggedErrors).toEqual([theError]);
    
  2079. 
    
  2080.     // We still can't render it on the client because we haven't unblocked the parent.
    
  2081.     await waitForAll([]);
    
  2082.     expect(getVisibleChildren(container)).toEqual('Loading root...');
    
  2083. 
    
  2084.     // Unblock the loading state
    
  2085.     await act(() => {
    
  2086.       resolveText('Loading...');
    
  2087.     });
    
  2088. 
    
  2089.     // Now we're able to show the inner boundary.
    
  2090.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  2091. 
    
  2092.     // That will let us client render it instead.
    
  2093.     await waitForAll([]);
    
  2094.     expectErrors(
    
  2095.       errors,
    
  2096.       [
    
  2097.         [
    
  2098.           theError.message,
    
  2099.           expectedDigest,
    
  2100.           componentStack([
    
  2101.             'AsyncText',
    
  2102.             'h1',
    
  2103.             'Suspense',
    
  2104.             'div',
    
  2105.             'Suspense',
    
  2106.             'App',
    
  2107.           ]),
    
  2108.         ],
    
  2109.       ],
    
  2110.       [
    
  2111.         [
    
  2112.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  2113.           expectedDigest,
    
  2114.         ],
    
  2115.       ],
    
  2116.     );
    
  2117. 
    
  2118.     // The client rendered HTML is now in place.
    
  2119.     expect(getVisibleChildren(container)).toEqual(
    
  2120.       <div>
    
  2121.         <h1>Hello</h1>
    
  2122.       </div>,
    
  2123.     );
    
  2124. 
    
  2125.     expect(loggedErrors).toEqual([theError]);
    
  2126.   });
    
  2127. 
    
  2128.   it('should be able to abort the fallback if the main content finishes first', async () => {
    
  2129.     await act(() => {
    
  2130.       const {pipe} = renderToPipeableStream(
    
  2131.         <Suspense fallback={<Text text="Loading Outer" />}>
    
  2132.           <div>
    
  2133.             <Suspense
    
  2134.               fallback={
    
  2135.                 <div>
    
  2136.                   <AsyncText text="Loading" />
    
  2137.                   Inner
    
  2138.                 </div>
    
  2139.               }>
    
  2140.               <AsyncText text="Hello" />
    
  2141.             </Suspense>
    
  2142.           </div>
    
  2143.         </Suspense>,
    
  2144.       );
    
  2145.       pipe(writable);
    
  2146.     });
    
  2147.     expect(getVisibleChildren(container)).toEqual('Loading Outer');
    
  2148.     // We should have received a partial segment containing the a partial of the fallback.
    
  2149.     expect(container.innerHTML).toContain('Inner');
    
  2150.     await act(() => {
    
  2151.       resolveText('Hello');
    
  2152.     });
    
  2153.     // We should've been able to display the content without waiting for the rest of the fallback.
    
  2154.     expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  2155.   });
    
  2156. 
    
  2157.   // @gate enableSuspenseAvoidThisFallbackFizz
    
  2158.   it('should respect unstable_avoidThisFallback', async () => {
    
  2159.     const resolved = {
    
  2160.       0: false,
    
  2161.       1: false,
    
  2162.     };
    
  2163.     const promiseRes = {};
    
  2164.     const promises = {
    
  2165.       0: new Promise(res => {
    
  2166.         promiseRes[0] = () => {
    
  2167.           resolved[0] = true;
    
  2168.           res();
    
  2169.         };
    
  2170.       }),
    
  2171.       1: new Promise(res => {
    
  2172.         promiseRes[1] = () => {
    
  2173.           resolved[1] = true;
    
  2174.           res();
    
  2175.         };
    
  2176.       }),
    
  2177.     };
    
  2178. 
    
  2179.     const InnerComponent = ({isClient, depth}) => {
    
  2180.       if (isClient) {
    
  2181.         // Resuspend after re-rendering on client to check that fallback shows on client
    
  2182.         throw new Promise(() => {});
    
  2183.       }
    
  2184.       if (!resolved[depth]) {
    
  2185.         throw promises[depth];
    
  2186.       }
    
  2187.       return (
    
  2188.         <div>
    
  2189.           <Text text={`resolved ${depth}`} />
    
  2190.         </div>
    
  2191.       );
    
  2192.     };
    
  2193. 
    
  2194.     function App({isClient}) {
    
  2195.       return (
    
  2196.         <div>
    
  2197.           <Text text="Non Suspense Content" />
    
  2198.           <Suspense
    
  2199.             fallback={
    
  2200.               <span>
    
  2201.                 <Text text="Avoided Fallback" />
    
  2202.               </span>
    
  2203.             }
    
  2204.             unstable_avoidThisFallback={true}>
    
  2205.             <InnerComponent isClient={isClient} depth={0} />
    
  2206.             <div>
    
  2207.               <Suspense fallback={<Text text="Fallback" />}>
    
  2208.                 <Suspense
    
  2209.                   fallback={
    
  2210.                     <span>
    
  2211.                       <Text text="Avoided Fallback2" />
    
  2212.                     </span>
    
  2213.                   }
    
  2214.                   unstable_avoidThisFallback={true}>
    
  2215.                   <InnerComponent isClient={isClient} depth={1} />
    
  2216.                 </Suspense>
    
  2217.               </Suspense>
    
  2218.             </div>
    
  2219.           </Suspense>
    
  2220.         </div>
    
  2221.       );
    
  2222.     }
    
  2223. 
    
  2224.     await jest.runAllTimers();
    
  2225. 
    
  2226.     await act(() => {
    
  2227.       const {pipe} = renderToPipeableStream(<App isClient={false} />);
    
  2228.       pipe(writable);
    
  2229.     });
    
  2230. 
    
  2231.     // Nothing is output since root has a suspense with avoidedThisFallback that hasn't resolved
    
  2232.     expect(getVisibleChildren(container)).toEqual(undefined);
    
  2233.     expect(container.innerHTML).not.toContain('Avoided Fallback');
    
  2234. 
    
  2235.     // resolve first suspense component with avoidThisFallback
    
  2236.     await act(() => {
    
  2237.       promiseRes[0]();
    
  2238.     });
    
  2239. 
    
  2240.     expect(getVisibleChildren(container)).toEqual(
    
  2241.       <div>
    
  2242.         Non Suspense Content
    
  2243.         <div>resolved 0</div>
    
  2244.         <div>Fallback</div>
    
  2245.       </div>,
    
  2246.     );
    
  2247. 
    
  2248.     expect(container.innerHTML).not.toContain('Avoided Fallback2');
    
  2249. 
    
  2250.     await act(() => {
    
  2251.       promiseRes[1]();
    
  2252.     });
    
  2253. 
    
  2254.     expect(getVisibleChildren(container)).toEqual(
    
  2255.       <div>
    
  2256.         Non Suspense Content
    
  2257.         <div>resolved 0</div>
    
  2258.         <div>
    
  2259.           <div>resolved 1</div>
    
  2260.         </div>
    
  2261.       </div>,
    
  2262.     );
    
  2263. 
    
  2264.     let root;
    
  2265.     await act(async () => {
    
  2266.       root = ReactDOMClient.hydrateRoot(container, <App isClient={false} />);
    
  2267.       await waitForAll([]);
    
  2268.       await jest.runAllTimers();
    
  2269.     });
    
  2270. 
    
  2271.     // No change after hydration
    
  2272.     expect(getVisibleChildren(container)).toEqual(
    
  2273.       <div>
    
  2274.         Non Suspense Content
    
  2275.         <div>resolved 0</div>
    
  2276.         <div>
    
  2277.           <div>resolved 1</div>
    
  2278.         </div>
    
  2279.       </div>,
    
  2280.     );
    
  2281. 
    
  2282.     await act(async () => {
    
  2283.       // Trigger update by changing isClient to true
    
  2284.       root.render(<App isClient={true} />);
    
  2285.       await waitForAll([]);
    
  2286.       await jest.runAllTimers();
    
  2287.     });
    
  2288. 
    
  2289.     // Now that we've resuspended at the root we show the root fallback
    
  2290.     expect(getVisibleChildren(container)).toEqual(
    
  2291.       <div>
    
  2292.         Non Suspense Content
    
  2293.         <div style="display: none;">resolved 0</div>
    
  2294.         <div style="display: none;">
    
  2295.           <div>resolved 1</div>
    
  2296.         </div>
    
  2297.         <span>Avoided Fallback</span>
    
  2298.       </div>,
    
  2299.     );
    
  2300.   });
    
  2301. 
    
  2302.   it('calls getServerSnapshot instead of getSnapshot', async () => {
    
  2303.     const ref = React.createRef();
    
  2304. 
    
  2305.     function getServerSnapshot() {
    
  2306.       return 'server';
    
  2307.     }
    
  2308. 
    
  2309.     function getClientSnapshot() {
    
  2310.       return 'client';
    
  2311.     }
    
  2312. 
    
  2313.     function subscribe() {
    
  2314.       return () => {};
    
  2315.     }
    
  2316. 
    
  2317.     function Child({text}) {
    
  2318.       Scheduler.log(text);
    
  2319.       return text;
    
  2320.     }
    
  2321. 
    
  2322.     function App() {
    
  2323.       const value = useSyncExternalStore(
    
  2324.         subscribe,
    
  2325.         getClientSnapshot,
    
  2326.         getServerSnapshot,
    
  2327.       );
    
  2328.       return (
    
  2329.         <div ref={ref}>
    
  2330.           <Child text={value} />
    
  2331.         </div>
    
  2332.       );
    
  2333.     }
    
  2334. 
    
  2335.     const loggedErrors = [];
    
  2336.     await act(() => {
    
  2337.       const {pipe} = renderToPipeableStream(
    
  2338.         <Suspense fallback="Loading...">
    
  2339.           <App />
    
  2340.         </Suspense>,
    
  2341.         {
    
  2342.           onError(x) {
    
  2343.             loggedErrors.push(x);
    
  2344.           },
    
  2345.         },
    
  2346.       );
    
  2347.       pipe(writable);
    
  2348.     });
    
  2349.     assertLog(['server']);
    
  2350. 
    
  2351.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  2352.       onRecoverableError(error) {
    
  2353.         Scheduler.log('Log recoverable error: ' + error.message);
    
  2354.       },
    
  2355.     });
    
  2356. 
    
  2357.     await expect(async () => {
    
  2358.       // The first paint switches to client rendering due to mismatch
    
  2359.       await waitForPaint([
    
  2360.         'client',
    
  2361.         'Log recoverable error: Hydration failed because the initial ' +
    
  2362.           'UI does not match what was rendered on the server.',
    
  2363.         'Log recoverable error: There was an error while hydrating. ' +
    
  2364.           'Because the error happened outside of a Suspense boundary, the ' +
    
  2365.           'entire root will switch to client rendering.',
    
  2366.       ]);
    
  2367.     }).toErrorDev(
    
  2368.       [
    
  2369.         'Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  2370.         'Warning: Expected server HTML to contain a matching <div> in <div>.\n' +
    
  2371.           '    in div (at **)\n' +
    
  2372.           '    in App (at **)',
    
  2373.       ],
    
  2374.       {withoutStack: 1},
    
  2375.     );
    
  2376.     expect(getVisibleChildren(container)).toEqual(<div>client</div>);
    
  2377.   });
    
  2378. 
    
  2379.   // The selector implementation uses the lazy ref initialization pattern
    
  2380. 
    
  2381.   it('calls getServerSnapshot instead of getSnapshot (with selector and isEqual)', async () => {
    
  2382.     // Same as previous test, but with a selector that returns a complex object
    
  2383.     // that is memoized with a custom `isEqual` function.
    
  2384.     const ref = React.createRef();
    
  2385.     function getServerSnapshot() {
    
  2386.       return {env: 'server', other: 'unrelated'};
    
  2387.     }
    
  2388.     function getClientSnapshot() {
    
  2389.       return {env: 'client', other: 'unrelated'};
    
  2390.     }
    
  2391.     function selector({env}) {
    
  2392.       return {env};
    
  2393.     }
    
  2394.     function isEqual(a, b) {
    
  2395.       return a.env === b.env;
    
  2396.     }
    
  2397.     function subscribe() {
    
  2398.       return () => {};
    
  2399.     }
    
  2400.     function Child({text}) {
    
  2401.       Scheduler.log(text);
    
  2402.       return text;
    
  2403.     }
    
  2404.     function App() {
    
  2405.       const {env} = useSyncExternalStoreWithSelector(
    
  2406.         subscribe,
    
  2407.         getClientSnapshot,
    
  2408.         getServerSnapshot,
    
  2409.         selector,
    
  2410.         isEqual,
    
  2411.       );
    
  2412.       return (
    
  2413.         <div ref={ref}>
    
  2414.           <Child text={env} />
    
  2415.         </div>
    
  2416.       );
    
  2417.     }
    
  2418.     const loggedErrors = [];
    
  2419.     await act(() => {
    
  2420.       const {pipe} = renderToPipeableStream(
    
  2421.         <Suspense fallback="Loading...">
    
  2422.           <App />
    
  2423.         </Suspense>,
    
  2424.         {
    
  2425.           onError(x) {
    
  2426.             loggedErrors.push(x);
    
  2427.           },
    
  2428.         },
    
  2429.       );
    
  2430.       pipe(writable);
    
  2431.     });
    
  2432.     assertLog(['server']);
    
  2433. 
    
  2434.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  2435.       onRecoverableError(error) {
    
  2436.         Scheduler.log('Log recoverable error: ' + error.message);
    
  2437.       },
    
  2438.     });
    
  2439. 
    
  2440.     // The first paint uses the client due to mismatch forcing client render
    
  2441.     await expect(async () => {
    
  2442.       // The first paint switches to client rendering due to mismatch
    
  2443.       await waitForPaint([
    
  2444.         'client',
    
  2445.         'Log recoverable error: Hydration failed because the initial ' +
    
  2446.           'UI does not match what was rendered on the server.',
    
  2447.         'Log recoverable error: There was an error while hydrating. ' +
    
  2448.           'Because the error happened outside of a Suspense boundary, the ' +
    
  2449.           'entire root will switch to client rendering.',
    
  2450.       ]);
    
  2451.     }).toErrorDev(
    
  2452.       [
    
  2453.         'Warning: An error occurred during hydration. The server HTML was replaced with client content',
    
  2454.         'Warning: Expected server HTML to contain a matching <div> in <div>.\n' +
    
  2455.           '    in div (at **)\n' +
    
  2456.           '    in App (at **)',
    
  2457.       ],
    
  2458.       {withoutStack: 1},
    
  2459.     );
    
  2460.     expect(getVisibleChildren(container)).toEqual(<div>client</div>);
    
  2461.   });
    
  2462. 
    
  2463.   it(
    
  2464.     'errors during hydration in the shell force a client render at the ' +
    
  2465.       'root, and during the client render it recovers',
    
  2466.     async () => {
    
  2467.       let isClient = false;
    
  2468. 
    
  2469.       function subscribe() {
    
  2470.         return () => {};
    
  2471.       }
    
  2472.       function getClientSnapshot() {
    
  2473.         return 'Yay!';
    
  2474.       }
    
  2475. 
    
  2476.       // At the time of writing, the only API that exposes whether it's currently
    
  2477.       // hydrating is the `getServerSnapshot` API, so I'm using that here to
    
  2478.       // simulate an error during hydration.
    
  2479.       function getServerSnapshot() {
    
  2480.         if (isClient) {
    
  2481.           throw new Error('Hydration error');
    
  2482.         }
    
  2483.         return 'Yay!';
    
  2484.       }
    
  2485. 
    
  2486.       function Child() {
    
  2487.         const value = useSyncExternalStore(
    
  2488.           subscribe,
    
  2489.           getClientSnapshot,
    
  2490.           getServerSnapshot,
    
  2491.         );
    
  2492.         Scheduler.log(value);
    
  2493.         return value;
    
  2494.       }
    
  2495. 
    
  2496.       const spanRef = React.createRef();
    
  2497. 
    
  2498.       function App() {
    
  2499.         return (
    
  2500.           <span ref={spanRef}>
    
  2501.             <Child />
    
  2502.           </span>
    
  2503.         );
    
  2504.       }
    
  2505. 
    
  2506.       await act(() => {
    
  2507.         const {pipe} = renderToPipeableStream(<App />);
    
  2508.         pipe(writable);
    
  2509.       });
    
  2510.       assertLog(['Yay!']);
    
  2511. 
    
  2512.       const span = container.getElementsByTagName('span')[0];
    
  2513. 
    
  2514.       // Hydrate the tree. Child will throw during hydration, but not when it
    
  2515.       // falls back to client rendering.
    
  2516.       isClient = true;
    
  2517.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  2518.         onRecoverableError(error) {
    
  2519.           Scheduler.log(error.message);
    
  2520.         },
    
  2521.       });
    
  2522. 
    
  2523.       // An error logged but instead of surfacing it to the UI, we switched
    
  2524.       // to client rendering.
    
  2525.       await expect(async () => {
    
  2526.         await waitForAll([
    
  2527.           'Yay!',
    
  2528.           'Hydration error',
    
  2529.           'There was an error while hydrating. Because the error happened ' +
    
  2530.             'outside of a Suspense boundary, the entire root will switch ' +
    
  2531.             'to client rendering.',
    
  2532.         ]);
    
  2533.       }).toErrorDev(
    
  2534.         'An error occurred during hydration. The server HTML was replaced',
    
  2535.         {withoutStack: true},
    
  2536.       );
    
  2537.       expect(getVisibleChildren(container)).toEqual(<span>Yay!</span>);
    
  2538. 
    
  2539.       // The node that's inside the boundary that errored during hydration was
    
  2540.       // not hydrated.
    
  2541.       expect(spanRef.current).not.toBe(span);
    
  2542.     },
    
  2543.   );
    
  2544. 
    
  2545.   it('can hydrate uSES in StrictMode with different client and server snapshot (sync)', async () => {
    
  2546.     function subscribe() {
    
  2547.       return () => {};
    
  2548.     }
    
  2549.     function getClientSnapshot() {
    
  2550.       return 'Yay!';
    
  2551.     }
    
  2552.     function getServerSnapshot() {
    
  2553.       return 'Nay!';
    
  2554.     }
    
  2555. 
    
  2556.     function App() {
    
  2557.       const value = useSyncExternalStore(
    
  2558.         subscribe,
    
  2559.         getClientSnapshot,
    
  2560.         getServerSnapshot,
    
  2561.       );
    
  2562.       Scheduler.log(value);
    
  2563. 
    
  2564.       return value;
    
  2565.     }
    
  2566. 
    
  2567.     const element = (
    
  2568.       <React.StrictMode>
    
  2569.         <App />
    
  2570.       </React.StrictMode>
    
  2571.     );
    
  2572. 
    
  2573.     await act(async () => {
    
  2574.       const {pipe} = renderToPipeableStream(element);
    
  2575.       pipe(writable);
    
  2576.     });
    
  2577. 
    
  2578.     assertLog(['Nay!']);
    
  2579.     expect(getVisibleChildren(container)).toEqual('Nay!');
    
  2580. 
    
  2581.     await clientAct(() => {
    
  2582.       ReactDOM.flushSync(() => {
    
  2583.         ReactDOMClient.hydrateRoot(container, element);
    
  2584.       });
    
  2585.     });
    
  2586. 
    
  2587.     expect(getVisibleChildren(container)).toEqual('Yay!');
    
  2588.     assertLog(['Nay!', 'Yay!']);
    
  2589.   });
    
  2590. 
    
  2591.   it('can hydrate uSES in StrictMode with different client and server snapshot (concurrent)', async () => {
    
  2592.     function subscribe() {
    
  2593.       return () => {};
    
  2594.     }
    
  2595.     function getClientSnapshot() {
    
  2596.       return 'Yay!';
    
  2597.     }
    
  2598.     function getServerSnapshot() {
    
  2599.       return 'Nay!';
    
  2600.     }
    
  2601. 
    
  2602.     function App() {
    
  2603.       const value = useSyncExternalStore(
    
  2604.         subscribe,
    
  2605.         getClientSnapshot,
    
  2606.         getServerSnapshot,
    
  2607.       );
    
  2608.       Scheduler.log(value);
    
  2609. 
    
  2610.       return value;
    
  2611.     }
    
  2612. 
    
  2613.     const element = (
    
  2614.       <React.StrictMode>
    
  2615.         <App />
    
  2616.       </React.StrictMode>
    
  2617.     );
    
  2618. 
    
  2619.     await act(async () => {
    
  2620.       const {pipe} = renderToPipeableStream(element);
    
  2621.       pipe(writable);
    
  2622.     });
    
  2623. 
    
  2624.     assertLog(['Nay!']);
    
  2625.     expect(getVisibleChildren(container)).toEqual('Nay!');
    
  2626. 
    
  2627.     await clientAct(() => {
    
  2628.       React.startTransition(() => {
    
  2629.         ReactDOMClient.hydrateRoot(container, element);
    
  2630.       });
    
  2631.     });
    
  2632. 
    
  2633.     expect(getVisibleChildren(container)).toEqual('Yay!');
    
  2634.     assertLog(['Nay!', 'Yay!']);
    
  2635.   });
    
  2636. 
    
  2637.   it(
    
  2638.     'errors during hydration force a client render at the nearest Suspense ' +
    
  2639.       'boundary, and during the client render it recovers',
    
  2640.     async () => {
    
  2641.       let isClient = false;
    
  2642. 
    
  2643.       function subscribe() {
    
  2644.         return () => {};
    
  2645.       }
    
  2646.       function getClientSnapshot() {
    
  2647.         return 'Yay!';
    
  2648.       }
    
  2649. 
    
  2650.       // At the time of writing, the only API that exposes whether it's currently
    
  2651.       // hydrating is the `getServerSnapshot` API, so I'm using that here to
    
  2652.       // simulate an error during hydration.
    
  2653.       function getServerSnapshot() {
    
  2654.         if (isClient) {
    
  2655.           throw new Error('Hydration error');
    
  2656.         }
    
  2657.         return 'Yay!';
    
  2658.       }
    
  2659. 
    
  2660.       function Child() {
    
  2661.         const value = useSyncExternalStore(
    
  2662.           subscribe,
    
  2663.           getClientSnapshot,
    
  2664.           getServerSnapshot,
    
  2665.         );
    
  2666.         Scheduler.log(value);
    
  2667.         return value;
    
  2668.       }
    
  2669. 
    
  2670.       const span1Ref = React.createRef();
    
  2671.       const span2Ref = React.createRef();
    
  2672.       const span3Ref = React.createRef();
    
  2673. 
    
  2674.       function App() {
    
  2675.         return (
    
  2676.           <div>
    
  2677.             <span ref={span1Ref} />
    
  2678.             <Suspense fallback="Loading...">
    
  2679.               <span ref={span2Ref}>
    
  2680.                 <Child />
    
  2681.               </span>
    
  2682.             </Suspense>
    
  2683.             <span ref={span3Ref} />
    
  2684.           </div>
    
  2685.         );
    
  2686.       }
    
  2687. 
    
  2688.       await act(() => {
    
  2689.         const {pipe} = renderToPipeableStream(<App />);
    
  2690.         pipe(writable);
    
  2691.       });
    
  2692.       assertLog(['Yay!']);
    
  2693. 
    
  2694.       const [span1, span2, span3] = container.getElementsByTagName('span');
    
  2695. 
    
  2696.       // Hydrate the tree. Child will throw during hydration, but not when it
    
  2697.       // falls back to client rendering.
    
  2698.       isClient = true;
    
  2699.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  2700.         onRecoverableError(error) {
    
  2701.           Scheduler.log(error.message);
    
  2702.         },
    
  2703.       });
    
  2704. 
    
  2705.       // An error logged but instead of surfacing it to the UI, we switched
    
  2706.       // to client rendering.
    
  2707.       await waitForAll([
    
  2708.         'Yay!',
    
  2709.         'Hydration error',
    
  2710.         'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  2711.       ]);
    
  2712.       expect(getVisibleChildren(container)).toEqual(
    
  2713.         <div>
    
  2714.           <span />
    
  2715.           <span>Yay!</span>
    
  2716.           <span />
    
  2717.         </div>,
    
  2718.       );
    
  2719. 
    
  2720.       // The node that's inside the boundary that errored during hydration was
    
  2721.       // not hydrated.
    
  2722.       expect(span2Ref.current).not.toBe(span2);
    
  2723. 
    
  2724.       // But the nodes outside the boundary were.
    
  2725.       expect(span1Ref.current).toBe(span1);
    
  2726.       expect(span3Ref.current).toBe(span3);
    
  2727.     },
    
  2728.   );
    
  2729. 
    
  2730.   it(
    
  2731.     'errors during hydration force a client render at the nearest Suspense ' +
    
  2732.       'boundary, and during the client render it fails again',
    
  2733.     async () => {
    
  2734.       // Similar to previous test, but the client render errors, too. We should
    
  2735.       // be able to capture it with an error boundary.
    
  2736. 
    
  2737.       let isClient = false;
    
  2738. 
    
  2739.       class ErrorBoundary extends React.Component {
    
  2740.         state = {error: null};
    
  2741.         static getDerivedStateFromError(error) {
    
  2742.           return {error};
    
  2743.         }
    
  2744.         render() {
    
  2745.           if (this.state.error !== null) {
    
  2746.             return this.state.error.message;
    
  2747.           }
    
  2748.           return this.props.children;
    
  2749.         }
    
  2750.       }
    
  2751. 
    
  2752.       function Child() {
    
  2753.         if (isClient) {
    
  2754.           throw new Error('Oops!');
    
  2755.         }
    
  2756.         Scheduler.log('Yay!');
    
  2757.         return 'Yay!';
    
  2758.       }
    
  2759. 
    
  2760.       const span1Ref = React.createRef();
    
  2761.       const span2Ref = React.createRef();
    
  2762.       const span3Ref = React.createRef();
    
  2763. 
    
  2764.       function App() {
    
  2765.         return (
    
  2766.           <ErrorBoundary>
    
  2767.             <span ref={span1Ref} />
    
  2768.             <Suspense fallback="Loading...">
    
  2769.               <span ref={span2Ref}>
    
  2770.                 <Child />
    
  2771.               </span>
    
  2772.             </Suspense>
    
  2773.             <span ref={span3Ref} />
    
  2774.           </ErrorBoundary>
    
  2775.         );
    
  2776.       }
    
  2777. 
    
  2778.       await act(() => {
    
  2779.         const {pipe} = renderToPipeableStream(<App />);
    
  2780.         pipe(writable);
    
  2781.       });
    
  2782.       assertLog(['Yay!']);
    
  2783. 
    
  2784.       // Hydrate the tree. Child will throw during render.
    
  2785.       isClient = true;
    
  2786.       const errors = [];
    
  2787.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  2788.         onRecoverableError(error) {
    
  2789.           errors.push(error.message);
    
  2790.         },
    
  2791.       });
    
  2792. 
    
  2793.       // Because we failed to recover from the error, onRecoverableError
    
  2794.       // shouldn't be called.
    
  2795.       await waitForAll([]);
    
  2796.       expect(getVisibleChildren(container)).toEqual('Oops!');
    
  2797. 
    
  2798.       expectErrors(errors, [], []);
    
  2799.     },
    
  2800.   );
    
  2801. 
    
  2802.   // Disabled because of a WWW late mutations regression.
    
  2803.   // We may want to re-enable this if we figure out why.
    
  2804. 
    
  2805.   // @gate FIXME
    
  2806.   it('does not recreate the fallback if server errors and hydration suspends', async () => {
    
  2807.     let isClient = false;
    
  2808. 
    
  2809.     function Child() {
    
  2810.       if (isClient) {
    
  2811.         readText('Yay!');
    
  2812.       } else {
    
  2813.         throw Error('Oops.');
    
  2814.       }
    
  2815.       Scheduler.log('Yay!');
    
  2816.       return 'Yay!';
    
  2817.     }
    
  2818. 
    
  2819.     const fallbackRef = React.createRef();
    
  2820.     function App() {
    
  2821.       return (
    
  2822.         <div>
    
  2823.           <Suspense fallback={<p ref={fallbackRef}>Loading...</p>}>
    
  2824.             <span>
    
  2825.               <Child />
    
  2826.             </span>
    
  2827.           </Suspense>
    
  2828.         </div>
    
  2829.       );
    
  2830.     }
    
  2831.     await act(() => {
    
  2832.       const {pipe} = renderToPipeableStream(<App />, {
    
  2833.         onError(error) {
    
  2834.           Scheduler.log('[s!] ' + error.message);
    
  2835.         },
    
  2836.       });
    
  2837.       pipe(writable);
    
  2838.     });
    
  2839.     assertLog(['[s!] Oops.']);
    
  2840. 
    
  2841.     // The server could not complete this boundary, so we'll retry on the client.
    
  2842.     const serverFallback = container.getElementsByTagName('p')[0];
    
  2843.     expect(serverFallback.innerHTML).toBe('Loading...');
    
  2844. 
    
  2845.     // Hydrate the tree. This will suspend.
    
  2846.     isClient = true;
    
  2847.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  2848.       onRecoverableError(error) {
    
  2849.         Scheduler.log('[c!] ' + error.message);
    
  2850.       },
    
  2851.     });
    
  2852.     // This should not report any errors yet.
    
  2853.     await waitForAll([]);
    
  2854.     expect(getVisibleChildren(container)).toEqual(
    
  2855.       <div>
    
  2856.         <p>Loading...</p>
    
  2857.       </div>,
    
  2858.     );
    
  2859. 
    
  2860.     // Normally, hydrating after server error would force a clean client render.
    
  2861.     // However, it suspended so at best we'd only get the same fallback anyway.
    
  2862.     // We don't want to recreate the same fallback in the DOM again because
    
  2863.     // that's extra work and would restart animations etc. Check we don't do that.
    
  2864.     const clientFallback = container.getElementsByTagName('p')[0];
    
  2865.     expect(serverFallback).toBe(clientFallback);
    
  2866. 
    
  2867.     // When we're able to fully hydrate, we expect a clean client render.
    
  2868.     await act(() => {
    
  2869.       resolveText('Yay!');
    
  2870.     });
    
  2871.     await waitForAll([
    
  2872.       'Yay!',
    
  2873.       '[c!] The server could not finish this Suspense boundary, ' +
    
  2874.         'likely due to an error during server rendering. ' +
    
  2875.         'Switched to client rendering.',
    
  2876.     ]);
    
  2877.     expect(getVisibleChildren(container)).toEqual(
    
  2878.       <div>
    
  2879.         <span>Yay!</span>
    
  2880.       </div>,
    
  2881.     );
    
  2882.   });
    
  2883. 
    
  2884.   // Disabled because of a WWW late mutations regression.
    
  2885.   // We may want to re-enable this if we figure out why.
    
  2886. 
    
  2887.   // @gate FIXME
    
  2888.   it(
    
  2889.     'does not recreate the fallback if server errors and hydration suspends ' +
    
  2890.       'and root receives a transition',
    
  2891.     async () => {
    
  2892.       let isClient = false;
    
  2893. 
    
  2894.       function Child({color}) {
    
  2895.         if (isClient) {
    
  2896.           readText('Yay!');
    
  2897.         } else {
    
  2898.           throw Error('Oops.');
    
  2899.         }
    
  2900.         Scheduler.log('Yay! (' + color + ')');
    
  2901.         return 'Yay! (' + color + ')';
    
  2902.       }
    
  2903. 
    
  2904.       const fallbackRef = React.createRef();
    
  2905.       function App({color}) {
    
  2906.         return (
    
  2907.           <div>
    
  2908.             <Suspense fallback={<p ref={fallbackRef}>Loading...</p>}>
    
  2909.               <span>
    
  2910.                 <Child color={color} />
    
  2911.               </span>
    
  2912.             </Suspense>
    
  2913.           </div>
    
  2914.         );
    
  2915.       }
    
  2916.       await act(() => {
    
  2917.         const {pipe} = renderToPipeableStream(<App color="red" />, {
    
  2918.           onError(error) {
    
  2919.             Scheduler.log('[s!] ' + error.message);
    
  2920.           },
    
  2921.         });
    
  2922.         pipe(writable);
    
  2923.       });
    
  2924.       assertLog(['[s!] Oops.']);
    
  2925. 
    
  2926.       // The server could not complete this boundary, so we'll retry on the client.
    
  2927.       const serverFallback = container.getElementsByTagName('p')[0];
    
  2928.       expect(serverFallback.innerHTML).toBe('Loading...');
    
  2929. 
    
  2930.       // Hydrate the tree. This will suspend.
    
  2931.       isClient = true;
    
  2932.       const root = ReactDOMClient.hydrateRoot(container, <App color="red" />, {
    
  2933.         onRecoverableError(error) {
    
  2934.           Scheduler.log('[c!] ' + error.message);
    
  2935.         },
    
  2936.       });
    
  2937.       // This should not report any errors yet.
    
  2938.       await waitForAll([]);
    
  2939.       expect(getVisibleChildren(container)).toEqual(
    
  2940.         <div>
    
  2941.           <p>Loading...</p>
    
  2942.         </div>,
    
  2943.       );
    
  2944. 
    
  2945.       // Normally, hydrating after server error would force a clean client render.
    
  2946.       // However, it suspended so at best we'd only get the same fallback anyway.
    
  2947.       // We don't want to recreate the same fallback in the DOM again because
    
  2948.       // that's extra work and would restart animations etc. Check we don't do that.
    
  2949.       const clientFallback = container.getElementsByTagName('p')[0];
    
  2950.       expect(serverFallback).toBe(clientFallback);
    
  2951. 
    
  2952.       // Transition updates shouldn't recreate the fallback either.
    
  2953.       React.startTransition(() => {
    
  2954.         root.render(<App color="blue" />);
    
  2955.       });
    
  2956.       await waitForAll([]);
    
  2957.       jest.runAllTimers();
    
  2958.       const clientFallback2 = container.getElementsByTagName('p')[0];
    
  2959.       expect(clientFallback2).toBe(serverFallback);
    
  2960. 
    
  2961.       // When we're able to fully hydrate, we expect a clean client render.
    
  2962.       await act(() => {
    
  2963.         resolveText('Yay!');
    
  2964.       });
    
  2965.       await waitForAll([
    
  2966.         'Yay! (red)',
    
  2967.         '[c!] The server could not finish this Suspense boundary, ' +
    
  2968.           'likely due to an error during server rendering. ' +
    
  2969.           'Switched to client rendering.',
    
  2970.         'Yay! (blue)',
    
  2971.       ]);
    
  2972.       expect(getVisibleChildren(container)).toEqual(
    
  2973.         <div>
    
  2974.           <span>Yay! (blue)</span>
    
  2975.         </div>,
    
  2976.       );
    
  2977.     },
    
  2978.   );
    
  2979. 
    
  2980.   // Disabled because of a WWW late mutations regression.
    
  2981.   // We may want to re-enable this if we figure out why.
    
  2982. 
    
  2983.   // @gate FIXME
    
  2984.   it(
    
  2985.     'recreates the fallback if server errors and hydration suspends but ' +
    
  2986.       'client receives new props',
    
  2987.     async () => {
    
  2988.       let isClient = false;
    
  2989. 
    
  2990.       function Child() {
    
  2991.         const value = 'Yay!';
    
  2992.         if (isClient) {
    
  2993.           readText(value);
    
  2994.         } else {
    
  2995.           throw Error('Oops.');
    
  2996.         }
    
  2997.         Scheduler.log(value);
    
  2998.         return value;
    
  2999.       }
    
  3000. 
    
  3001.       const fallbackRef = React.createRef();
    
  3002.       function App({fallbackText}) {
    
  3003.         return (
    
  3004.           <div>
    
  3005.             <Suspense fallback={<p ref={fallbackRef}>{fallbackText}</p>}>
    
  3006.               <span>
    
  3007.                 <Child />
    
  3008.               </span>
    
  3009.             </Suspense>
    
  3010.           </div>
    
  3011.         );
    
  3012.       }
    
  3013. 
    
  3014.       await act(() => {
    
  3015.         const {pipe} = renderToPipeableStream(
    
  3016.           <App fallbackText="Loading..." />,
    
  3017.           {
    
  3018.             onError(error) {
    
  3019.               Scheduler.log('[s!] ' + error.message);
    
  3020.             },
    
  3021.           },
    
  3022.         );
    
  3023.         pipe(writable);
    
  3024.       });
    
  3025.       assertLog(['[s!] Oops.']);
    
  3026. 
    
  3027.       const serverFallback = container.getElementsByTagName('p')[0];
    
  3028.       expect(serverFallback.innerHTML).toBe('Loading...');
    
  3029. 
    
  3030.       // Hydrate the tree. This will suspend.
    
  3031.       isClient = true;
    
  3032.       const root = ReactDOMClient.hydrateRoot(
    
  3033.         container,
    
  3034.         <App fallbackText="Loading..." />,
    
  3035.         {
    
  3036.           onRecoverableError(error) {
    
  3037.             Scheduler.log('[c!] ' + error.message);
    
  3038.           },
    
  3039.         },
    
  3040.       );
    
  3041.       // This should not report any errors yet.
    
  3042.       await waitForAll([]);
    
  3043.       expect(getVisibleChildren(container)).toEqual(
    
  3044.         <div>
    
  3045.           <p>Loading...</p>
    
  3046.         </div>,
    
  3047.       );
    
  3048. 
    
  3049.       // Normally, hydration after server error would force a clean client render.
    
  3050.       // However, that suspended so at best we'd only get a fallback anyway.
    
  3051.       // We don't want to replace a fallback with the same fallback because
    
  3052.       // that's extra work and would restart animations etc. Verify we don't do that.
    
  3053.       const clientFallback1 = container.getElementsByTagName('p')[0];
    
  3054.       expect(serverFallback).toBe(clientFallback1);
    
  3055. 
    
  3056.       // However, an update may have changed the fallback props. In that case we have to
    
  3057.       // actually force it to re-render on the client and throw away the server one.
    
  3058.       root.render(<App fallbackText="More loading..." />);
    
  3059.       await waitForAll([]);
    
  3060.       jest.runAllTimers();
    
  3061.       assertLog([
    
  3062.         '[c!] The server could not finish this Suspense boundary, ' +
    
  3063.           'likely due to an error during server rendering. ' +
    
  3064.           'Switched to client rendering.',
    
  3065.       ]);
    
  3066.       expect(getVisibleChildren(container)).toEqual(
    
  3067.         <div>
    
  3068.           <p>More loading...</p>
    
  3069.         </div>,
    
  3070.       );
    
  3071.       // This should be a clean render without reusing DOM.
    
  3072.       const clientFallback2 = container.getElementsByTagName('p')[0];
    
  3073.       expect(clientFallback2).not.toBe(clientFallback1);
    
  3074. 
    
  3075.       // Verify we can still do a clean content render after.
    
  3076.       await act(() => {
    
  3077.         resolveText('Yay!');
    
  3078.       });
    
  3079.       await waitForAll(['Yay!']);
    
  3080.       expect(getVisibleChildren(container)).toEqual(
    
  3081.         <div>
    
  3082.           <span>Yay!</span>
    
  3083.         </div>,
    
  3084.       );
    
  3085.     },
    
  3086.   );
    
  3087. 
    
  3088.   it(
    
  3089.     'errors during hydration force a client render at the nearest Suspense ' +
    
  3090.       'boundary, and during the client render it recovers, then a deeper ' +
    
  3091.       'child suspends',
    
  3092.     async () => {
    
  3093.       let isClient = false;
    
  3094. 
    
  3095.       function subscribe() {
    
  3096.         return () => {};
    
  3097.       }
    
  3098.       function getClientSnapshot() {
    
  3099.         return 'Yay!';
    
  3100.       }
    
  3101. 
    
  3102.       // At the time of writing, the only API that exposes whether it's currently
    
  3103.       // hydrating is the `getServerSnapshot` API, so I'm using that here to
    
  3104.       // simulate an error during hydration.
    
  3105.       function getServerSnapshot() {
    
  3106.         if (isClient) {
    
  3107.           throw new Error('Hydration error');
    
  3108.         }
    
  3109.         return 'Yay!';
    
  3110.       }
    
  3111. 
    
  3112.       function Child() {
    
  3113.         const value = useSyncExternalStore(
    
  3114.           subscribe,
    
  3115.           getClientSnapshot,
    
  3116.           getServerSnapshot,
    
  3117.         );
    
  3118.         if (isClient) {
    
  3119.           readText(value);
    
  3120.         }
    
  3121.         Scheduler.log(value);
    
  3122.         return value;
    
  3123.       }
    
  3124. 
    
  3125.       const span1Ref = React.createRef();
    
  3126.       const span2Ref = React.createRef();
    
  3127.       const span3Ref = React.createRef();
    
  3128. 
    
  3129.       function App() {
    
  3130.         return (
    
  3131.           <div>
    
  3132.             <span ref={span1Ref} />
    
  3133.             <Suspense fallback="Loading...">
    
  3134.               <span ref={span2Ref}>
    
  3135.                 <Child />
    
  3136.               </span>
    
  3137.             </Suspense>
    
  3138.             <span ref={span3Ref} />
    
  3139.           </div>
    
  3140.         );
    
  3141.       }
    
  3142. 
    
  3143.       await act(() => {
    
  3144.         const {pipe} = renderToPipeableStream(<App />);
    
  3145.         pipe(writable);
    
  3146.       });
    
  3147.       assertLog(['Yay!']);
    
  3148. 
    
  3149.       const [span1, span2, span3] = container.getElementsByTagName('span');
    
  3150. 
    
  3151.       // Hydrate the tree. Child will throw during hydration, but not when it
    
  3152.       // falls back to client rendering.
    
  3153.       isClient = true;
    
  3154.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  3155.         onRecoverableError(error) {
    
  3156.           Scheduler.log(error.message);
    
  3157.         },
    
  3158.       });
    
  3159. 
    
  3160.       // An error logged but instead of surfacing it to the UI, we switched
    
  3161.       // to client rendering.
    
  3162.       await waitForAll([
    
  3163.         'Hydration error',
    
  3164.         'There was an error while hydrating this Suspense boundary. Switched ' +
    
  3165.           'to client rendering.',
    
  3166.       ]);
    
  3167.       expect(getVisibleChildren(container)).toEqual(
    
  3168.         <div>
    
  3169.           <span />
    
  3170.           Loading...
    
  3171.           <span />
    
  3172.         </div>,
    
  3173.       );
    
  3174. 
    
  3175.       await clientAct(() => {
    
  3176.         resolveText('Yay!');
    
  3177.       });
    
  3178.       assertLog(['Yay!']);
    
  3179.       expect(getVisibleChildren(container)).toEqual(
    
  3180.         <div>
    
  3181.           <span />
    
  3182.           <span>Yay!</span>
    
  3183.           <span />
    
  3184.         </div>,
    
  3185.       );
    
  3186. 
    
  3187.       // The node that's inside the boundary that errored during hydration was
    
  3188.       // not hydrated.
    
  3189.       expect(span2Ref.current).not.toBe(span2);
    
  3190. 
    
  3191.       // But the nodes outside the boundary were.
    
  3192.       expect(span1Ref.current).toBe(span1);
    
  3193.       expect(span3Ref.current).toBe(span3);
    
  3194.     },
    
  3195.   );
    
  3196. 
    
  3197.   it('logs regular (non-hydration) errors when the UI recovers', async () => {
    
  3198.     let shouldThrow = true;
    
  3199. 
    
  3200.     function A() {
    
  3201.       if (shouldThrow) {
    
  3202.         Scheduler.log('Oops!');
    
  3203.         throw new Error('Oops!');
    
  3204.       }
    
  3205.       Scheduler.log('A');
    
  3206.       return 'A';
    
  3207.     }
    
  3208. 
    
  3209.     function B() {
    
  3210.       Scheduler.log('B');
    
  3211.       return 'B';
    
  3212.     }
    
  3213. 
    
  3214.     function App() {
    
  3215.       return (
    
  3216.         <>
    
  3217.           <A />
    
  3218.           <B />
    
  3219.         </>
    
  3220.       );
    
  3221.     }
    
  3222. 
    
  3223.     const root = ReactDOMClient.createRoot(container, {
    
  3224.       onRecoverableError(error) {
    
  3225.         Scheduler.log('Logged a recoverable error: ' + error.message);
    
  3226.       },
    
  3227.     });
    
  3228.     React.startTransition(() => {
    
  3229.       root.render(<App />);
    
  3230.     });
    
  3231. 
    
  3232.     // Partially render A, but yield before the render has finished
    
  3233.     await waitFor(['Oops!', 'Oops!']);
    
  3234. 
    
  3235.     // React will try rendering again synchronously. During the retry, A will
    
  3236.     // not throw. This simulates a concurrent data race that is fixed by
    
  3237.     // blocking the main thread.
    
  3238.     shouldThrow = false;
    
  3239.     await waitForAll([
    
  3240.       // Render again, synchronously
    
  3241.       'A',
    
  3242.       'B',
    
  3243. 
    
  3244.       // Log the error
    
  3245.       'Logged a recoverable error: Oops!',
    
  3246.     ]);
    
  3247. 
    
  3248.     // UI looks normal
    
  3249.     expect(container.textContent).toEqual('AB');
    
  3250.   });
    
  3251. 
    
  3252.   it('logs multiple hydration errors in the same render', async () => {
    
  3253.     let isClient = false;
    
  3254. 
    
  3255.     function subscribe() {
    
  3256.       return () => {};
    
  3257.     }
    
  3258.     function getClientSnapshot() {
    
  3259.       return 'Yay!';
    
  3260.     }
    
  3261.     function getServerSnapshot() {
    
  3262.       if (isClient) {
    
  3263.         throw new Error('Hydration error');
    
  3264.       }
    
  3265.       return 'Yay!';
    
  3266.     }
    
  3267. 
    
  3268.     function Child({label}) {
    
  3269.       // This will throw during client hydration. Only reason to use
    
  3270.       // useSyncExternalStore in this test is because getServerSnapshot has the
    
  3271.       // ability to observe whether we're hydrating.
    
  3272.       useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot);
    
  3273.       Scheduler.log(label);
    
  3274.       return label;
    
  3275.     }
    
  3276. 
    
  3277.     function App() {
    
  3278.       return (
    
  3279.         <>
    
  3280.           <Suspense fallback="Loading...">
    
  3281.             <Child label="A" />
    
  3282.           </Suspense>
    
  3283.           <Suspense fallback="Loading...">
    
  3284.             <Child label="B" />
    
  3285.           </Suspense>
    
  3286.         </>
    
  3287.       );
    
  3288.     }
    
  3289. 
    
  3290.     await act(() => {
    
  3291.       const {pipe} = renderToPipeableStream(<App />);
    
  3292.       pipe(writable);
    
  3293.     });
    
  3294.     assertLog(['A', 'B']);
    
  3295. 
    
  3296.     // Hydrate the tree. Child will throw during hydration, but not when it
    
  3297.     // falls back to client rendering.
    
  3298.     isClient = true;
    
  3299.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  3300.       onRecoverableError(error) {
    
  3301.         Scheduler.log('Logged recoverable error: ' + error.message);
    
  3302.       },
    
  3303.     });
    
  3304. 
    
  3305.     await waitForAll([
    
  3306.       'A',
    
  3307.       'B',
    
  3308. 
    
  3309.       'Logged recoverable error: Hydration error',
    
  3310.       'Logged recoverable error: There was an error while hydrating this ' +
    
  3311.         'Suspense boundary. Switched to client rendering.',
    
  3312. 
    
  3313.       'Logged recoverable error: Hydration error',
    
  3314.       'Logged recoverable error: There was an error while hydrating this ' +
    
  3315.         'Suspense boundary. Switched to client rendering.',
    
  3316.     ]);
    
  3317.   });
    
  3318. 
    
  3319.   // @gate enableServerContext
    
  3320.   it('supports ServerContext', async () => {
    
  3321.     let ServerContext;
    
  3322.     function inlineLazyServerContextInitialization() {
    
  3323.       if (!ServerContext) {
    
  3324.         expect(() => {
    
  3325.           ServerContext = React.createServerContext('ServerContext', 'default');
    
  3326.         }).toErrorDev(
    
  3327.           'Server Context is deprecated and will soon be removed. ' +
    
  3328.             'It was never documented and we have found it not to be useful ' +
    
  3329.             'enough to warrant the downside it imposes on all apps.',
    
  3330.         );
    
  3331.       }
    
  3332.       return ServerContext;
    
  3333.     }
    
  3334. 
    
  3335.     function Foo() {
    
  3336.       React.useState(); // component stack generation shouldn't reinit
    
  3337.       inlineLazyServerContextInitialization();
    
  3338.       return (
    
  3339.         <>
    
  3340.           <ServerContext.Provider value="hi this is server outer">
    
  3341.             <ServerContext.Provider value="hi this is server">
    
  3342.               <Bar />
    
  3343.             </ServerContext.Provider>
    
  3344.             <ServerContext.Provider value="hi this is server2">
    
  3345.               <Bar />
    
  3346.             </ServerContext.Provider>
    
  3347.             <Bar />
    
  3348.           </ServerContext.Provider>
    
  3349.           <ServerContext.Provider value="hi this is server outer2">
    
  3350.             <Bar />
    
  3351.           </ServerContext.Provider>
    
  3352.           <Bar />
    
  3353.         </>
    
  3354.       );
    
  3355.     }
    
  3356.     function Bar() {
    
  3357.       const context = React.useContext(inlineLazyServerContextInitialization());
    
  3358.       return <span>{context}</span>;
    
  3359.     }
    
  3360. 
    
  3361.     await act(() => {
    
  3362.       const {pipe} = renderToPipeableStream(<Foo />);
    
  3363.       pipe(writable);
    
  3364.     });
    
  3365. 
    
  3366.     expect(getVisibleChildren(container)).toEqual([
    
  3367.       <span>hi this is server</span>,
    
  3368.       <span>hi this is server2</span>,
    
  3369.       <span>hi this is server outer</span>,
    
  3370.       <span>hi this is server outer2</span>,
    
  3371.       <span>default</span>,
    
  3372.     ]);
    
  3373.   });
    
  3374. 
    
  3375.   it('Supports iterable', async () => {
    
  3376.     const Immutable = require('immutable');
    
  3377. 
    
  3378.     const mappedJSX = Immutable.fromJS([
    
  3379.       {name: 'a', value: 'a'},
    
  3380.       {name: 'b', value: 'b'},
    
  3381.     ]).map(item => <li key={item.get('value')}>{item.get('name')}</li>);
    
  3382. 
    
  3383.     await act(() => {
    
  3384.       const {pipe} = renderToPipeableStream(<ul>{mappedJSX}</ul>);
    
  3385.       pipe(writable);
    
  3386.     });
    
  3387.     expect(getVisibleChildren(container)).toEqual(
    
  3388.       <ul>
    
  3389.         <li>a</li>
    
  3390.         <li>b</li>
    
  3391.       </ul>,
    
  3392.     );
    
  3393.   });
    
  3394. 
    
  3395.   it('Supports custom abort reasons with a string', async () => {
    
  3396.     function App() {
    
  3397.       return (
    
  3398.         <div>
    
  3399.           <p>
    
  3400.             <Suspense fallback={'p'}>
    
  3401.               <AsyncText text={'hello'} />
    
  3402.             </Suspense>
    
  3403.           </p>
    
  3404.           <span>
    
  3405.             <Suspense fallback={'span'}>
    
  3406.               <AsyncText text={'world'} />
    
  3407.             </Suspense>
    
  3408.           </span>
    
  3409.         </div>
    
  3410.       );
    
  3411.     }
    
  3412. 
    
  3413.     let abort;
    
  3414.     const loggedErrors = [];
    
  3415.     await act(() => {
    
  3416.       const {pipe, abort: abortImpl} = renderToPipeableStream(<App />, {
    
  3417.         onError(error) {
    
  3418.           // In this test we contrive erroring with strings so we push the error whereas in most
    
  3419.           // other tests we contrive erroring with Errors and push the message.
    
  3420.           loggedErrors.push(error);
    
  3421.           return 'a digest';
    
  3422.         },
    
  3423.       });
    
  3424.       abort = abortImpl;
    
  3425.       pipe(writable);
    
  3426.     });
    
  3427. 
    
  3428.     expect(loggedErrors).toEqual([]);
    
  3429.     expect(getVisibleChildren(container)).toEqual(
    
  3430.       <div>
    
  3431.         <p>p</p>
    
  3432.         <span>span</span>
    
  3433.       </div>,
    
  3434.     );
    
  3435. 
    
  3436.     await act(() => {
    
  3437.       abort('foobar');
    
  3438.     });
    
  3439. 
    
  3440.     expect(loggedErrors).toEqual(['foobar', 'foobar']);
    
  3441. 
    
  3442.     const errors = [];
    
  3443.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  3444.       onRecoverableError(error, errorInfo) {
    
  3445.         errors.push({error, errorInfo});
    
  3446.       },
    
  3447.     });
    
  3448. 
    
  3449.     await waitForAll([]);
    
  3450. 
    
  3451.     expectErrors(
    
  3452.       errors,
    
  3453.       [
    
  3454.         [
    
  3455.           'The server did not finish this Suspense boundary: foobar',
    
  3456.           'a digest',
    
  3457.           componentStack(['Suspense', 'p', 'div', 'App']),
    
  3458.         ],
    
  3459.         [
    
  3460.           'The server did not finish this Suspense boundary: foobar',
    
  3461.           'a digest',
    
  3462.           componentStack(['Suspense', 'span', 'div', 'App']),
    
  3463.         ],
    
  3464.       ],
    
  3465.       [
    
  3466.         [
    
  3467.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  3468.           'a digest',
    
  3469.         ],
    
  3470.         [
    
  3471.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  3472.           'a digest',
    
  3473.         ],
    
  3474.       ],
    
  3475.     );
    
  3476.   });
    
  3477. 
    
  3478.   it('Supports custom abort reasons with an Error', async () => {
    
  3479.     function App() {
    
  3480.       return (
    
  3481.         <div>
    
  3482.           <p>
    
  3483.             <Suspense fallback={'p'}>
    
  3484.               <AsyncText text={'hello'} />
    
  3485.             </Suspense>
    
  3486.           </p>
    
  3487.           <span>
    
  3488.             <Suspense fallback={'span'}>
    
  3489.               <AsyncText text={'world'} />
    
  3490.             </Suspense>
    
  3491.           </span>
    
  3492.         </div>
    
  3493.       );
    
  3494.     }
    
  3495. 
    
  3496.     let abort;
    
  3497.     const loggedErrors = [];
    
  3498.     await act(() => {
    
  3499.       const {pipe, abort: abortImpl} = renderToPipeableStream(<App />, {
    
  3500.         onError(error) {
    
  3501.           loggedErrors.push(error.message);
    
  3502.           return 'a digest';
    
  3503.         },
    
  3504.       });
    
  3505.       abort = abortImpl;
    
  3506.       pipe(writable);
    
  3507.     });
    
  3508. 
    
  3509.     expect(loggedErrors).toEqual([]);
    
  3510.     expect(getVisibleChildren(container)).toEqual(
    
  3511.       <div>
    
  3512.         <p>p</p>
    
  3513.         <span>span</span>
    
  3514.       </div>,
    
  3515.     );
    
  3516. 
    
  3517.     await act(() => {
    
  3518.       abort(new Error('uh oh'));
    
  3519.     });
    
  3520. 
    
  3521.     expect(loggedErrors).toEqual(['uh oh', 'uh oh']);
    
  3522. 
    
  3523.     const errors = [];
    
  3524.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  3525.       onRecoverableError(error, errorInfo) {
    
  3526.         errors.push({error, errorInfo});
    
  3527.       },
    
  3528.     });
    
  3529. 
    
  3530.     await waitForAll([]);
    
  3531. 
    
  3532.     expectErrors(
    
  3533.       errors,
    
  3534.       [
    
  3535.         [
    
  3536.           'The server did not finish this Suspense boundary: uh oh',
    
  3537.           'a digest',
    
  3538.           componentStack(['Suspense', 'p', 'div', 'App']),
    
  3539.         ],
    
  3540.         [
    
  3541.           'The server did not finish this Suspense boundary: uh oh',
    
  3542.           'a digest',
    
  3543.           componentStack(['Suspense', 'span', 'div', 'App']),
    
  3544.         ],
    
  3545.       ],
    
  3546.       [
    
  3547.         [
    
  3548.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  3549.           'a digest',
    
  3550.         ],
    
  3551.         [
    
  3552.           'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  3553.           'a digest',
    
  3554.         ],
    
  3555.       ],
    
  3556.     );
    
  3557.   });
    
  3558. 
    
  3559.   it('warns in dev if you access digest from errorInfo in onRecoverableError', async () => {
    
  3560.     await act(() => {
    
  3561.       const {pipe} = renderToPipeableStream(
    
  3562.         <div>
    
  3563.           <Suspense fallback={'loading...'}>
    
  3564.             <AsyncText text={'hello'} />
    
  3565.           </Suspense>
    
  3566.         </div>,
    
  3567.         {
    
  3568.           onError(error) {
    
  3569.             return 'a digest';
    
  3570.           },
    
  3571.         },
    
  3572.       );
    
  3573.       rejectText('hello');
    
  3574.       pipe(writable);
    
  3575.     });
    
  3576.     expect(getVisibleChildren(container)).toEqual(<div>loading...</div>);
    
  3577. 
    
  3578.     ReactDOMClient.hydrateRoot(
    
  3579.       container,
    
  3580.       <div>
    
  3581.         <Suspense fallback={'loading...'}>hello</Suspense>
    
  3582.       </div>,
    
  3583.       {
    
  3584.         onRecoverableError(error, errorInfo) {
    
  3585.           expect(() => {
    
  3586.             expect(error.digest).toBe('a digest');
    
  3587.             expect(errorInfo.digest).toBe('a digest');
    
  3588.           }).toErrorDev(
    
  3589.             'Warning: You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
    
  3590.               ' This property is deprecated and will be removed in a future version of React.' +
    
  3591.               ' To access the digest of an Error look for this property on the Error instance itself.',
    
  3592.             {withoutStack: true},
    
  3593.           );
    
  3594.         },
    
  3595.       },
    
  3596.     );
    
  3597.     await waitForAll([]);
    
  3598.   });
    
  3599. 
    
  3600.   it('takes an importMap option which emits an "importmap" script in the head', async () => {
    
  3601.     const importMap = {
    
  3602.       foo: './path/to/foo.js',
    
  3603.     };
    
  3604.     await act(() => {
    
  3605.       renderToPipeableStream(
    
  3606.         <html>
    
  3607.           <head>
    
  3608.             <script async={true} src="foo" />
    
  3609.           </head>
    
  3610.           <body>
    
  3611.             <div>hello world</div>
    
  3612.           </body>
    
  3613.         </html>,
    
  3614.         {
    
  3615.           importMap,
    
  3616.         },
    
  3617.       ).pipe(writable);
    
  3618.     });
    
  3619. 
    
  3620.     expect(document.head.innerHTML).toBe(
    
  3621.       '<script type="importmap">' +
    
  3622.         JSON.stringify(importMap) +
    
  3623.         '</script><script async="" src="foo"></script>',
    
  3624.     );
    
  3625.   });
    
  3626. 
    
  3627.   // bugfix: https://github.com/facebook/react/issues/27286 affecting enableCustomElementPropertySupport flag
    
  3628.   it('can render custom elements with children on ther server', async () => {
    
  3629.     await act(() => {
    
  3630.       renderToPipeableStream(
    
  3631.         <html>
    
  3632.           <body>
    
  3633.             <my-element>
    
  3634.               <div>foo</div>
    
  3635.             </my-element>
    
  3636.           </body>
    
  3637.         </html>,
    
  3638.       ).pipe(writable);
    
  3639.     });
    
  3640. 
    
  3641.     expect(getVisibleChildren(document)).toEqual(
    
  3642.       <html>
    
  3643.         <head />
    
  3644.         <body>
    
  3645.           <my-element>
    
  3646.             <div>foo</div>
    
  3647.           </my-element>
    
  3648.         </body>
    
  3649.       </html>,
    
  3650.     );
    
  3651.   });
    
  3652. 
    
  3653.   // https://github.com/facebook/react/issues/27540
    
  3654.   // This test is not actually asserting much because there is possibly a bug in the closeing logic for the
    
  3655.   // Node implementation of Fizz. The close leads to an abort which sets the destination to null before the Float
    
  3656.   // method has an opportunity to schedule a write. We should fix this probably and once we do this test will start
    
  3657.   // to fail if the underyling issue of writing after stream completion isn't fixed
    
  3658.   it('does not try to write to the stream after it has been closed', async () => {
    
  3659.     async function preloadLate() {
    
  3660.       await 1;
    
  3661.       ReactDOM.preconnect('foo');
    
  3662.     }
    
  3663. 
    
  3664.     function Preload() {
    
  3665.       preloadLate();
    
  3666.       return null;
    
  3667.     }
    
  3668. 
    
  3669.     function App() {
    
  3670.       return (
    
  3671.         <html>
    
  3672.           <body>
    
  3673.             <main>hello</main>
    
  3674.             <Preload />
    
  3675.           </body>
    
  3676.         </html>
    
  3677.       );
    
  3678.     }
    
  3679.     await act(() => {
    
  3680.       renderToPipeableStream(<App />).pipe(writable);
    
  3681.     });
    
  3682. 
    
  3683.     expect(getVisibleChildren(document)).toEqual(
    
  3684.       <html>
    
  3685.         <head />
    
  3686.         <body>
    
  3687.           <main>hello</main>
    
  3688.         </body>
    
  3689.       </html>,
    
  3690.     );
    
  3691.   });
    
  3692. 
    
  3693.   describe('error escaping', () => {
    
  3694.     it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => {
    
  3695.       window.__outlet = {};
    
  3696. 
    
  3697.       const dangerousErrorString =
    
  3698.         '"></template></div><script>window.__outlet.message="from error"</script><div><template data-foo="';
    
  3699. 
    
  3700.       function Erroring() {
    
  3701.         throw new Error(dangerousErrorString);
    
  3702.       }
    
  3703. 
    
  3704.       // We can't test newline in component stacks because the stack always takes just one line and we end up
    
  3705.       // dropping the first part including the \n character
    
  3706.       Erroring.displayName =
    
  3707.         'DangerousName' +
    
  3708.         dangerousErrorString.replace(
    
  3709.           'message="from error"',
    
  3710.           'stack="from_stack"',
    
  3711.         );
    
  3712. 
    
  3713.       function App() {
    
  3714.         return (
    
  3715.           <div>
    
  3716.             <Suspense fallback={<div>Loading...</div>}>
    
  3717.               <Erroring />
    
  3718.             </Suspense>
    
  3719.           </div>
    
  3720.         );
    
  3721.       }
    
  3722. 
    
  3723.       function onError(x) {
    
  3724.         return `dangerous hash ${x.message.replace(
    
  3725.           'message="from error"',
    
  3726.           'hash="from hash"',
    
  3727.         )}`;
    
  3728.       }
    
  3729. 
    
  3730.       await act(() => {
    
  3731.         const {pipe} = renderToPipeableStream(<App />, {
    
  3732.           onError,
    
  3733.         });
    
  3734.         pipe(writable);
    
  3735.       });
    
  3736.       expect(window.__outlet).toEqual({});
    
  3737.     });
    
  3738. 
    
  3739.     it('escapes error hash, message, and component stack values in clientRenderInstruction (javascript escaping)', async () => {
    
  3740.       window.__outlet = {};
    
  3741. 
    
  3742.       const dangerousErrorString =
    
  3743.         '");window.__outlet.message="from error";</script><script>(() => {})("';
    
  3744. 
    
  3745.       let rejectComponent;
    
  3746.       const SuspensyErroring = React.lazy(() => {
    
  3747.         return new Promise((resolve, reject) => {
    
  3748.           rejectComponent = reject;
    
  3749.         });
    
  3750.       });
    
  3751. 
    
  3752.       // We can't test newline in component stacks because the stack always takes just one line and we end up
    
  3753.       // dropping the first part including the \n character
    
  3754.       SuspensyErroring.displayName =
    
  3755.         'DangerousName' +
    
  3756.         dangerousErrorString.replace(
    
  3757.           'message="from error"',
    
  3758.           'stack="from_stack"',
    
  3759.         );
    
  3760. 
    
  3761.       function App() {
    
  3762.         return (
    
  3763.           <div>
    
  3764.             <Suspense fallback={<div>Loading...</div>}>
    
  3765.               <SuspensyErroring />
    
  3766.             </Suspense>
    
  3767.           </div>
    
  3768.         );
    
  3769.       }
    
  3770. 
    
  3771.       function onError(x) {
    
  3772.         return `dangerous hash ${x.message.replace(
    
  3773.           'message="from error"',
    
  3774.           'hash="from hash"',
    
  3775.         )}`;
    
  3776.       }
    
  3777. 
    
  3778.       await act(() => {
    
  3779.         const {pipe} = renderToPipeableStream(<App />, {
    
  3780.           onError,
    
  3781.         });
    
  3782.         pipe(writable);
    
  3783.       });
    
  3784. 
    
  3785.       await act(() => {
    
  3786.         rejectComponent(new Error(dangerousErrorString));
    
  3787.       });
    
  3788.       expect(window.__outlet).toEqual({});
    
  3789.     });
    
  3790. 
    
  3791.     it('escapes such that attributes cannot be masked', async () => {
    
  3792.       const dangerousErrorString = '" data-msg="bad message" data-foo="';
    
  3793.       const theError = new Error(dangerousErrorString);
    
  3794. 
    
  3795.       function Erroring({isClient}) {
    
  3796.         if (isClient) return 'Hello';
    
  3797.         throw theError;
    
  3798.       }
    
  3799. 
    
  3800.       function App({isClient}) {
    
  3801.         return (
    
  3802.           <div>
    
  3803.             <Suspense fallback={<div>Loading...</div>}>
    
  3804.               <Erroring isClient={isClient} />
    
  3805.             </Suspense>
    
  3806.           </div>
    
  3807.         );
    
  3808.       }
    
  3809. 
    
  3810.       const loggedErrors = [];
    
  3811.       function onError(x) {
    
  3812.         loggedErrors.push(x);
    
  3813.         return x.message.replace('bad message', 'bad hash');
    
  3814.       }
    
  3815.       const expectedDigest = onError(theError);
    
  3816.       loggedErrors.length = 0;
    
  3817. 
    
  3818.       await act(() => {
    
  3819.         const {pipe} = renderToPipeableStream(<App />, {
    
  3820.           onError,
    
  3821.         });
    
  3822.         pipe(writable);
    
  3823.       });
    
  3824. 
    
  3825.       expect(loggedErrors).toEqual([theError]);
    
  3826. 
    
  3827.       const errors = [];
    
  3828.       ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  3829.         onRecoverableError(error, errorInfo) {
    
  3830.           errors.push({error, errorInfo});
    
  3831.         },
    
  3832.       });
    
  3833.       await waitForAll([]);
    
  3834. 
    
  3835.       // If escaping were not done we would get a message that says "bad hash"
    
  3836.       expectErrors(
    
  3837.         errors,
    
  3838.         [
    
  3839.           [
    
  3840.             theError.message,
    
  3841.             expectedDigest,
    
  3842.             componentStack(['Erroring', 'Suspense', 'div', 'App']),
    
  3843.           ],
    
  3844.         ],
    
  3845.         [
    
  3846.           [
    
  3847.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  3848.             expectedDigest,
    
  3849.           ],
    
  3850.         ],
    
  3851.       );
    
  3852.     });
    
  3853.   });
    
  3854. 
    
  3855.   it('accepts an integrity property for bootstrapScripts and bootstrapModules', async () => {
    
  3856.     await act(() => {
    
  3857.       const {pipe} = renderToPipeableStream(
    
  3858.         <html>
    
  3859.           <head />
    
  3860.           <body>
    
  3861.             <div>hello world</div>
    
  3862.           </body>
    
  3863.         </html>,
    
  3864.         {
    
  3865.           bootstrapScripts: [
    
  3866.             'foo',
    
  3867.             {
    
  3868.               src: 'bar',
    
  3869.             },
    
  3870.             {
    
  3871.               src: 'baz',
    
  3872.               integrity: 'qux',
    
  3873.             },
    
  3874.           ],
    
  3875.           bootstrapModules: [
    
  3876.             'quux',
    
  3877.             {
    
  3878.               src: 'corge',
    
  3879.             },
    
  3880.             {
    
  3881.               src: 'grault',
    
  3882.               integrity: 'garply',
    
  3883.             },
    
  3884.           ],
    
  3885.         },
    
  3886.       );
    
  3887.       pipe(writable);
    
  3888.     });
    
  3889. 
    
  3890.     expect(getVisibleChildren(document)).toEqual(
    
  3891.       <html>
    
  3892.         <head>
    
  3893.           <link rel="preload" fetchpriority="low" href="foo" as="script" />
    
  3894.           <link rel="preload" fetchpriority="low" href="bar" as="script" />
    
  3895.           <link
    
  3896.             rel="preload"
    
  3897.             fetchpriority="low"
    
  3898.             href="baz"
    
  3899.             as="script"
    
  3900.             integrity="qux"
    
  3901.           />
    
  3902.           <link rel="modulepreload" fetchpriority="low" href="quux" />
    
  3903.           <link rel="modulepreload" fetchpriority="low" href="corge" />
    
  3904.           <link
    
  3905.             rel="modulepreload"
    
  3906.             fetchpriority="low"
    
  3907.             href="grault"
    
  3908.             integrity="garply"
    
  3909.           />
    
  3910.         </head>
    
  3911.         <body>
    
  3912.           <div>hello world</div>
    
  3913.         </body>
    
  3914.       </html>,
    
  3915.     );
    
  3916.     expect(
    
  3917.       stripExternalRuntimeInNodes(
    
  3918.         document.getElementsByTagName('script'),
    
  3919.         renderOptions.unstable_externalRuntimeSrc,
    
  3920.       ).map(n => n.outerHTML),
    
  3921.     ).toEqual([
    
  3922.       '<script src="foo" async=""></script>',
    
  3923.       '<script src="bar" async=""></script>',
    
  3924.       '<script src="baz" integrity="qux" async=""></script>',
    
  3925.       '<script type="module" src="quux" async=""></script>',
    
  3926.       '<script type="module" src="corge" async=""></script>',
    
  3927.       '<script type="module" src="grault" integrity="garply" async=""></script>',
    
  3928.     ]);
    
  3929.   });
    
  3930. 
    
  3931.   it('accepts a crossOrigin property for bootstrapScripts and bootstrapModules', async () => {
    
  3932.     await act(() => {
    
  3933.       const {pipe} = renderToPipeableStream(
    
  3934.         <html>
    
  3935.           <head />
    
  3936.           <body>
    
  3937.             <div>hello world</div>
    
  3938.           </body>
    
  3939.         </html>,
    
  3940.         {
    
  3941.           bootstrapScripts: [
    
  3942.             'foo',
    
  3943.             {
    
  3944.               src: 'bar',
    
  3945.             },
    
  3946.             {
    
  3947.               src: 'baz',
    
  3948.               crossOrigin: '',
    
  3949.             },
    
  3950.             {
    
  3951.               src: 'qux',
    
  3952.               crossOrigin: 'defaults-to-empty',
    
  3953.             },
    
  3954.           ],
    
  3955.           bootstrapModules: [
    
  3956.             'quux',
    
  3957.             {
    
  3958.               src: 'corge',
    
  3959.             },
    
  3960.             {
    
  3961.               src: 'grault',
    
  3962.               crossOrigin: 'use-credentials',
    
  3963.             },
    
  3964.           ],
    
  3965.         },
    
  3966.       );
    
  3967.       pipe(writable);
    
  3968.     });
    
  3969. 
    
  3970.     expect(getVisibleChildren(document)).toEqual(
    
  3971.       <html>
    
  3972.         <head>
    
  3973.           <link rel="preload" fetchpriority="low" href="foo" as="script" />
    
  3974.           <link rel="preload" fetchpriority="low" href="bar" as="script" />
    
  3975.           <link
    
  3976.             rel="preload"
    
  3977.             fetchpriority="low"
    
  3978.             href="baz"
    
  3979.             as="script"
    
  3980.             crossorigin=""
    
  3981.           />
    
  3982.           <link
    
  3983.             rel="preload"
    
  3984.             fetchpriority="low"
    
  3985.             href="qux"
    
  3986.             as="script"
    
  3987.             crossorigin=""
    
  3988.           />
    
  3989.           <link rel="modulepreload" fetchpriority="low" href="quux" />
    
  3990.           <link rel="modulepreload" fetchpriority="low" href="corge" />
    
  3991.           <link
    
  3992.             rel="modulepreload"
    
  3993.             fetchpriority="low"
    
  3994.             href="grault"
    
  3995.             crossorigin="use-credentials"
    
  3996.           />
    
  3997.         </head>
    
  3998.         <body>
    
  3999.           <div>hello world</div>
    
  4000.         </body>
    
  4001.       </html>,
    
  4002.     );
    
  4003.     expect(
    
  4004.       stripExternalRuntimeInNodes(
    
  4005.         document.getElementsByTagName('script'),
    
  4006.         renderOptions.unstable_externalRuntimeSrc,
    
  4007.       ).map(n => n.outerHTML),
    
  4008.     ).toEqual([
    
  4009.       '<script src="foo" async=""></script>',
    
  4010.       '<script src="bar" async=""></script>',
    
  4011.       '<script src="baz" crossorigin="" async=""></script>',
    
  4012.       '<script src="qux" crossorigin="" async=""></script>',
    
  4013.       '<script type="module" src="quux" async=""></script>',
    
  4014.       '<script type="module" src="corge" async=""></script>',
    
  4015.       '<script type="module" src="grault" crossorigin="use-credentials" async=""></script>',
    
  4016.     ]);
    
  4017.   });
    
  4018. 
    
  4019.   describe('bootstrapScriptContent and importMap escaping', () => {
    
  4020.     it('the "S" in "</?[Ss]cript" strings are replaced with unicode escaped lowercase s or S depending on case, preserving case sensitivity of nearby characters', async () => {
    
  4021.       window.__test_outlet = '';
    
  4022.       const stringWithScriptsInIt =
    
  4023.         'prescription pre<scription pre<Scription pre</scRipTion pre</ScripTion </script><script><!-- <script> -->';
    
  4024.       await act(() => {
    
  4025.         const {pipe} = renderToPipeableStream(<div />, {
    
  4026.           bootstrapScriptContent:
    
  4027.             'window.__test_outlet = "This should have been replaced";var x = "' +
    
  4028.             stringWithScriptsInIt +
    
  4029.             '";\nwindow.__test_outlet = x;',
    
  4030.         });
    
  4031.         pipe(writable);
    
  4032.       });
    
  4033.       expect(window.__test_outlet).toMatch(stringWithScriptsInIt);
    
  4034.     });
    
  4035. 
    
  4036.     it('does not escape \\u2028, or \\u2029 characters', async () => {
    
  4037.       // these characters are ignored in engines support https://github.com/tc39/proposal-json-superset
    
  4038.       // in this test with JSDOM the characters are silently dropped and thus don't need to be encoded.
    
  4039.       // if you send these characters to an older browser they could fail so it is a good idea to
    
  4040.       // sanitize JSON input of these characters
    
  4041.       window.__test_outlet = '';
    
  4042.       const el = document.createElement('p');
    
  4043.       el.textContent = '{"one":1,\u2028\u2029"two":2}';
    
  4044.       const stringWithLSAndPSCharacters = el.textContent;
    
  4045.       await act(() => {
    
  4046.         const {pipe} = renderToPipeableStream(<div />, {
    
  4047.           bootstrapScriptContent:
    
  4048.             'let x = ' +
    
  4049.             stringWithLSAndPSCharacters +
    
  4050.             '; window.__test_outlet = x;',
    
  4051.         });
    
  4052.         pipe(writable);
    
  4053.       });
    
  4054.       const outletString = JSON.stringify(window.__test_outlet);
    
  4055.       expect(outletString).toBe(
    
  4056.         stringWithLSAndPSCharacters.replace(/[\u2028\u2029]/g, ''),
    
  4057.       );
    
  4058.     });
    
  4059. 
    
  4060.     it('does not escape <, >, or & characters', async () => {
    
  4061.       // these characters valid javascript and may be necessary in scripts and won't be interpretted properly
    
  4062.       // escaped outside of a string context within javascript
    
  4063.       window.__test_outlet = null;
    
  4064.       // this boolean expression will be cast to a number due to the bitwise &. we will look for a truthy value (1) below
    
  4065.       const booleanLogicString = '1 < 2 & 3 > 1';
    
  4066.       await act(() => {
    
  4067.         const {pipe} = renderToPipeableStream(<div />, {
    
  4068.           bootstrapScriptContent:
    
  4069.             'let x = ' + booleanLogicString + '; window.__test_outlet = x;',
    
  4070.         });
    
  4071.         pipe(writable);
    
  4072.       });
    
  4073.       expect(window.__test_outlet).toBe(1);
    
  4074.     });
    
  4075. 
    
  4076.     it('escapes </[sS]cirpt> in importMaps', async () => {
    
  4077.       window.__test_outlet_key = '';
    
  4078.       window.__test_outlet_value = '';
    
  4079.       const jsonWithScriptsInIt = {
    
  4080.         "keypos</script><script>window.__test_outlet_key = 'pwned'</script><script>":
    
  4081.           'value',
    
  4082.         key: "valuepos</script><script>window.__test_outlet_value = 'pwned'</script><script>",
    
  4083.       };
    
  4084.       await act(() => {
    
  4085.         const {pipe} = renderToPipeableStream(<div />, {
    
  4086.           importMap: jsonWithScriptsInIt,
    
  4087.         });
    
  4088.         pipe(writable);
    
  4089.       });
    
  4090.       expect(window.__test_outlet_key).toBe('');
    
  4091.       expect(window.__test_outlet_value).toBe('');
    
  4092.     });
    
  4093.   });
    
  4094. 
    
  4095.   // @gate enableFizzExternalRuntime
    
  4096.   it('supports option to load runtime as an external script', async () => {
    
  4097.     await act(() => {
    
  4098.       const {pipe} = renderToPipeableStream(
    
  4099.         <html>
    
  4100.           <head />
    
  4101.           <body>
    
  4102.             <Suspense fallback={'loading...'}>
    
  4103.               <AsyncText text="Hello" />
    
  4104.             </Suspense>
    
  4105.           </body>
    
  4106.         </html>,
    
  4107.         {
    
  4108.           unstable_externalRuntimeSrc: 'src-of-external-runtime',
    
  4109.         },
    
  4110.       );
    
  4111.       pipe(writable);
    
  4112.     });
    
  4113. 
    
  4114.     // We want the external runtime to be sent in <head> so the script can be
    
  4115.     // fetched and executed as early as possible. For SSR pages using Suspense,
    
  4116.     // this script execution would be render blocking.
    
  4117.     expect(
    
  4118.       Array.from(document.head.getElementsByTagName('script')).map(
    
  4119.         n => n.outerHTML,
    
  4120.       ),
    
  4121.     ).toEqual(['<script src="src-of-external-runtime" async=""></script>']);
    
  4122. 
    
  4123.     expect(getVisibleChildren(document)).toEqual(
    
  4124.       <html>
    
  4125.         <head />
    
  4126.         <body>loading...</body>
    
  4127.       </html>,
    
  4128.     );
    
  4129.   });
    
  4130. 
    
  4131.   // @gate enableFizzExternalRuntime
    
  4132.   it('does not send script tags for SSR instructions when using the external runtime', async () => {
    
  4133.     function App() {
    
  4134.       return (
    
  4135.         <div>
    
  4136.           <Suspense fallback="Loading...">
    
  4137.             <div>
    
  4138.               <AsyncText text="Hello" />
    
  4139.             </div>
    
  4140.           </Suspense>
    
  4141.         </div>
    
  4142.       );
    
  4143.     }
    
  4144.     await act(() => {
    
  4145.       const {pipe} = renderToPipeableStream(<App />);
    
  4146.       pipe(writable);
    
  4147.     });
    
  4148.     await act(() => {
    
  4149.       resolveText('Hello');
    
  4150.     });
    
  4151. 
    
  4152.     // The only script elements sent should be from unstable_externalRuntimeSrc
    
  4153.     expect(document.getElementsByTagName('script').length).toEqual(1);
    
  4154.   });
    
  4155. 
    
  4156.   it('does not send the external runtime for static pages', async () => {
    
  4157.     await act(() => {
    
  4158.       const {pipe} = renderToPipeableStream(
    
  4159.         <html>
    
  4160.           <head />
    
  4161.           <body>
    
  4162.             <p>hello world!</p>
    
  4163.           </body>
    
  4164.         </html>,
    
  4165.       );
    
  4166.       pipe(writable);
    
  4167.     });
    
  4168. 
    
  4169.     // no scripts should be sent
    
  4170.     expect(document.getElementsByTagName('script').length).toEqual(0);
    
  4171. 
    
  4172.     // the html should be as-is
    
  4173.     expect(document.documentElement.innerHTML).toEqual(
    
  4174.       '<head></head><body><p>hello world!</p></body>',
    
  4175.     );
    
  4176.   });
    
  4177. 
    
  4178.   it('#24384: Suspending should halt hydration warnings and not emit any if hydration completes successfully after unsuspending', async () => {
    
  4179.     const makeApp = () => {
    
  4180.       let resolve, resolved;
    
  4181.       const promise = new Promise(r => {
    
  4182.         resolve = () => {
    
  4183.           resolved = true;
    
  4184.           return r();
    
  4185.         };
    
  4186.       });
    
  4187.       function ComponentThatSuspends() {
    
  4188.         if (!resolved) {
    
  4189.           throw promise;
    
  4190.         }
    
  4191.         return <p>A</p>;
    
  4192.       }
    
  4193. 
    
  4194.       const App = () => {
    
  4195.         return (
    
  4196.           <div>
    
  4197.             <Suspense fallback={<h1>Loading...</h1>}>
    
  4198.               <ComponentThatSuspends />
    
  4199.               <h2 name="hello">world</h2>
    
  4200.             </Suspense>
    
  4201.           </div>
    
  4202.         );
    
  4203.       };
    
  4204. 
    
  4205.       return [App, resolve];
    
  4206.     };
    
  4207. 
    
  4208.     const [ServerApp, serverResolve] = makeApp();
    
  4209.     await act(() => {
    
  4210.       const {pipe} = renderToPipeableStream(<ServerApp />);
    
  4211.       pipe(writable);
    
  4212.     });
    
  4213.     await act(() => {
    
  4214.       serverResolve();
    
  4215.     });
    
  4216. 
    
  4217.     expect(getVisibleChildren(container)).toEqual(
    
  4218.       <div>
    
  4219.         <p>A</p>
    
  4220.         <h2 name="hello">world</h2>
    
  4221.       </div>,
    
  4222.     );
    
  4223. 
    
  4224.     const [ClientApp, clientResolve] = makeApp();
    
  4225.     ReactDOMClient.hydrateRoot(container, <ClientApp />, {
    
  4226.       onRecoverableError(error) {
    
  4227.         Scheduler.log('Logged recoverable error: ' + error.message);
    
  4228.       },
    
  4229.     });
    
  4230.     await waitForAll([]);
    
  4231. 
    
  4232.     expect(getVisibleChildren(container)).toEqual(
    
  4233.       <div>
    
  4234.         <p>A</p>
    
  4235.         <h2 name="hello">world</h2>
    
  4236.       </div>,
    
  4237.     );
    
  4238. 
    
  4239.     // Now that the boundary resolves to it's children the hydration completes and discovers that there is a mismatch requiring
    
  4240.     // client-side rendering.
    
  4241.     await clientResolve();
    
  4242.     await waitForAll([]);
    
  4243.     expect(getVisibleChildren(container)).toEqual(
    
  4244.       <div>
    
  4245.         <p>A</p>
    
  4246.         <h2 name="hello">world</h2>
    
  4247.       </div>,
    
  4248.     );
    
  4249.   });
    
  4250. 
    
  4251.   // @gate enableClientRenderFallbackOnTextMismatch
    
  4252.   it('#24384: Suspending should halt hydration warnings but still emit hydration warnings after unsuspending if mismatches are genuine', async () => {
    
  4253.     const makeApp = () => {
    
  4254.       let resolve, resolved;
    
  4255.       const promise = new Promise(r => {
    
  4256.         resolve = () => {
    
  4257.           resolved = true;
    
  4258.           return r();
    
  4259.         };
    
  4260.       });
    
  4261.       function ComponentThatSuspends() {
    
  4262.         if (!resolved) {
    
  4263.           throw promise;
    
  4264.         }
    
  4265.         return <p>A</p>;
    
  4266.       }
    
  4267. 
    
  4268.       const App = ({text}) => {
    
  4269.         return (
    
  4270.           <div>
    
  4271.             <Suspense fallback={<h1>Loading...</h1>}>
    
  4272.               <ComponentThatSuspends />
    
  4273.               <h2 name={text}>{text}</h2>
    
  4274.             </Suspense>
    
  4275.           </div>
    
  4276.         );
    
  4277.       };
    
  4278. 
    
  4279.       return [App, resolve];
    
  4280.     };
    
  4281. 
    
  4282.     const [ServerApp, serverResolve] = makeApp();
    
  4283.     await act(() => {
    
  4284.       const {pipe} = renderToPipeableStream(<ServerApp text="initial" />);
    
  4285.       pipe(writable);
    
  4286.     });
    
  4287.     await act(() => {
    
  4288.       serverResolve();
    
  4289.     });
    
  4290. 
    
  4291.     expect(getVisibleChildren(container)).toEqual(
    
  4292.       <div>
    
  4293.         <p>A</p>
    
  4294.         <h2 name="initial">initial</h2>
    
  4295.       </div>,
    
  4296.     );
    
  4297. 
    
  4298.     // The client app is rendered with an intentionally incorrect text. The still Suspended component causes
    
  4299.     // hydration to fail silently (allowing for cache warming but otherwise skipping this boundary) until it
    
  4300.     // resolves.
    
  4301.     const [ClientApp, clientResolve] = makeApp();
    
  4302.     ReactDOMClient.hydrateRoot(container, <ClientApp text="replaced" />, {
    
  4303.       onRecoverableError(error) {
    
  4304.         Scheduler.log('Logged recoverable error: ' + error.message);
    
  4305.       },
    
  4306.     });
    
  4307.     await waitForAll([]);
    
  4308. 
    
  4309.     expect(getVisibleChildren(container)).toEqual(
    
  4310.       <div>
    
  4311.         <p>A</p>
    
  4312.         <h2 name="initial">initial</h2>
    
  4313.       </div>,
    
  4314.     );
    
  4315. 
    
  4316.     // Now that the boundary resolves to it's children the hydration completes and discovers that there is a mismatch requiring
    
  4317.     // client-side rendering.
    
  4318.     await clientResolve();
    
  4319.     await expect(async () => {
    
  4320.       await waitForAll([
    
  4321.         'Logged recoverable error: Text content does not match server-rendered HTML.',
    
  4322.         'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  4323.       ]);
    
  4324.     }).toErrorDev(
    
  4325.       'Warning: Text content did not match. Server: "initial" Client: "replaced',
    
  4326.     );
    
  4327.     expect(getVisibleChildren(container)).toEqual(
    
  4328.       <div>
    
  4329.         <p>A</p>
    
  4330.         <h2 name="replaced">replaced</h2>
    
  4331.       </div>,
    
  4332.     );
    
  4333. 
    
  4334.     await waitForAll([]);
    
  4335.   });
    
  4336. 
    
  4337.   // @gate enableClientRenderFallbackOnTextMismatch
    
  4338.   it('only warns once on hydration mismatch while within a suspense boundary', async () => {
    
  4339.     const originalConsoleError = console.error;
    
  4340.     const mockError = jest.fn();
    
  4341.     console.error = (...args) => {
    
  4342.       mockError(...args.map(normalizeCodeLocInfo));
    
  4343.     };
    
  4344. 
    
  4345.     const App = ({text}) => {
    
  4346.       return (
    
  4347.         <div>
    
  4348.           <Suspense fallback={<h1>Loading...</h1>}>
    
  4349.             <h2>{text}</h2>
    
  4350.             <h2>{text}</h2>
    
  4351.             <h2>{text}</h2>
    
  4352.           </Suspense>
    
  4353.         </div>
    
  4354.       );
    
  4355.     };
    
  4356. 
    
  4357.     try {
    
  4358.       await act(() => {
    
  4359.         const {pipe} = renderToPipeableStream(<App text="initial" />);
    
  4360.         pipe(writable);
    
  4361.       });
    
  4362. 
    
  4363.       expect(getVisibleChildren(container)).toEqual(
    
  4364.         <div>
    
  4365.           <h2>initial</h2>
    
  4366.           <h2>initial</h2>
    
  4367.           <h2>initial</h2>
    
  4368.         </div>,
    
  4369.       );
    
  4370. 
    
  4371.       ReactDOMClient.hydrateRoot(container, <App text="replaced" />, {
    
  4372.         onRecoverableError(error) {
    
  4373.           Scheduler.log('Logged recoverable error: ' + error.message);
    
  4374.         },
    
  4375.       });
    
  4376.       await waitForAll([
    
  4377.         'Logged recoverable error: Text content does not match server-rendered HTML.',
    
  4378.         'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  4379.       ]);
    
  4380. 
    
  4381.       expect(getVisibleChildren(container)).toEqual(
    
  4382.         <div>
    
  4383.           <h2>replaced</h2>
    
  4384.           <h2>replaced</h2>
    
  4385.           <h2>replaced</h2>
    
  4386.         </div>,
    
  4387.       );
    
  4388. 
    
  4389.       await waitForAll([]);
    
  4390.       if (__DEV__) {
    
  4391.         expect(mockError.mock.calls.length).toBe(1);
    
  4392.         expect(mockError.mock.calls[0]).toEqual([
    
  4393.           'Warning: Text content did not match. Server: "%s" Client: "%s"%s',
    
  4394.           'initial',
    
  4395.           'replaced',
    
  4396.           '\n' +
    
  4397.             '    in h2 (at **)\n' +
    
  4398.             '    in Suspense (at **)\n' +
    
  4399.             '    in div (at **)\n' +
    
  4400.             '    in App (at **)',
    
  4401.         ]);
    
  4402.       } else {
    
  4403.         expect(mockError.mock.calls.length).toBe(0);
    
  4404.       }
    
  4405.     } finally {
    
  4406.       console.error = originalConsoleError;
    
  4407.     }
    
  4408.   });
    
  4409. 
    
  4410.   it('supresses hydration warnings when an error occurs within a Suspense boundary', async () => {
    
  4411.     let isClient = false;
    
  4412. 
    
  4413.     function ThrowWhenHydrating({children}) {
    
  4414.       // This is a trick to only throw if we're hydrating, because
    
  4415.       // useSyncExternalStore calls getServerSnapshot instead of the regular
    
  4416.       // getSnapshot in that case.
    
  4417.       useSyncExternalStore(
    
  4418.         () => {},
    
  4419.         t => t,
    
  4420.         () => {
    
  4421.           if (isClient) {
    
  4422.             throw new Error('uh oh');
    
  4423.           }
    
  4424.         },
    
  4425.       );
    
  4426.       return children;
    
  4427.     }
    
  4428. 
    
  4429.     const App = () => {
    
  4430.       return (
    
  4431.         <div>
    
  4432.           <Suspense fallback={<h1>Loading...</h1>}>
    
  4433.             <ThrowWhenHydrating>
    
  4434.               <h1>one</h1>
    
  4435.             </ThrowWhenHydrating>
    
  4436.             <h2>two</h2>
    
  4437.             <h3>{isClient ? 'five' : 'three'}</h3>
    
  4438.           </Suspense>
    
  4439.         </div>
    
  4440.       );
    
  4441.     };
    
  4442. 
    
  4443.     await act(() => {
    
  4444.       const {pipe} = renderToPipeableStream(<App />);
    
  4445.       pipe(writable);
    
  4446.     });
    
  4447. 
    
  4448.     expect(getVisibleChildren(container)).toEqual(
    
  4449.       <div>
    
  4450.         <h1>one</h1>
    
  4451.         <h2>two</h2>
    
  4452.         <h3>three</h3>
    
  4453.       </div>,
    
  4454.     );
    
  4455. 
    
  4456.     isClient = true;
    
  4457. 
    
  4458.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  4459.       onRecoverableError(error) {
    
  4460.         Scheduler.log('Logged recoverable error: ' + error.message);
    
  4461.       },
    
  4462.     });
    
  4463.     await waitForAll([
    
  4464.       'Logged recoverable error: uh oh',
    
  4465.       'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  4466.     ]);
    
  4467. 
    
  4468.     expect(getVisibleChildren(container)).toEqual(
    
  4469.       <div>
    
  4470.         <h1>one</h1>
    
  4471.         <h2>two</h2>
    
  4472.         <h3>five</h3>
    
  4473.       </div>,
    
  4474.     );
    
  4475. 
    
  4476.     await waitForAll([]);
    
  4477.   });
    
  4478. 
    
  4479.   // @gate __DEV__
    
  4480.   it('does not invokeGuardedCallback for errors after the first hydration error', async () => {
    
  4481.     // We can't use the toErrorDev helper here because this is async.
    
  4482.     const originalConsoleError = console.error;
    
  4483.     const mockError = jest.fn();
    
  4484.     console.error = (...args) => {
    
  4485.       if (args.length > 1) {
    
  4486.         if (typeof args[1] === 'object') {
    
  4487.           mockError(args[0].split('\n')[0]);
    
  4488.           return;
    
  4489.         }
    
  4490.       }
    
  4491.       mockError(...args.map(normalizeCodeLocInfo));
    
  4492.     };
    
  4493.     let isClient = false;
    
  4494. 
    
  4495.     function ThrowWhenHydrating({children, message}) {
    
  4496.       // This is a trick to only throw if we're hydrating, because
    
  4497.       // useSyncExternalStore calls getServerSnapshot instead of the regular
    
  4498.       // getSnapshot in that case.
    
  4499.       useSyncExternalStore(
    
  4500.         () => {},
    
  4501.         t => t,
    
  4502.         () => {
    
  4503.           if (isClient) {
    
  4504.             Scheduler.log('throwing: ' + message);
    
  4505.             throw new Error(message);
    
  4506.           }
    
  4507.         },
    
  4508.       );
    
  4509.       return children;
    
  4510.     }
    
  4511. 
    
  4512.     const App = () => {
    
  4513.       return (
    
  4514.         <div>
    
  4515.           <Suspense fallback={<h1>Loading...</h1>}>
    
  4516.             <ThrowWhenHydrating message="first error">
    
  4517.               <h1>one</h1>
    
  4518.             </ThrowWhenHydrating>
    
  4519.             <ThrowWhenHydrating message="second error">
    
  4520.               <h2>two</h2>
    
  4521.             </ThrowWhenHydrating>
    
  4522.             <ThrowWhenHydrating message="third error">
    
  4523.               <h3>three</h3>
    
  4524.             </ThrowWhenHydrating>
    
  4525.           </Suspense>
    
  4526.         </div>
    
  4527.       );
    
  4528.     };
    
  4529. 
    
  4530.     try {
    
  4531.       await act(() => {
    
  4532.         const {pipe} = renderToPipeableStream(<App />);
    
  4533.         pipe(writable);
    
  4534.       });
    
  4535. 
    
  4536.       expect(getVisibleChildren(container)).toEqual(
    
  4537.         <div>
    
  4538.           <h1>one</h1>
    
  4539.           <h2>two</h2>
    
  4540.           <h3>three</h3>
    
  4541.         </div>,
    
  4542.       );
    
  4543. 
    
  4544.       isClient = true;
    
  4545. 
    
  4546.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  4547.         onRecoverableError(error) {
    
  4548.           Scheduler.log('Logged recoverable error: ' + error.message);
    
  4549.         },
    
  4550.       });
    
  4551.       await waitForAll([
    
  4552.         'throwing: first error',
    
  4553.         // this repeated first error is the invokeGuardedCallback throw
    
  4554.         'throwing: first error',
    
  4555. 
    
  4556.         // onRecoverableError because the UI recovered without surfacing the
    
  4557.         // error to the user.
    
  4558.         'Logged recoverable error: first error',
    
  4559.         'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  4560.       ]);
    
  4561.       // These Uncaught error calls are the error reported by the runtime (jsdom here, browser in actual use)
    
  4562.       // when invokeGuardedCallback is used to replay an error in dev using event dispatching in the document
    
  4563.       expect(mockError.mock.calls).toEqual([
    
  4564.         // we only get one because we suppress invokeGuardedCallback after the first one when hydrating in a
    
  4565.         // suspense boundary
    
  4566.         ['Error: Uncaught [Error: first error]'],
    
  4567.       ]);
    
  4568.       mockError.mockClear();
    
  4569. 
    
  4570.       expect(getVisibleChildren(container)).toEqual(
    
  4571.         <div>
    
  4572.           <h1>one</h1>
    
  4573.           <h2>two</h2>
    
  4574.           <h3>three</h3>
    
  4575.         </div>,
    
  4576.       );
    
  4577. 
    
  4578.       await waitForAll([]);
    
  4579.       expect(mockError.mock.calls).toEqual([]);
    
  4580.     } finally {
    
  4581.       console.error = originalConsoleError;
    
  4582.     }
    
  4583.   });
    
  4584. 
    
  4585.   // @gate __DEV__
    
  4586.   it('does not invokeGuardedCallback for errors after a preceding fiber suspends', async () => {
    
  4587.     // We can't use the toErrorDev helper here because this is async.
    
  4588.     const originalConsoleError = console.error;
    
  4589.     const mockError = jest.fn();
    
  4590.     console.error = (...args) => {
    
  4591.       if (args.length > 1) {
    
  4592.         if (typeof args[1] === 'object') {
    
  4593.           mockError(args[0].split('\n')[0]);
    
  4594.           return;
    
  4595.         }
    
  4596.       }
    
  4597.       mockError(...args.map(normalizeCodeLocInfo));
    
  4598.     };
    
  4599.     let isClient = false;
    
  4600.     let promise = null;
    
  4601.     let unsuspend = null;
    
  4602.     let isResolved = false;
    
  4603. 
    
  4604.     function ComponentThatSuspendsOnClient() {
    
  4605.       if (isClient && !isResolved) {
    
  4606.         if (promise === null) {
    
  4607.           promise = new Promise(resolve => {
    
  4608.             unsuspend = () => {
    
  4609.               isResolved = true;
    
  4610.               resolve();
    
  4611.             };
    
  4612.           });
    
  4613.         }
    
  4614.         Scheduler.log('suspending');
    
  4615.         throw promise;
    
  4616.       }
    
  4617.       return null;
    
  4618.     }
    
  4619. 
    
  4620.     function ThrowWhenHydrating({children, message}) {
    
  4621.       // This is a trick to only throw if we're hydrating, because
    
  4622.       // useSyncExternalStore calls getServerSnapshot instead of the regular
    
  4623.       // getSnapshot in that case.
    
  4624.       useSyncExternalStore(
    
  4625.         () => {},
    
  4626.         t => t,
    
  4627.         () => {
    
  4628.           if (isClient) {
    
  4629.             Scheduler.log('throwing: ' + message);
    
  4630.             throw new Error(message);
    
  4631.           }
    
  4632.         },
    
  4633.       );
    
  4634.       return children;
    
  4635.     }
    
  4636. 
    
  4637.     const App = () => {
    
  4638.       return (
    
  4639.         <div>
    
  4640.           <Suspense fallback={<h1>Loading...</h1>}>
    
  4641.             <ComponentThatSuspendsOnClient />
    
  4642.             <ThrowWhenHydrating message="first error">
    
  4643.               <h1>one</h1>
    
  4644.             </ThrowWhenHydrating>
    
  4645.             <ThrowWhenHydrating message="second error">
    
  4646.               <h2>two</h2>
    
  4647.             </ThrowWhenHydrating>
    
  4648.             <ThrowWhenHydrating message="third error">
    
  4649.               <h3>three</h3>
    
  4650.             </ThrowWhenHydrating>
    
  4651.           </Suspense>
    
  4652.         </div>
    
  4653.       );
    
  4654.     };
    
  4655. 
    
  4656.     try {
    
  4657.       await act(() => {
    
  4658.         const {pipe} = renderToPipeableStream(<App />);
    
  4659.         pipe(writable);
    
  4660.       });
    
  4661. 
    
  4662.       expect(getVisibleChildren(container)).toEqual(
    
  4663.         <div>
    
  4664.           <h1>one</h1>
    
  4665.           <h2>two</h2>
    
  4666.           <h3>three</h3>
    
  4667.         </div>,
    
  4668.       );
    
  4669. 
    
  4670.       isClient = true;
    
  4671. 
    
  4672.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  4673.         onRecoverableError(error) {
    
  4674.           Scheduler.log('Logged recoverable error: ' + error.message);
    
  4675.         },
    
  4676.       });
    
  4677.       await waitForAll(['suspending']);
    
  4678.       expect(mockError.mock.calls).toEqual([]);
    
  4679. 
    
  4680.       expect(getVisibleChildren(container)).toEqual(
    
  4681.         <div>
    
  4682.           <h1>one</h1>
    
  4683.           <h2>two</h2>
    
  4684.           <h3>three</h3>
    
  4685.         </div>,
    
  4686.       );
    
  4687.       await unsuspend();
    
  4688.       await waitForAll([
    
  4689.         'throwing: first error',
    
  4690.         'throwing: first error',
    
  4691.         'Logged recoverable error: first error',
    
  4692.         'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  4693.       ]);
    
  4694.       expect(getVisibleChildren(container)).toEqual(
    
  4695.         <div>
    
  4696.           <h1>one</h1>
    
  4697.           <h2>two</h2>
    
  4698.           <h3>three</h3>
    
  4699.         </div>,
    
  4700.       );
    
  4701.     } finally {
    
  4702.       console.error = originalConsoleError;
    
  4703.     }
    
  4704.   });
    
  4705. 
    
  4706.   // @gate __DEV__
    
  4707.   it('(outdated behavior) suspending after erroring will cause errors previously queued to be silenced until the boundary resolves', async () => {
    
  4708.     // NOTE: This test was originally written to test a scenario that doesn't happen
    
  4709.     // anymore. If something errors during hydration, we immediately unwind the
    
  4710.     // stack and revert to client rendering. I've kept the test around just to
    
  4711.     // demonstrate what actually happens in this sequence of events.
    
  4712. 
    
  4713.     // We can't use the toErrorDev helper here because this is async.
    
  4714.     const originalConsoleError = console.error;
    
  4715.     const mockError = jest.fn();
    
  4716.     console.error = (...args) => {
    
  4717.       if (args.length > 1) {
    
  4718.         if (typeof args[1] === 'object') {
    
  4719.           mockError(args[0].split('\n')[0]);
    
  4720.           return;
    
  4721.         }
    
  4722.       }
    
  4723.       mockError(...args.map(normalizeCodeLocInfo));
    
  4724.     };
    
  4725.     let isClient = false;
    
  4726.     let promise = null;
    
  4727.     let unsuspend = null;
    
  4728.     let isResolved = false;
    
  4729. 
    
  4730.     function ComponentThatSuspendsOnClient() {
    
  4731.       if (isClient && !isResolved) {
    
  4732.         if (promise === null) {
    
  4733.           promise = new Promise(resolve => {
    
  4734.             unsuspend = () => {
    
  4735.               isResolved = true;
    
  4736.               resolve();
    
  4737.             };
    
  4738.           });
    
  4739.         }
    
  4740.         Scheduler.log('suspending');
    
  4741.         throw promise;
    
  4742.       }
    
  4743.       return null;
    
  4744.     }
    
  4745. 
    
  4746.     function ThrowWhenHydrating({children, message}) {
    
  4747.       // This is a trick to only throw if we're hydrating, because
    
  4748.       // useSyncExternalStore calls getServerSnapshot instead of the regular
    
  4749.       // getSnapshot in that case.
    
  4750.       useSyncExternalStore(
    
  4751.         () => {},
    
  4752.         t => t,
    
  4753.         () => {
    
  4754.           if (isClient) {
    
  4755.             Scheduler.log('throwing: ' + message);
    
  4756.             throw new Error(message);
    
  4757.           }
    
  4758.         },
    
  4759.       );
    
  4760.       return children;
    
  4761.     }
    
  4762. 
    
  4763.     const App = () => {
    
  4764.       return (
    
  4765.         <div>
    
  4766.           <Suspense fallback={<h1>Loading...</h1>}>
    
  4767.             <ThrowWhenHydrating message="first error">
    
  4768.               <h1>one</h1>
    
  4769.             </ThrowWhenHydrating>
    
  4770.             <ThrowWhenHydrating message="second error">
    
  4771.               <h2>two</h2>
    
  4772.             </ThrowWhenHydrating>
    
  4773.             <ComponentThatSuspendsOnClient />
    
  4774.             <ThrowWhenHydrating message="third error">
    
  4775.               <h3>three</h3>
    
  4776.             </ThrowWhenHydrating>
    
  4777.           </Suspense>
    
  4778.         </div>
    
  4779.       );
    
  4780.     };
    
  4781. 
    
  4782.     try {
    
  4783.       await act(() => {
    
  4784.         const {pipe} = renderToPipeableStream(<App />);
    
  4785.         pipe(writable);
    
  4786.       });
    
  4787. 
    
  4788.       expect(getVisibleChildren(container)).toEqual(
    
  4789.         <div>
    
  4790.           <h1>one</h1>
    
  4791.           <h2>two</h2>
    
  4792.           <h3>three</h3>
    
  4793.         </div>,
    
  4794.       );
    
  4795. 
    
  4796.       isClient = true;
    
  4797. 
    
  4798.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  4799.         onRecoverableError(error) {
    
  4800.           Scheduler.log('Logged recoverable error: ' + error.message);
    
  4801.         },
    
  4802.       });
    
  4803.       await waitForAll([
    
  4804.         'throwing: first error',
    
  4805.         // duplicate because first error is re-done in invokeGuardedCallback
    
  4806.         'throwing: first error',
    
  4807.         'suspending',
    
  4808.         'Logged recoverable error: first error',
    
  4809.         'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  4810.       ]);
    
  4811.       // These Uncaught error calls are the error reported by the runtime (jsdom here, browser in actual use)
    
  4812.       // when invokeGuardedCallback is used to replay an error in dev using event dispatching in the document
    
  4813.       expect(mockError.mock.calls).toEqual([
    
  4814.         // we only get one because we suppress invokeGuardedCallback after the first one when hydrating in a
    
  4815.         // suspense boundary
    
  4816.         ['Error: Uncaught [Error: first error]'],
    
  4817.       ]);
    
  4818.       mockError.mockClear();
    
  4819. 
    
  4820.       expect(getVisibleChildren(container)).toEqual(
    
  4821.         <div>
    
  4822.           <h1>Loading...</h1>
    
  4823.         </div>,
    
  4824.       );
    
  4825.       await clientAct(() => unsuspend());
    
  4826.       // Since our client components only throw on the very first render there are no
    
  4827.       // new throws in this pass
    
  4828.       assertLog([]);
    
  4829.       expect(mockError.mock.calls).toEqual([]);
    
  4830. 
    
  4831.       expect(getVisibleChildren(container)).toEqual(
    
  4832.         <div>
    
  4833.           <h1>one</h1>
    
  4834.           <h2>two</h2>
    
  4835.           <h3>three</h3>
    
  4836.         </div>,
    
  4837.       );
    
  4838.     } finally {
    
  4839.       console.error = originalConsoleError;
    
  4840.     }
    
  4841.   });
    
  4842. 
    
  4843.   it('#24578 Hydration errors caused by a suspending component should not become recoverable when nested in an ancestor Suspense that is showing primary content', async () => {
    
  4844.     // this test failed before because hydration errors on the inner boundary were upgraded to recoverable by
    
  4845.     // a codepath of the outer boundary
    
  4846.     function App({isClient}) {
    
  4847.       return (
    
  4848.         <Suspense fallback={'outer'}>
    
  4849.           <Suspense fallback={'inner'}>
    
  4850.             <div>
    
  4851.               {isClient ? <AsyncText text="A" /> : <Text text="A" />}
    
  4852.               <b>B</b>
    
  4853.             </div>
    
  4854.           </Suspense>
    
  4855.         </Suspense>
    
  4856.       );
    
  4857.     }
    
  4858.     await act(() => {
    
  4859.       const {pipe} = renderToPipeableStream(<App />);
    
  4860.       pipe(writable);
    
  4861.     });
    
  4862. 
    
  4863.     const errors = [];
    
  4864.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  4865.       onRecoverableError(error) {
    
  4866.         errors.push(error.message);
    
  4867.       },
    
  4868.     });
    
  4869. 
    
  4870.     await waitForAll([]);
    
  4871.     expect(errors).toEqual([]);
    
  4872.     expect(getVisibleChildren(container)).toEqual(
    
  4873.       <div>
    
  4874.         A<b>B</b>
    
  4875.       </div>,
    
  4876.     );
    
  4877. 
    
  4878.     resolveText('A');
    
  4879.     await waitForAll([]);
    
  4880.     expect(errors).toEqual([]);
    
  4881.     expect(getVisibleChildren(container)).toEqual(
    
  4882.       <div>
    
  4883.         A<b>B</b>
    
  4884.       </div>,
    
  4885.     );
    
  4886.   });
    
  4887. 
    
  4888.   it('hydration warnings for mismatched text with multiple text nodes caused by suspending should be suppressed', async () => {
    
  4889.     let resolve;
    
  4890.     const Lazy = React.lazy(() => {
    
  4891.       return new Promise(r => {
    
  4892.         resolve = r;
    
  4893.       });
    
  4894.     });
    
  4895. 
    
  4896.     function App({isClient}) {
    
  4897.       return (
    
  4898.         <div>
    
  4899.           {isClient ? <Lazy /> : <p>lazy</p>}
    
  4900.           <p>some {'text'}</p>
    
  4901.         </div>
    
  4902.       );
    
  4903.     }
    
  4904.     await act(() => {
    
  4905.       const {pipe} = renderToPipeableStream(<App />);
    
  4906.       pipe(writable);
    
  4907.     });
    
  4908. 
    
  4909.     const errors = [];
    
  4910.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  4911.       onRecoverableError(error) {
    
  4912.         errors.push(error.message);
    
  4913.       },
    
  4914.     });
    
  4915. 
    
  4916.     await waitForAll([]);
    
  4917.     expect(errors).toEqual([]);
    
  4918.     expect(getVisibleChildren(container)).toEqual(
    
  4919.       <div>
    
  4920.         <p>lazy</p>
    
  4921.         <p>some {'text'}</p>
    
  4922.       </div>,
    
  4923.     );
    
  4924. 
    
  4925.     resolve({default: () => <p>lazy</p>});
    
  4926.     await waitForAll([]);
    
  4927.     expect(errors).toEqual([]);
    
  4928.     expect(getVisibleChildren(container)).toEqual(
    
  4929.       <div>
    
  4930.         <p>lazy</p>
    
  4931.         <p>some {'text'}</p>
    
  4932.       </div>,
    
  4933.     );
    
  4934.   });
    
  4935. 
    
  4936.   // @gate enableFloat
    
  4937.   it('can emit the preamble even if the head renders asynchronously', async () => {
    
  4938.     function AsyncNoOutput() {
    
  4939.       readText('nooutput');
    
  4940.       return null;
    
  4941.     }
    
  4942.     function AsyncHead() {
    
  4943.       readText('head');
    
  4944.       return (
    
  4945.         <head data-foo="foo">
    
  4946.           <title>a title</title>
    
  4947.         </head>
    
  4948.       );
    
  4949.     }
    
  4950.     function AsyncBody() {
    
  4951.       readText('body');
    
  4952.       return (
    
  4953.         <body data-bar="bar">
    
  4954.           <link rel="preload" as="style" href="foo" />
    
  4955.           hello
    
  4956.         </body>
    
  4957.       );
    
  4958.     }
    
  4959.     await act(() => {
    
  4960.       const {pipe} = renderToPipeableStream(
    
  4961.         <html data-html="html">
    
  4962.           <AsyncNoOutput />
    
  4963.           <AsyncHead />
    
  4964.           <AsyncBody />
    
  4965.         </html>,
    
  4966.       );
    
  4967.       pipe(writable);
    
  4968.     });
    
  4969.     await act(() => {
    
  4970.       resolveText('body');
    
  4971.     });
    
  4972.     await act(() => {
    
  4973.       resolveText('nooutput');
    
  4974.     });
    
  4975.     await act(() => {
    
  4976.       resolveText('head');
    
  4977.     });
    
  4978.     expect(getVisibleChildren(document)).toEqual(
    
  4979.       <html data-html="html">
    
  4980.         <head data-foo="foo">
    
  4981.           <link rel="preload" as="style" href="foo" />
    
  4982.           <title>a title</title>
    
  4983.         </head>
    
  4984.         <body data-bar="bar">hello</body>
    
  4985.       </html>,
    
  4986.     );
    
  4987.   });
    
  4988. 
    
  4989.   // @gate enableFloat
    
  4990.   it('holds back body and html closing tags (the postamble) until all pending tasks are completed', async () => {
    
  4991.     const chunks = [];
    
  4992.     writable.on('data', chunk => {
    
  4993.       chunks.push(chunk);
    
  4994.     });
    
  4995. 
    
  4996.     await act(() => {
    
  4997.       const {pipe} = renderToPipeableStream(
    
  4998.         <html>
    
  4999.           <head />
    
  5000.           <body>
    
  5001.             first
    
  5002.             <Suspense>
    
  5003.               <AsyncText text="second" />
    
  5004.             </Suspense>
    
  5005.           </body>
    
  5006.         </html>,
    
  5007.       );
    
  5008.       pipe(writable);
    
  5009.     });
    
  5010. 
    
  5011.     expect(getVisibleChildren(document)).toEqual(
    
  5012.       <html>
    
  5013.         <head />
    
  5014.         <body>{'first'}</body>
    
  5015.       </html>,
    
  5016.     );
    
  5017. 
    
  5018.     await act(() => {
    
  5019.       resolveText('second');
    
  5020.     });
    
  5021. 
    
  5022.     expect(getVisibleChildren(document)).toEqual(
    
  5023.       <html>
    
  5024.         <head />
    
  5025.         <body>
    
  5026.           {'first'}
    
  5027.           {'second'}
    
  5028.         </body>
    
  5029.       </html>,
    
  5030.     );
    
  5031. 
    
  5032.     expect(chunks.pop()).toEqual('</body></html>');
    
  5033.   });
    
  5034. 
    
  5035.   describe('text separators', () => {
    
  5036.     // To force performWork to start before resolving AsyncText but before piping we need to wait until
    
  5037.     // after scheduleWork which currently uses setImmediate to delay performWork
    
  5038.     function afterImmediate() {
    
  5039.       return new Promise(resolve => {
    
  5040.         setImmediate(resolve);
    
  5041.       });
    
  5042.     }
    
  5043. 
    
  5044.     it('it only includes separators between adjacent text nodes', async () => {
    
  5045.       function App({name}) {
    
  5046.         return (
    
  5047.           <div>
    
  5048.             hello<b>world, {name}</b>!
    
  5049.           </div>
    
  5050.         );
    
  5051.       }
    
  5052. 
    
  5053.       await act(() => {
    
  5054.         const {pipe} = renderToPipeableStream(<App name="Foo" />);
    
  5055.         pipe(writable);
    
  5056.       });
    
  5057. 
    
  5058.       expect(container.innerHTML).toEqual(
    
  5059.         '<div>hello<b>world, <!-- -->Foo</b>!</div>',
    
  5060.       );
    
  5061.       const errors = [];
    
  5062.       ReactDOMClient.hydrateRoot(container, <App name="Foo" />, {
    
  5063.         onRecoverableError(error) {
    
  5064.           errors.push(error.message);
    
  5065.         },
    
  5066.       });
    
  5067.       await waitForAll([]);
    
  5068.       expect(errors).toEqual([]);
    
  5069.       expect(getVisibleChildren(container)).toEqual(
    
  5070.         <div>
    
  5071.           hello<b>world, {'Foo'}</b>!
    
  5072.         </div>,
    
  5073.       );
    
  5074.     });
    
  5075. 
    
  5076.     it('it does not insert text separators even when adjacent text is in a delayed segment', async () => {
    
  5077.       function App({name}) {
    
  5078.         return (
    
  5079.           <Suspense fallback={'loading...'}>
    
  5080.             <div id="app-div">
    
  5081.               hello
    
  5082.               <b>
    
  5083.                 world, <AsyncText text={name} />
    
  5084.               </b>
    
  5085.               !
    
  5086.             </div>
    
  5087.           </Suspense>
    
  5088.         );
    
  5089.       }
    
  5090. 
    
  5091.       await act(() => {
    
  5092.         const {pipe} = renderToPipeableStream(<App name="Foo" />);
    
  5093.         pipe(writable);
    
  5094.       });
    
  5095. 
    
  5096.       expect(document.getElementById('app-div').outerHTML).toEqual(
    
  5097.         '<div id="app-div">hello<b>world, <template id="P:1"></template></b>!</div>',
    
  5098.       );
    
  5099. 
    
  5100.       await act(() => resolveText('Foo'));
    
  5101. 
    
  5102.       const div = stripExternalRuntimeInNodes(
    
  5103.         container.children,
    
  5104.         renderOptions.unstable_externalRuntimeSrc,
    
  5105.       )[0];
    
  5106.       expect(div.outerHTML).toEqual(
    
  5107.         '<div id="app-div">hello<b>world, Foo</b>!</div>',
    
  5108.       );
    
  5109. 
    
  5110.       // there may be either:
    
  5111.       //  - an external runtime script and deleted nodes with data attributes
    
  5112.       //  - extra script nodes containing fizz instructions at the end of container
    
  5113.       expect(
    
  5114.         Array.from(container.childNodes).filter(e => e.tagName !== 'SCRIPT')
    
  5115.           .length,
    
  5116.       ).toBe(3);
    
  5117. 
    
  5118.       expect(div.childNodes.length).toBe(3);
    
  5119.       const b = div.childNodes[1];
    
  5120.       expect(b.childNodes.length).toBe(2);
    
  5121.       expect(b.childNodes[0]).toMatchInlineSnapshot('world, ');
    
  5122.       expect(b.childNodes[1]).toMatchInlineSnapshot('Foo');
    
  5123. 
    
  5124.       const errors = [];
    
  5125.       ReactDOMClient.hydrateRoot(container, <App name="Foo" />, {
    
  5126.         onRecoverableError(error) {
    
  5127.           errors.push(error.message);
    
  5128.         },
    
  5129.       });
    
  5130.       await waitForAll([]);
    
  5131.       expect(errors).toEqual([]);
    
  5132.       expect(getVisibleChildren(container)).toEqual(
    
  5133.         <div id="app-div">
    
  5134.           hello<b>world, {'Foo'}</b>!
    
  5135.         </div>,
    
  5136.       );
    
  5137.     });
    
  5138. 
    
  5139.     it('it works with multiple adjacent segments', async () => {
    
  5140.       function App() {
    
  5141.         return (
    
  5142.           <Suspense fallback={'loading...'}>
    
  5143.             <div id="app-div">
    
  5144.               h<AsyncText text={'ello'} />
    
  5145.               w<AsyncText text={'orld'} />
    
  5146.             </div>
    
  5147.           </Suspense>
    
  5148.         );
    
  5149.       }
    
  5150. 
    
  5151.       await act(() => {
    
  5152.         const {pipe} = renderToPipeableStream(<App />);
    
  5153.         pipe(writable);
    
  5154.       });
    
  5155. 
    
  5156.       expect(document.getElementById('app-div').outerHTML).toEqual(
    
  5157.         '<div id="app-div">h<template id="P:1"></template>w<template id="P:2"></template></div>',
    
  5158.       );
    
  5159. 
    
  5160.       await act(() => resolveText('orld'));
    
  5161. 
    
  5162.       expect(document.getElementById('app-div').outerHTML).toEqual(
    
  5163.         '<div id="app-div">h<template id="P:1"></template>world</div>',
    
  5164.       );
    
  5165. 
    
  5166.       await act(() => resolveText('ello'));
    
  5167.       expect(
    
  5168.         stripExternalRuntimeInNodes(
    
  5169.           container.children,
    
  5170.           renderOptions.unstable_externalRuntimeSrc,
    
  5171.         )[0].outerHTML,
    
  5172.       ).toEqual('<div id="app-div">helloworld</div>');
    
  5173. 
    
  5174.       const errors = [];
    
  5175.       ReactDOMClient.hydrateRoot(container, <App name="Foo" />, {
    
  5176.         onRecoverableError(error) {
    
  5177.           errors.push(error.message);
    
  5178.         },
    
  5179.       });
    
  5180.       await waitForAll([]);
    
  5181.       expect(errors).toEqual([]);
    
  5182.       expect(getVisibleChildren(container)).toEqual(
    
  5183.         <div id="app-div">{['h', 'ello', 'w', 'orld']}</div>,
    
  5184.       );
    
  5185.     });
    
  5186. 
    
  5187.     it('it works when some segments are flushed and others are patched', async () => {
    
  5188.       function App() {
    
  5189.         return (
    
  5190.           <Suspense fallback={'loading...'}>
    
  5191.             <div id="app-div">
    
  5192.               h<AsyncText text={'ello'} />
    
  5193.               w<AsyncText text={'orld'} />
    
  5194.             </div>
    
  5195.           </Suspense>
    
  5196.         );
    
  5197.       }
    
  5198. 
    
  5199.       await act(async () => {
    
  5200.         const {pipe} = renderToPipeableStream(<App />);
    
  5201.         await afterImmediate();
    
  5202.         await act(() => resolveText('ello'));
    
  5203.         pipe(writable);
    
  5204.       });
    
  5205. 
    
  5206.       expect(document.getElementById('app-div').outerHTML).toEqual(
    
  5207.         '<div id="app-div">h<!-- -->ello<!-- -->w<template id="P:1"></template></div>',
    
  5208.       );
    
  5209. 
    
  5210.       await act(() => resolveText('orld'));
    
  5211. 
    
  5212.       expect(
    
  5213.         stripExternalRuntimeInNodes(
    
  5214.           container.children,
    
  5215.           renderOptions.unstable_externalRuntimeSrc,
    
  5216.         )[0].outerHTML,
    
  5217.       ).toEqual('<div id="app-div">h<!-- -->ello<!-- -->world</div>');
    
  5218. 
    
  5219.       const errors = [];
    
  5220.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5221.         onRecoverableError(error) {
    
  5222.           errors.push(error.message);
    
  5223.         },
    
  5224.       });
    
  5225.       await waitForAll([]);
    
  5226.       expect(errors).toEqual([]);
    
  5227.       expect(getVisibleChildren(container)).toEqual(
    
  5228.         <div id="app-div">{['h', 'ello', 'w', 'orld']}</div>,
    
  5229.       );
    
  5230.     });
    
  5231. 
    
  5232.     it('it does not prepend a text separators if the segment follows a non-Text Node', async () => {
    
  5233.       function App() {
    
  5234.         return (
    
  5235.           <Suspense fallback={'loading...'}>
    
  5236.             <div>
    
  5237.               hello
    
  5238.               <b>
    
  5239.                 <AsyncText text={'world'} />
    
  5240.               </b>
    
  5241.             </div>
    
  5242.           </Suspense>
    
  5243.         );
    
  5244.       }
    
  5245. 
    
  5246.       await act(async () => {
    
  5247.         const {pipe} = renderToPipeableStream(<App />);
    
  5248.         await afterImmediate();
    
  5249.         await act(() => resolveText('world'));
    
  5250.         pipe(writable);
    
  5251.       });
    
  5252. 
    
  5253.       expect(container.firstElementChild.outerHTML).toEqual(
    
  5254.         '<div>hello<b>world<!-- --></b></div>',
    
  5255.       );
    
  5256. 
    
  5257.       const errors = [];
    
  5258.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5259.         onRecoverableError(error) {
    
  5260.           errors.push(error.message);
    
  5261.         },
    
  5262.       });
    
  5263.       await waitForAll([]);
    
  5264.       expect(errors).toEqual([]);
    
  5265.       expect(getVisibleChildren(container)).toEqual(
    
  5266.         <div>
    
  5267.           hello<b>world</b>
    
  5268.         </div>,
    
  5269.       );
    
  5270.     });
    
  5271. 
    
  5272.     it('it does not prepend a text separators if the segments first emission is a non-Text Node', async () => {
    
  5273.       function App() {
    
  5274.         return (
    
  5275.           <Suspense fallback={'loading...'}>
    
  5276.             <div>
    
  5277.               hello
    
  5278.               <AsyncTextWrapped as={'b'} text={'world'} />
    
  5279.             </div>
    
  5280.           </Suspense>
    
  5281.         );
    
  5282.       }
    
  5283. 
    
  5284.       await act(async () => {
    
  5285.         const {pipe} = renderToPipeableStream(<App />);
    
  5286.         await afterImmediate();
    
  5287.         await act(() => resolveText('world'));
    
  5288.         pipe(writable);
    
  5289.       });
    
  5290. 
    
  5291.       expect(container.firstElementChild.outerHTML).toEqual(
    
  5292.         '<div>hello<b>world</b></div>',
    
  5293.       );
    
  5294. 
    
  5295.       const errors = [];
    
  5296.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5297.         onRecoverableError(error) {
    
  5298.           errors.push(error.message);
    
  5299.         },
    
  5300.       });
    
  5301.       await waitForAll([]);
    
  5302.       expect(errors).toEqual([]);
    
  5303.       expect(getVisibleChildren(container)).toEqual(
    
  5304.         <div>
    
  5305.           hello<b>world</b>
    
  5306.         </div>,
    
  5307.       );
    
  5308.     });
    
  5309. 
    
  5310.     it('should not insert separators for text inside Suspense boundaries even if they would otherwise be considered text-embedded', async () => {
    
  5311.       function App() {
    
  5312.         return (
    
  5313.           <Suspense fallback={'loading...'}>
    
  5314.             <div id="app-div">
    
  5315.               start
    
  5316.               <Suspense fallback={'[loading first]'}>
    
  5317.                 firststart
    
  5318.                 <AsyncText text={'first suspended'} />
    
  5319.                 firstend
    
  5320.               </Suspense>
    
  5321.               <Suspense fallback={'[loading second]'}>
    
  5322.                 secondstart
    
  5323.                 <b>
    
  5324.                   <AsyncText text={'second suspended'} />
    
  5325.                 </b>
    
  5326.               </Suspense>
    
  5327.               end
    
  5328.             </div>
    
  5329.           </Suspense>
    
  5330.         );
    
  5331.       }
    
  5332. 
    
  5333.       await act(async () => {
    
  5334.         const {pipe} = renderToPipeableStream(<App />);
    
  5335.         await afterImmediate();
    
  5336.         await act(() => resolveText('world'));
    
  5337.         pipe(writable);
    
  5338.       });
    
  5339. 
    
  5340.       expect(document.getElementById('app-div').outerHTML).toEqual(
    
  5341.         '<div id="app-div">start<!--$?--><template id="B:0"></template>[loading first]<!--/$--><!--$?--><template id="B:1"></template>[loading second]<!--/$-->end</div>',
    
  5342.       );
    
  5343. 
    
  5344.       await act(() => {
    
  5345.         resolveText('first suspended');
    
  5346.       });
    
  5347. 
    
  5348.       expect(document.getElementById('app-div').outerHTML).toEqual(
    
  5349.         '<div id="app-div">start<!--$-->firststartfirst suspendedfirstend<!--/$--><!--$?--><template id="B:1"></template>[loading second]<!--/$-->end</div>',
    
  5350.       );
    
  5351. 
    
  5352.       const errors = [];
    
  5353.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5354.         onRecoverableError(error) {
    
  5355.           errors.push(error.message);
    
  5356.         },
    
  5357.       });
    
  5358.       await waitForAll([]);
    
  5359.       expect(errors).toEqual([]);
    
  5360.       expect(getVisibleChildren(container)).toEqual(
    
  5361.         <div id="app-div">
    
  5362.           {'start'}
    
  5363.           {'firststart'}
    
  5364.           {'first suspended'}
    
  5365.           {'firstend'}
    
  5366.           {'[loading second]'}
    
  5367.           {'end'}
    
  5368.         </div>,
    
  5369.       );
    
  5370. 
    
  5371.       await act(() => {
    
  5372.         resolveText('second suspended');
    
  5373.       });
    
  5374. 
    
  5375.       expect(
    
  5376.         stripExternalRuntimeInNodes(
    
  5377.           container.children,
    
  5378.           renderOptions.unstable_externalRuntimeSrc,
    
  5379.         )[0].outerHTML,
    
  5380.       ).toEqual(
    
  5381.         '<div id="app-div">start<!--$-->firststartfirst suspendedfirstend<!--/$--><!--$-->secondstart<b>second suspended</b><!--/$-->end</div>',
    
  5382.       );
    
  5383. 
    
  5384.       await waitForAll([]);
    
  5385.       expect(errors).toEqual([]);
    
  5386.       expect(getVisibleChildren(container)).toEqual(
    
  5387.         <div id="app-div">
    
  5388.           {'start'}
    
  5389.           {'firststart'}
    
  5390.           {'first suspended'}
    
  5391.           {'firstend'}
    
  5392.           {'secondstart'}
    
  5393.           <b>second suspended</b>
    
  5394.           {'end'}
    
  5395.         </div>,
    
  5396.       );
    
  5397.     });
    
  5398. 
    
  5399.     it('(only) includes extraneous text separators in segments that complete before flushing, followed by nothing or a non-Text node', async () => {
    
  5400.       function App() {
    
  5401.         return (
    
  5402.           <div>
    
  5403.             <Suspense fallback={'text before, nothing after...'}>
    
  5404.               hello
    
  5405.               <AsyncText text="world" />
    
  5406.             </Suspense>
    
  5407.             <Suspense fallback={'nothing before or after...'}>
    
  5408.               <AsyncText text="world" />
    
  5409.             </Suspense>
    
  5410.             <Suspense fallback={'text before, element after...'}>
    
  5411.               hello
    
  5412.               <AsyncText text="world" />
    
  5413.               <br />
    
  5414.             </Suspense>
    
  5415.             <Suspense fallback={'nothing before, element after...'}>
    
  5416.               <AsyncText text="world" />
    
  5417.               <br />
    
  5418.             </Suspense>
    
  5419.           </div>
    
  5420.         );
    
  5421.       }
    
  5422. 
    
  5423.       await act(async () => {
    
  5424.         const {pipe} = renderToPipeableStream(<App />);
    
  5425.         await afterImmediate();
    
  5426.         await act(() => resolveText('world'));
    
  5427.         pipe(writable);
    
  5428.       });
    
  5429. 
    
  5430.       expect(container.innerHTML).toEqual(
    
  5431.         '<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
    
  5432.       );
    
  5433. 
    
  5434.       const errors = [];
    
  5435.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5436.         onRecoverableError(error) {
    
  5437.           errors.push(error.message);
    
  5438.         },
    
  5439.       });
    
  5440.       await waitForAll([]);
    
  5441.       expect(errors).toEqual([]);
    
  5442.       expect(getVisibleChildren(container)).toEqual(
    
  5443.         <div>
    
  5444.           {/* first boundary */}
    
  5445.           {'hello'}
    
  5446.           {'world'}
    
  5447.           {/* second boundary */}
    
  5448.           {'world'}
    
  5449.           {/* third boundary */}
    
  5450.           {'hello'}
    
  5451.           {'world'}
    
  5452.           <br />
    
  5453.           {/* fourth boundary */}
    
  5454.           {'world'}
    
  5455.           <br />
    
  5456.         </div>,
    
  5457.       );
    
  5458.     });
    
  5459.   });
    
  5460. 
    
  5461.   describe('title children', () => {
    
  5462.     it('should accept a single string child', async () => {
    
  5463.       // a Single string child
    
  5464.       function App() {
    
  5465.         return (
    
  5466.           <head>
    
  5467.             <title>hello</title>
    
  5468.           </head>
    
  5469.         );
    
  5470.       }
    
  5471. 
    
  5472.       await act(() => {
    
  5473.         const {pipe} = renderToPipeableStream(<App />);
    
  5474.         pipe(writable);
    
  5475.       });
    
  5476.       expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
    
  5477. 
    
  5478.       const errors = [];
    
  5479.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5480.         onRecoverableError(error) {
    
  5481.           errors.push(error.message);
    
  5482.         },
    
  5483.       });
    
  5484.       await waitForAll([]);
    
  5485.       expect(errors).toEqual([]);
    
  5486.       expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
    
  5487.     });
    
  5488. 
    
  5489.     it('should accept children array of length 1 containing a string', async () => {
    
  5490.       // a Single string child
    
  5491.       function App() {
    
  5492.         return (
    
  5493.           <head>
    
  5494.             <title>{['hello']}</title>
    
  5495.           </head>
    
  5496.         );
    
  5497.       }
    
  5498. 
    
  5499.       await act(() => {
    
  5500.         const {pipe} = renderToPipeableStream(<App />);
    
  5501.         pipe(writable);
    
  5502.       });
    
  5503.       expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
    
  5504. 
    
  5505.       const errors = [];
    
  5506.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  5507.         onRecoverableError(error) {
    
  5508.           errors.push(error.message);
    
  5509.         },
    
  5510.       });
    
  5511.       await waitForAll([]);
    
  5512.       expect(errors).toEqual([]);
    
  5513.       expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
    
  5514.     });
    
  5515. 
    
  5516.     it('should warn in dev when given an array of length 2 or more', async () => {
    
  5517.       function App() {
    
  5518.         return (
    
  5519.           <head>
    
  5520.             <title>{['hello1', 'hello2']}</title>
    
  5521.           </head>
    
  5522.         );
    
  5523.       }
    
  5524. 
    
  5525.       await expect(async () => {
    
  5526.         await act(() => {
    
  5527.           const {pipe} = renderToPipeableStream(<App />);
    
  5528.           pipe(writable);
    
  5529.         });
    
  5530.       }).toErrorDev([
    
  5531.         'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an Array with length 2 instead. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes. For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
    
  5532.       ]);
    
  5533. 
    
  5534.       if (gate(flags => flags.enableFloat)) {
    
  5535.         expect(getVisibleChildren(document.head)).toEqual(<title />);
    
  5536.       } else {
    
  5537.         expect(getVisibleChildren(document.head)).toEqual(
    
  5538.           <title>{'hello1<!-- -->hello2'}</title>,
    
  5539.         );
    
  5540.       }
    
  5541. 
    
  5542.       const errors = [];
    
  5543.       ReactDOMClient.hydrateRoot(document.head, <App />, {
    
  5544.         onRecoverableError(error) {
    
  5545.           errors.push(error.message);
    
  5546.         },
    
  5547.       });
    
  5548.       await waitForAll([]);
    
  5549.       if (gate(flags => flags.enableFloat)) {
    
  5550.         expect(errors).toEqual([]);
    
  5551.         // with float, the title doesn't render on the client or on the server
    
  5552.         expect(getVisibleChildren(document.head)).toEqual(<title />);
    
  5553.       } else {
    
  5554.         expect(errors).toEqual(
    
  5555.           [
    
  5556.             gate(flags => flags.enableClientRenderFallbackOnTextMismatch)
    
  5557.               ? 'Text content does not match server-rendered HTML.'
    
  5558.               : null,
    
  5559.             'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  5560.             'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  5561.           ].filter(Boolean),
    
  5562.         );
    
  5563.         expect(getVisibleChildren(document.head)).toEqual(
    
  5564.           <title>{['hello1', 'hello2']}</title>,
    
  5565.         );
    
  5566.       }
    
  5567.     });
    
  5568. 
    
  5569.     it('should warn in dev if you pass a React Component as a child to <title>', async () => {
    
  5570.       function IndirectTitle() {
    
  5571.         return 'hello';
    
  5572.       }
    
  5573. 
    
  5574.       function App() {
    
  5575.         return (
    
  5576.           <head>
    
  5577.             <title>
    
  5578.               <IndirectTitle />
    
  5579.             </title>
    
  5580.           </head>
    
  5581.         );
    
  5582.       }
    
  5583. 
    
  5584.       if (gate(flags => flags.enableFloat)) {
    
  5585.         await expect(async () => {
    
  5586.           await act(() => {
    
  5587.             const {pipe} = renderToPipeableStream(<App />);
    
  5588.             pipe(writable);
    
  5589.           });
    
  5590.         }).toErrorDev([
    
  5591.           'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that appears to be a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
    
  5592.         ]);
    
  5593.       } else {
    
  5594.         await expect(async () => {
    
  5595.           await act(() => {
    
  5596.             const {pipe} = renderToPipeableStream(<App />);
    
  5597.             pipe(writable);
    
  5598.           });
    
  5599.         }).toErrorDev([
    
  5600.           'A title element received a React element for children. In the browser title Elements can only have Text Nodes as children. If the children being rendered output more than a single text node in aggregate the browser will display markup and comments as text in the title and hydration will likely fail and fall back to client rendering',
    
  5601.         ]);
    
  5602.       }
    
  5603. 
    
  5604.       if (gate(flags => flags.enableFloat)) {
    
  5605.         // object titles are toStringed when float is on
    
  5606.         expect(getVisibleChildren(document.head)).toEqual(
    
  5607.           <title>{'[object Object]'}</title>,
    
  5608.         );
    
  5609.       } else {
    
  5610.         expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
    
  5611.       }
    
  5612. 
    
  5613.       const errors = [];
    
  5614.       ReactDOMClient.hydrateRoot(document.head, <App />, {
    
  5615.         onRecoverableError(error) {
    
  5616.           errors.push(error.message);
    
  5617.         },
    
  5618.       });
    
  5619.       await waitForAll([]);
    
  5620.       expect(errors).toEqual([]);
    
  5621.       if (gate(flags => flags.enableFloat)) {
    
  5622.         // object titles are toStringed when float is on
    
  5623.         expect(getVisibleChildren(document.head)).toEqual(
    
  5624.           <title>{'[object Object]'}</title>,
    
  5625.         );
    
  5626.       } else {
    
  5627.         expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
    
  5628.       }
    
  5629.     });
    
  5630.   });
    
  5631. 
    
  5632.   it('basic use(promise)', async () => {
    
  5633.     const promiseA = Promise.resolve('A');
    
  5634.     const promiseB = Promise.resolve('B');
    
  5635.     const promiseC = Promise.resolve('C');
    
  5636. 
    
  5637.     function Async() {
    
  5638.       return use(promiseA) + use(promiseB) + use(promiseC);
    
  5639.     }
    
  5640. 
    
  5641.     function App() {
    
  5642.       return (
    
  5643.         <Suspense fallback="Loading...">
    
  5644.           <Async />
    
  5645.         </Suspense>
    
  5646.       );
    
  5647.     }
    
  5648. 
    
  5649.     await act(() => {
    
  5650.       const {pipe} = renderToPipeableStream(<App />);
    
  5651.       pipe(writable);
    
  5652.     });
    
  5653. 
    
  5654.     // TODO: The `act` implementation in this file doesn't unwrap microtasks
    
  5655.     // automatically. We can't use the same `act` we use for Fiber tests
    
  5656.     // because that relies on the mock Scheduler. Doesn't affect any public
    
  5657.     // API but we might want to fix this for our own internal tests.
    
  5658.     //
    
  5659.     // For now, wait for each promise in sequence.
    
  5660.     await act(async () => {
    
  5661.       await promiseA;
    
  5662.     });
    
  5663.     await act(async () => {
    
  5664.       await promiseB;
    
  5665.     });
    
  5666.     await act(async () => {
    
  5667.       await promiseC;
    
  5668.     });
    
  5669. 
    
  5670.     expect(getVisibleChildren(container)).toEqual('ABC');
    
  5671. 
    
  5672.     ReactDOMClient.hydrateRoot(container, <App />);
    
  5673.     await waitForAll([]);
    
  5674.     expect(getVisibleChildren(container)).toEqual('ABC');
    
  5675.   });
    
  5676. 
    
  5677.   // @gate enableServerContext
    
  5678.   it('basic use(context)', async () => {
    
  5679.     const ContextA = React.createContext('default');
    
  5680.     const ContextB = React.createContext('B');
    
  5681.     let ServerContext;
    
  5682.     expect(() => {
    
  5683.       ServerContext = React.createServerContext('ServerContext', 'default');
    
  5684.     }).toErrorDev(
    
  5685.       'Server Context is deprecated and will soon be removed. ' +
    
  5686.         'It was never documented and we have found it not to be useful ' +
    
  5687.         'enough to warrant the downside it imposes on all apps.',
    
  5688.       {withoutStack: true},
    
  5689.     );
    
  5690.     function Client() {
    
  5691.       return use(ContextA) + use(ContextB);
    
  5692.     }
    
  5693.     function ServerComponent() {
    
  5694.       return use(ServerContext);
    
  5695.     }
    
  5696.     function Server() {
    
  5697.       return (
    
  5698.         <ServerContext.Provider value="C">
    
  5699.           <ServerComponent />
    
  5700.         </ServerContext.Provider>
    
  5701.       );
    
  5702.     }
    
  5703.     function App() {
    
  5704.       return (
    
  5705.         <>
    
  5706.           <ContextA.Provider value="A">
    
  5707.             <Client />
    
  5708.           </ContextA.Provider>
    
  5709.           <Server />
    
  5710.         </>
    
  5711.       );
    
  5712.     }
    
  5713. 
    
  5714.     await act(() => {
    
  5715.       const {pipe} = renderToPipeableStream(<App />);
    
  5716.       pipe(writable);
    
  5717.     });
    
  5718.     expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
    
  5719. 
    
  5720.     // Hydration uses a different renderer runtime (Fiber instead of Fizz).
    
  5721.     // We reset _currentRenderer here to not trigger a warning about multiple
    
  5722.     // renderers concurrently using these contexts
    
  5723.     ContextA._currentRenderer = null;
    
  5724.     ServerContext._currentRenderer = null;
    
  5725.     ReactDOMClient.hydrateRoot(container, <App />);
    
  5726.     await waitForAll([]);
    
  5727.     expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
    
  5728.   });
    
  5729. 
    
  5730.   it('use(promise) in multiple components', async () => {
    
  5731.     const promiseA = Promise.resolve('A');
    
  5732.     const promiseB = Promise.resolve('B');
    
  5733.     const promiseC = Promise.resolve('C');
    
  5734.     const promiseD = Promise.resolve('D');
    
  5735. 
    
  5736.     function Child({prefix}) {
    
  5737.       return prefix + use(promiseC) + use(promiseD);
    
  5738.     }
    
  5739. 
    
  5740.     function Parent() {
    
  5741.       return <Child prefix={use(promiseA) + use(promiseB)} />;
    
  5742.     }
    
  5743. 
    
  5744.     function App() {
    
  5745.       return (
    
  5746.         <Suspense fallback="Loading...">
    
  5747.           <Parent />
    
  5748.         </Suspense>
    
  5749.       );
    
  5750.     }
    
  5751. 
    
  5752.     await act(() => {
    
  5753.       const {pipe} = renderToPipeableStream(<App />);
    
  5754.       pipe(writable);
    
  5755.     });
    
  5756. 
    
  5757.     // TODO: The `act` implementation in this file doesn't unwrap microtasks
    
  5758.     // automatically. We can't use the same `act` we use for Fiber tests
    
  5759.     // because that relies on the mock Scheduler. Doesn't affect any public
    
  5760.     // API but we might want to fix this for our own internal tests.
    
  5761.     //
    
  5762.     // For now, wait for each promise in sequence.
    
  5763.     await act(async () => {
    
  5764.       await promiseA;
    
  5765.     });
    
  5766.     await act(async () => {
    
  5767.       await promiseB;
    
  5768.     });
    
  5769.     await act(async () => {
    
  5770.       await promiseC;
    
  5771.     });
    
  5772.     await act(async () => {
    
  5773.       await promiseD;
    
  5774.     });
    
  5775. 
    
  5776.     expect(getVisibleChildren(container)).toEqual('ABCD');
    
  5777. 
    
  5778.     ReactDOMClient.hydrateRoot(container, <App />);
    
  5779.     await waitForAll([]);
    
  5780.     expect(getVisibleChildren(container)).toEqual('ABCD');
    
  5781.   });
    
  5782. 
    
  5783.   it('using a rejected promise will throw', async () => {
    
  5784.     const promiseA = Promise.resolve('A');
    
  5785.     const promiseB = Promise.reject(new Error('Oops!'));
    
  5786.     const promiseC = Promise.resolve('C');
    
  5787. 
    
  5788.     // Jest/Node will raise an unhandled rejected error unless we await this. It
    
  5789.     // works fine in the browser, though.
    
  5790.     await expect(promiseB).rejects.toThrow('Oops!');
    
  5791. 
    
  5792.     function Async() {
    
  5793.       return use(promiseA) + use(promiseB) + use(promiseC);
    
  5794.     }
    
  5795. 
    
  5796.     class ErrorBoundary extends React.Component {
    
  5797.       state = {error: null};
    
  5798.       static getDerivedStateFromError(error) {
    
  5799.         return {error};
    
  5800.       }
    
  5801.       render() {
    
  5802.         if (this.state.error) {
    
  5803.           return this.state.error.message;
    
  5804.         }
    
  5805.         return this.props.children;
    
  5806.       }
    
  5807.     }
    
  5808. 
    
  5809.     function App() {
    
  5810.       return (
    
  5811.         <Suspense fallback="Loading...">
    
  5812.           <ErrorBoundary>
    
  5813.             <Async />
    
  5814.           </ErrorBoundary>
    
  5815.         </Suspense>
    
  5816.       );
    
  5817.     }
    
  5818. 
    
  5819.     const reportedServerErrors = [];
    
  5820.     await act(() => {
    
  5821.       const {pipe} = renderToPipeableStream(<App />, {
    
  5822.         onError(error) {
    
  5823.           reportedServerErrors.push(error);
    
  5824.         },
    
  5825.       });
    
  5826.       pipe(writable);
    
  5827.     });
    
  5828. 
    
  5829.     // TODO: The `act` implementation in this file doesn't unwrap microtasks
    
  5830.     // automatically. We can't use the same `act` we use for Fiber tests
    
  5831.     // because that relies on the mock Scheduler. Doesn't affect any public
    
  5832.     // API but we might want to fix this for our own internal tests.
    
  5833.     //
    
  5834.     // For now, wait for each promise in sequence.
    
  5835.     await act(async () => {
    
  5836.       await promiseA;
    
  5837.     });
    
  5838.     await act(async () => {
    
  5839.       await expect(promiseB).rejects.toThrow('Oops!');
    
  5840.     });
    
  5841.     await act(async () => {
    
  5842.       await promiseC;
    
  5843.     });
    
  5844. 
    
  5845.     expect(getVisibleChildren(container)).toEqual('Loading...');
    
  5846.     expect(reportedServerErrors.length).toBe(1);
    
  5847.     expect(reportedServerErrors[0].message).toBe('Oops!');
    
  5848. 
    
  5849.     const reportedClientErrors = [];
    
  5850.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  5851.       onRecoverableError(error) {
    
  5852.         reportedClientErrors.push(error);
    
  5853.       },
    
  5854.     });
    
  5855.     await waitForAll([]);
    
  5856.     expect(getVisibleChildren(container)).toEqual('Oops!');
    
  5857.     expect(reportedClientErrors.length).toBe(1);
    
  5858.     if (__DEV__) {
    
  5859.       expect(reportedClientErrors[0].message).toBe('Oops!');
    
  5860.     } else {
    
  5861.       expect(reportedClientErrors[0].message).toBe(
    
  5862.         'The server could not finish this Suspense boundary, likely due to ' +
    
  5863.           'an error during server rendering. Switched to client rendering.',
    
  5864.       );
    
  5865.     }
    
  5866.   });
    
  5867. 
    
  5868.   it("use a promise that's already been instrumented and resolved", async () => {
    
  5869.     const thenable = {
    
  5870.       status: 'fulfilled',
    
  5871.       value: 'Hi',
    
  5872.       then() {},
    
  5873.     };
    
  5874. 
    
  5875.     // This will never suspend because the thenable already resolved
    
  5876.     function App() {
    
  5877.       return use(thenable);
    
  5878.     }
    
  5879. 
    
  5880.     await act(() => {
    
  5881.       const {pipe} = renderToPipeableStream(<App />);
    
  5882.       pipe(writable);
    
  5883.     });
    
  5884.     expect(getVisibleChildren(container)).toEqual('Hi');
    
  5885. 
    
  5886.     ReactDOMClient.hydrateRoot(container, <App />);
    
  5887.     await waitForAll([]);
    
  5888.     expect(getVisibleChildren(container)).toEqual('Hi');
    
  5889.   });
    
  5890. 
    
  5891.   it('unwraps thenable that fulfills synchronously without suspending', async () => {
    
  5892.     function App() {
    
  5893.       const thenable = {
    
  5894.         then(resolve) {
    
  5895.           // This thenable immediately resolves, synchronously, without waiting
    
  5896.           // a microtask.
    
  5897.           resolve('Hi');
    
  5898.         },
    
  5899.       };
    
  5900.       try {
    
  5901.         return <Text text={use(thenable)} />;
    
  5902.       } catch {
    
  5903.         throw new Error(
    
  5904.           '`use` should not suspend because the thenable resolved synchronously.',
    
  5905.         );
    
  5906.       }
    
  5907.     }
    
  5908.     // Because the thenable resolves synchronously, we should be able to finish
    
  5909.     // rendering synchronously, with no fallback.
    
  5910.     await act(() => {
    
  5911.       const {pipe} = renderToPipeableStream(<App />);
    
  5912.       pipe(writable);
    
  5913.     });
    
  5914.     expect(getVisibleChildren(container)).toEqual('Hi');
    
  5915.   });
    
  5916. 
    
  5917.   it('promise as node', async () => {
    
  5918.     const promise = Promise.resolve('Hi');
    
  5919.     await act(async () => {
    
  5920.       const {pipe} = renderToPipeableStream(promise);
    
  5921.       pipe(writable);
    
  5922.     });
    
  5923. 
    
  5924.     // TODO: The `act` implementation in this file doesn't unwrap microtasks
    
  5925.     // automatically. We can't use the same `act` we use for Fiber tests
    
  5926.     // because that relies on the mock Scheduler. Doesn't affect any public
    
  5927.     // API but we might want to fix this for our own internal tests.
    
  5928.     await act(async () => {
    
  5929.       await promise;
    
  5930.     });
    
  5931. 
    
  5932.     expect(getVisibleChildren(container)).toEqual('Hi');
    
  5933.   });
    
  5934. 
    
  5935.   it('context as node', async () => {
    
  5936.     const Context = React.createContext('Hi');
    
  5937.     await act(async () => {
    
  5938.       const {pipe} = renderToPipeableStream(Context);
    
  5939.       pipe(writable);
    
  5940.     });
    
  5941.     expect(getVisibleChildren(container)).toEqual('Hi');
    
  5942.   });
    
  5943. 
    
  5944.   it('recursive Usable as node', async () => {
    
  5945.     const Context = React.createContext('Hi');
    
  5946.     const promiseForContext = Promise.resolve(Context);
    
  5947.     await act(async () => {
    
  5948.       const {pipe} = renderToPipeableStream(promiseForContext);
    
  5949.       pipe(writable);
    
  5950.     });
    
  5951. 
    
  5952.     // TODO: The `act` implementation in this file doesn't unwrap microtasks
    
  5953.     // automatically. We can't use the same `act` we use for Fiber tests
    
  5954.     // because that relies on the mock Scheduler. Doesn't affect any public
    
  5955.     // API but we might want to fix this for our own internal tests.
    
  5956.     await act(async () => {
    
  5957.       await promiseForContext;
    
  5958.     });
    
  5959. 
    
  5960.     expect(getVisibleChildren(container)).toEqual('Hi');
    
  5961.   });
    
  5962. 
    
  5963.   // @gate enableFormActions
    
  5964.   // @gate enableAsyncActions
    
  5965.   it('useFormState hydrates without a mismatch', async () => {
    
  5966.     // This is testing an implementation detail: useFormState emits comment
    
  5967.     // nodes into the SSR stream, so this checks that they are handled correctly
    
  5968.     // during hydration.
    
  5969. 
    
  5970.     async function action(state) {
    
  5971.       return state;
    
  5972.     }
    
  5973. 
    
  5974.     const childRef = React.createRef(null);
    
  5975.     function Form() {
    
  5976.       const [state] = useFormState(action, 0);
    
  5977.       const text = `Child: ${state}`;
    
  5978.       return (
    
  5979.         <div id="child" ref={childRef}>
    
  5980.           {text}
    
  5981.         </div>
    
  5982.       );
    
  5983.     }
    
  5984. 
    
  5985.     function App() {
    
  5986.       return (
    
  5987.         <div>
    
  5988.           <div>
    
  5989.             <Form />
    
  5990.           </div>
    
  5991.           <span>Sibling</span>
    
  5992.         </div>
    
  5993.       );
    
  5994.     }
    
  5995. 
    
  5996.     await act(() => {
    
  5997.       const {pipe} = renderToPipeableStream(<App />);
    
  5998.       pipe(writable);
    
  5999.     });
    
  6000.     expect(getVisibleChildren(container)).toEqual(
    
  6001.       <div>
    
  6002.         <div>
    
  6003.           <div id="child">Child: 0</div>
    
  6004.         </div>
    
  6005.         <span>Sibling</span>
    
  6006.       </div>,
    
  6007.     );
    
  6008.     const child = document.getElementById('child');
    
  6009. 
    
  6010.     // Confirm that it hydrates correctly
    
  6011.     await clientAct(() => {
    
  6012.       ReactDOMClient.hydrateRoot(container, <App />);
    
  6013.     });
    
  6014.     expect(childRef.current).toBe(child);
    
  6015.   });
    
  6016. 
    
  6017.   // @gate enableFormActions
    
  6018.   // @gate enableAsyncActions
    
  6019.   it("useFormState hydrates without a mismatch if there's a render phase update", async () => {
    
  6020.     async function action(state) {
    
  6021.       return state;
    
  6022.     }
    
  6023. 
    
  6024.     const childRef = React.createRef(null);
    
  6025.     function Form() {
    
  6026.       const [localState, setLocalState] = React.useState(0);
    
  6027.       if (localState < 3) {
    
  6028.         setLocalState(localState + 1);
    
  6029.       }
    
  6030. 
    
  6031.       // Because of the render phase update above, this component is evaluated
    
  6032.       // multiple times (even during SSR), but it should only emit a single
    
  6033.       // marker per useFormState instance.
    
  6034.       const [formState] = useFormState(action, 0);
    
  6035.       const text = `${readText('Child')}:${formState}:${localState}`;
    
  6036.       return (
    
  6037.         <div id="child" ref={childRef}>
    
  6038.           {text}
    
  6039.         </div>
    
  6040.       );
    
  6041.     }
    
  6042. 
    
  6043.     function App() {
    
  6044.       return (
    
  6045.         <div>
    
  6046.           <Suspense fallback="Loading...">
    
  6047.             <Form />
    
  6048.           </Suspense>
    
  6049.           <span>Sibling</span>
    
  6050.         </div>
    
  6051.       );
    
  6052.     }
    
  6053. 
    
  6054.     await act(() => {
    
  6055.       const {pipe} = renderToPipeableStream(<App />);
    
  6056.       pipe(writable);
    
  6057.     });
    
  6058.     expect(getVisibleChildren(container)).toEqual(
    
  6059.       <div>
    
  6060.         Loading...<span>Sibling</span>
    
  6061.       </div>,
    
  6062.     );
    
  6063. 
    
  6064.     await act(() => resolveText('Child'));
    
  6065.     expect(getVisibleChildren(container)).toEqual(
    
  6066.       <div>
    
  6067.         <div id="child">Child:0:3</div>
    
  6068.         <span>Sibling</span>
    
  6069.       </div>,
    
  6070.     );
    
  6071.     const child = document.getElementById('child');
    
  6072. 
    
  6073.     // Confirm that it hydrates correctly
    
  6074.     await clientAct(() => {
    
  6075.       ReactDOMClient.hydrateRoot(container, <App />);
    
  6076.     });
    
  6077.     expect(childRef.current).toBe(child);
    
  6078.   });
    
  6079. 
    
  6080.   describe('useEffectEvent', () => {
    
  6081.     // @gate enableUseEffectEventHook
    
  6082.     it('can server render a component with useEffectEvent', async () => {
    
  6083.       const ref = React.createRef();
    
  6084.       function App() {
    
  6085.         const [count, setCount] = React.useState(0);
    
  6086.         const onClick = React.experimental_useEffectEvent(() => {
    
  6087.           setCount(c => c + 1);
    
  6088.         });
    
  6089.         return (
    
  6090.           <button ref={ref} onClick={() => onClick()}>
    
  6091.             {count}
    
  6092.           </button>
    
  6093.         );
    
  6094.       }
    
  6095.       await act(() => {
    
  6096.         const {pipe} = renderToPipeableStream(<App />);
    
  6097.         pipe(writable);
    
  6098.       });
    
  6099.       expect(getVisibleChildren(container)).toEqual(<button>0</button>);
    
  6100. 
    
  6101.       ReactDOMClient.hydrateRoot(container, <App />);
    
  6102.       await waitForAll([]);
    
  6103.       expect(getVisibleChildren(container)).toEqual(<button>0</button>);
    
  6104. 
    
  6105.       ref.current.dispatchEvent(
    
  6106.         new window.MouseEvent('click', {bubbles: true}),
    
  6107.       );
    
  6108.       await jest.runAllTimers();
    
  6109.       expect(getVisibleChildren(container)).toEqual(<button>1</button>);
    
  6110.     });
    
  6111. 
    
  6112.     // @gate enableUseEffectEventHook
    
  6113.     it('throws if useEffectEvent is called during a server render', async () => {
    
  6114.       const logs = [];
    
  6115.       function App() {
    
  6116.         const onRender = React.experimental_useEffectEvent(() => {
    
  6117.           logs.push('rendered');
    
  6118.         });
    
  6119.         onRender();
    
  6120.         return <p>Hello</p>;
    
  6121.       }
    
  6122. 
    
  6123.       const reportedServerErrors = [];
    
  6124.       let caughtError;
    
  6125.       try {
    
  6126.         await act(() => {
    
  6127.           const {pipe} = renderToPipeableStream(<App />, {
    
  6128.             onError(e) {
    
  6129.               reportedServerErrors.push(e);
    
  6130.             },
    
  6131.           });
    
  6132.           pipe(writable);
    
  6133.         });
    
  6134.       } catch (err) {
    
  6135.         caughtError = err;
    
  6136.       }
    
  6137.       expect(logs).toEqual([]);
    
  6138.       expect(caughtError.message).toContain(
    
  6139.         "A function wrapped in useEffectEvent can't be called during rendering.",
    
  6140.       );
    
  6141.       expect(reportedServerErrors).toEqual([caughtError]);
    
  6142.     });
    
  6143. 
    
  6144.     // @gate enableUseEffectEventHook
    
  6145.     it('does not guarantee useEffectEvent return values during server rendering are distinct', async () => {
    
  6146.       function App() {
    
  6147.         const onClick1 = React.experimental_useEffectEvent(() => {});
    
  6148.         const onClick2 = React.experimental_useEffectEvent(() => {});
    
  6149.         if (onClick1 === onClick2) {
    
  6150.           return <div />;
    
  6151.         } else {
    
  6152.           return <span />;
    
  6153.         }
    
  6154.       }
    
  6155.       await act(() => {
    
  6156.         const {pipe} = renderToPipeableStream(<App />);
    
  6157.         pipe(writable);
    
  6158.       });
    
  6159.       expect(getVisibleChildren(container)).toEqual(<div />);
    
  6160. 
    
  6161.       const errors = [];
    
  6162.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  6163.         onRecoverableError(error) {
    
  6164.           errors.push(error);
    
  6165.         },
    
  6166.       });
    
  6167.       await expect(async () => {
    
  6168.         await waitForAll([]);
    
  6169.       }).toErrorDev(
    
  6170.         [
    
  6171.           'Expected server HTML to contain a matching <span> in <div>',
    
  6172.           'An error occurred during hydration',
    
  6173.         ],
    
  6174.         {withoutStack: 1},
    
  6175.       );
    
  6176.       expect(errors.length).toEqual(2);
    
  6177.       expect(getVisibleChildren(container)).toEqual(<span />);
    
  6178.     });
    
  6179.   });
    
  6180. 
    
  6181.   it('can render scripts with simple children', async () => {
    
  6182.     await act(async () => {
    
  6183.       const {pipe} = renderToPipeableStream(
    
  6184.         <html>
    
  6185.           <body>
    
  6186.             <script>{'try { foo() } catch (e) {} ;'}</script>
    
  6187.           </body>
    
  6188.         </html>,
    
  6189.       );
    
  6190.       pipe(writable);
    
  6191.     });
    
  6192. 
    
  6193.     expect(document.documentElement.outerHTML).toEqual(
    
  6194.       '<html><head></head><body><script>try { foo() } catch (e) {} ;</script></body></html>',
    
  6195.     );
    
  6196.   });
    
  6197. 
    
  6198.   // @gate enableFloat
    
  6199.   it('warns if script has complex children', async () => {
    
  6200.     function MyScript() {
    
  6201.       return 'bar();';
    
  6202.     }
    
  6203.     const originalConsoleError = console.error;
    
  6204.     const mockError = jest.fn();
    
  6205.     console.error = (...args) => {
    
  6206.       mockError(...args.map(normalizeCodeLocInfo));
    
  6207.     };
    
  6208. 
    
  6209.     try {
    
  6210.       await act(async () => {
    
  6211.         const {pipe} = renderToPipeableStream(
    
  6212.           <html>
    
  6213.             <body>
    
  6214.               <script>{2}</script>
    
  6215.               <script>
    
  6216.                 {[
    
  6217.                   'try { foo() } catch (e) {} ;',
    
  6218.                   'try { bar() } catch (e) {} ;',
    
  6219.                 ]}
    
  6220.               </script>
    
  6221.               <script>
    
  6222.                 <MyScript />
    
  6223.               </script>
    
  6224.             </body>
    
  6225.           </html>,
    
  6226.         );
    
  6227.         pipe(writable);
    
  6228.       });
    
  6229. 
    
  6230.       if (__DEV__) {
    
  6231.         expect(mockError.mock.calls.length).toBe(3);
    
  6232.         expect(mockError.mock.calls[0]).toEqual([
    
  6233.           'Warning: A script element was rendered with %s. If script element has children it must be a single string. Consider using dangerouslySetInnerHTML or passing a plain string as children.%s',
    
  6234.           'a number for children',
    
  6235.           componentStack(['script', 'body', 'html']),
    
  6236.         ]);
    
  6237.         expect(mockError.mock.calls[1]).toEqual([
    
  6238.           'Warning: A script element was rendered with %s. If script element has children it must be a single string. Consider using dangerouslySetInnerHTML or passing a plain string as children.%s',
    
  6239.           'an array for children',
    
  6240.           componentStack(['script', 'body', 'html']),
    
  6241.         ]);
    
  6242.         expect(mockError.mock.calls[2]).toEqual([
    
  6243.           'Warning: A script element was rendered with %s. If script element has children it must be a single string. Consider using dangerouslySetInnerHTML or passing a plain string as children.%s',
    
  6244.           'something unexpected for children',
    
  6245.           componentStack(['script', 'body', 'html']),
    
  6246.         ]);
    
  6247.       } else {
    
  6248.         expect(mockError.mock.calls.length).toBe(0);
    
  6249.       }
    
  6250.     } finally {
    
  6251.       console.error = originalConsoleError;
    
  6252.     }
    
  6253.   });
    
  6254. 
    
  6255.   // @gate enablePostpone
    
  6256.   it('client renders postponed boundaries without erroring', async () => {
    
  6257.     function Postponed({isClient}) {
    
  6258.       if (!isClient) {
    
  6259.         React.unstable_postpone('testing postpone');
    
  6260.       }
    
  6261.       return 'client only';
    
  6262.     }
    
  6263. 
    
  6264.     function App({isClient}) {
    
  6265.       return (
    
  6266.         <div>
    
  6267.           <Suspense fallback={'loading...'}>
    
  6268.             <Postponed isClient={isClient} />
    
  6269.           </Suspense>
    
  6270.         </div>
    
  6271.       );
    
  6272.     }
    
  6273. 
    
  6274.     const errors = [];
    
  6275. 
    
  6276.     await act(() => {
    
  6277.       const {pipe} = renderToPipeableStream(<App isClient={false} />, {
    
  6278.         onError(error) {
    
  6279.           errors.push(error.message);
    
  6280.         },
    
  6281.       });
    
  6282.       pipe(writable);
    
  6283.     });
    
  6284. 
    
  6285.     expect(getVisibleChildren(container)).toEqual(<div>loading...</div>);
    
  6286. 
    
  6287.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  6288.       onRecoverableError(error) {
    
  6289.         errors.push(error.message);
    
  6290.       },
    
  6291.     });
    
  6292.     await waitForAll([]);
    
  6293.     // Postponing should not be logged as a recoverable error since it's intentional.
    
  6294.     expect(errors).toEqual([]);
    
  6295.     expect(getVisibleChildren(container)).toEqual(<div>client only</div>);
    
  6296.   });
    
  6297. 
    
  6298.   // @gate enablePostpone
    
  6299.   it('errors if trying to postpone outside a Suspense boundary', async () => {
    
  6300.     function Postponed() {
    
  6301.       React.unstable_postpone('testing postpone');
    
  6302.       return 'client only';
    
  6303.     }
    
  6304. 
    
  6305.     function App() {
    
  6306.       return (
    
  6307.         <div>
    
  6308.           <Postponed />
    
  6309.         </div>
    
  6310.       );
    
  6311.     }
    
  6312. 
    
  6313.     const errors = [];
    
  6314.     const fatalErrors = [];
    
  6315.     const postponed = [];
    
  6316.     let written = false;
    
  6317. 
    
  6318.     const testWritable = new Stream.Writable();
    
  6319.     testWritable._write = (chunk, encoding, next) => {
    
  6320.       written = true;
    
  6321.     };
    
  6322. 
    
  6323.     await act(() => {
    
  6324.       const {pipe} = renderToPipeableStream(<App />, {
    
  6325.         onPostpone(reason) {
    
  6326.           postponed.push(reason);
    
  6327.         },
    
  6328.         onError(error) {
    
  6329.           errors.push(error.message);
    
  6330.         },
    
  6331.         onShellError(error) {
    
  6332.           fatalErrors.push(error.message);
    
  6333.         },
    
  6334.       });
    
  6335.       pipe(testWritable);
    
  6336.     });
    
  6337. 
    
  6338.     expect(written).toBe(false);
    
  6339.     // Postponing is not logged as an error but as a postponed reason.
    
  6340.     expect(errors).toEqual([]);
    
  6341.     expect(postponed).toEqual(['testing postpone']);
    
  6342.     // However, it does error the shell.
    
  6343.     expect(fatalErrors).toEqual(['testing postpone']);
    
  6344.   });
    
  6345. 
    
  6346.   // @gate enablePostpone
    
  6347.   it('can postpone in a fallback', async () => {
    
  6348.     function Postponed({isClient}) {
    
  6349.       if (!isClient) {
    
  6350.         React.unstable_postpone('testing postpone');
    
  6351.       }
    
  6352.       return 'loading...';
    
  6353.     }
    
  6354. 
    
  6355.     const lazyText = React.lazy(async () => {
    
  6356.       await 0; // causes the fallback to start work
    
  6357.       return {default: 'Hello'};
    
  6358.     });
    
  6359. 
    
  6360.     function App({isClient}) {
    
  6361.       return (
    
  6362.         <div>
    
  6363.           <Suspense fallback="Outer">
    
  6364.             <Suspense fallback={<Postponed isClient={isClient} />}>
    
  6365.               {lazyText}
    
  6366.             </Suspense>
    
  6367.           </Suspense>
    
  6368.         </div>
    
  6369.       );
    
  6370.     }
    
  6371. 
    
  6372.     const errors = [];
    
  6373. 
    
  6374.     await act(() => {
    
  6375.       const {pipe} = renderToPipeableStream(<App isClient={false} />, {
    
  6376.         onError(error) {
    
  6377.           errors.push(error.message);
    
  6378.         },
    
  6379.       });
    
  6380.       pipe(writable);
    
  6381.     });
    
  6382. 
    
  6383.     // TODO: This should actually be fully resolved because the value could eventually
    
  6384.     // resolve on the server even though the fallback couldn't so we should have been
    
  6385.     // able to render it.
    
  6386.     expect(getVisibleChildren(container)).toEqual(<div>Outer</div>);
    
  6387. 
    
  6388.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  6389.       onRecoverableError(error) {
    
  6390.         errors.push(error.message);
    
  6391.       },
    
  6392.     });
    
  6393.     await waitForAll([]);
    
  6394.     // Postponing should not be logged as a recoverable error since it's intentional.
    
  6395.     expect(errors).toEqual([]);
    
  6396.     expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  6397.   });
    
  6398. 
    
  6399.   it(
    
  6400.     'a transition that flows into a dehydrated boundary should not suspend ' +
    
  6401.       'if the boundary is showing a fallback',
    
  6402.     async () => {
    
  6403.       let setSearch;
    
  6404.       function App() {
    
  6405.         const [search, _setSearch] = React.useState('initial query');
    
  6406.         setSearch = _setSearch;
    
  6407.         return (
    
  6408.           <div>
    
  6409.             <div>{search}</div>
    
  6410.             <div>
    
  6411.               <Suspense fallback="Loading...">
    
  6412.                 <AsyncText text="Async" />
    
  6413.               </Suspense>
    
  6414.             </div>
    
  6415.           </div>
    
  6416.         );
    
  6417.       }
    
  6418. 
    
  6419.       // Render the initial HTML, which is showing a fallback.
    
  6420.       await act(() => {
    
  6421.         const {pipe} = renderToPipeableStream(<App />);
    
  6422.         pipe(writable);
    
  6423.       });
    
  6424. 
    
  6425.       // Start hydrating.
    
  6426.       await clientAct(() => {
    
  6427.         ReactDOMClient.hydrateRoot(container, <App />);
    
  6428.       });
    
  6429.       expect(getVisibleChildren(container)).toEqual(
    
  6430.         <div>
    
  6431.           <div>initial query</div>
    
  6432.           <div>Loading...</div>
    
  6433.         </div>,
    
  6434.       );
    
  6435. 
    
  6436.       // Before the HTML has streamed in, update the query. The part outside
    
  6437.       // the fallback should be allowed to finish.
    
  6438.       await clientAct(() => {
    
  6439.         React.startTransition(() => setSearch('updated query'));
    
  6440.       });
    
  6441.       expect(getVisibleChildren(container)).toEqual(
    
  6442.         <div>
    
  6443.           <div>updated query</div>
    
  6444.           <div>Loading...</div>
    
  6445.         </div>,
    
  6446.       );
    
  6447.     },
    
  6448.   );
    
  6449. 
    
  6450.   // @gate enablePostpone
    
  6451.   it('supports postponing in prerender and resuming later', async () => {
    
  6452.     let prerendering = true;
    
  6453.     function Postpone() {
    
  6454.       if (prerendering) {
    
  6455.         React.unstable_postpone();
    
  6456.       }
    
  6457.       return 'Hello';
    
  6458.     }
    
  6459. 
    
  6460.     function App() {
    
  6461.       return (
    
  6462.         <div>
    
  6463.           <Suspense fallback="Loading...">
    
  6464.             <Postpone />
    
  6465.           </Suspense>
    
  6466.         </div>
    
  6467.       );
    
  6468.     }
    
  6469. 
    
  6470.     const prerendered = await ReactDOMFizzStatic.prerenderToNodeStream(<App />);
    
  6471.     expect(prerendered.postponed).not.toBe(null);
    
  6472. 
    
  6473.     prerendering = false;
    
  6474. 
    
  6475.     const resumed = ReactDOMFizzServer.resumeToPipeableStream(
    
  6476.       <App />,
    
  6477.       JSON.parse(JSON.stringify(prerendered.postponed)),
    
  6478.     );
    
  6479. 
    
  6480.     // Create a separate stream so it doesn't close the writable. I.e. simple concat.
    
  6481.     const preludeWritable = new Stream.PassThrough();
    
  6482.     preludeWritable.setEncoding('utf8');
    
  6483.     preludeWritable.on('data', chunk => {
    
  6484.       writable.write(chunk);
    
  6485.     });
    
  6486. 
    
  6487.     await act(() => {
    
  6488.       prerendered.prelude.pipe(preludeWritable);
    
  6489.     });
    
  6490. 
    
  6491.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  6492. 
    
  6493.     await act(() => {
    
  6494.       resumed.pipe(writable);
    
  6495.     });
    
  6496. 
    
  6497.     expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
    
  6498.   });
    
  6499. 
    
  6500.   // @gate enablePostpone
    
  6501.   it('client renders a component if it errors during resuming', async () => {
    
  6502.     let prerendering = true;
    
  6503.     let ssr = true;
    
  6504.     function PostponeAndError() {
    
  6505.       if (prerendering) {
    
  6506.         React.unstable_postpone();
    
  6507.       }
    
  6508.       if (ssr) {
    
  6509.         throw new Error('server error');
    
  6510.       }
    
  6511.       return 'Hello';
    
  6512.     }
    
  6513. 
    
  6514.     function Postpone() {
    
  6515.       if (prerendering) {
    
  6516.         React.unstable_postpone();
    
  6517.       }
    
  6518.       return 'Hello';
    
  6519.     }
    
  6520. 
    
  6521.     const lazyPostponeAndError = React.lazy(async () => {
    
  6522.       return {default: <PostponeAndError />};
    
  6523.     });
    
  6524. 
    
  6525.     function ReplayError() {
    
  6526.       if (prerendering) {
    
  6527.         return <Postpone />;
    
  6528.       }
    
  6529.       if (ssr) {
    
  6530.         throw new Error('replay error');
    
  6531.       }
    
  6532.       return 'Hello';
    
  6533.     }
    
  6534. 
    
  6535.     function App() {
    
  6536.       return (
    
  6537.         <div>
    
  6538.           <Suspense fallback="Loading1">
    
  6539.             <PostponeAndError />
    
  6540.           </Suspense>
    
  6541.           <Suspense fallback="Loading2">
    
  6542.             <Postpone />
    
  6543.             <Suspense fallback="Loading3">{lazyPostponeAndError}</Suspense>
    
  6544.           </Suspense>
    
  6545.           <Suspense fallback="Loading4">
    
  6546.             <ReplayError />
    
  6547.           </Suspense>
    
  6548.         </div>
    
  6549.       );
    
  6550.     }
    
  6551. 
    
  6552.     const prerenderErrors = [];
    
  6553.     const prerendered = await ReactDOMFizzStatic.prerenderToNodeStream(
    
  6554.       <App />,
    
  6555.       {
    
  6556.         onError(x) {
    
  6557.           prerenderErrors.push(x.message);
    
  6558.         },
    
  6559.       },
    
  6560.     );
    
  6561.     expect(prerendered.postponed).not.toBe(null);
    
  6562. 
    
  6563.     prerendering = false;
    
  6564. 
    
  6565.     const ssrErrors = [];
    
  6566. 
    
  6567.     const resumed = ReactDOMFizzServer.resumeToPipeableStream(
    
  6568.       <App />,
    
  6569.       JSON.parse(JSON.stringify(prerendered.postponed)),
    
  6570.       {
    
  6571.         onError(x) {
    
  6572.           ssrErrors.push(x.message);
    
  6573.         },
    
  6574.       },
    
  6575.     );
    
  6576. 
    
  6577.     // Create a separate stream so it doesn't close the writable. I.e. simple concat.
    
  6578.     const preludeWritable = new Stream.PassThrough();
    
  6579.     preludeWritable.setEncoding('utf8');
    
  6580.     preludeWritable.on('data', chunk => {
    
  6581.       writable.write(chunk);
    
  6582.     });
    
  6583. 
    
  6584.     await act(() => {
    
  6585.       prerendered.prelude.pipe(preludeWritable);
    
  6586.     });
    
  6587. 
    
  6588.     expect(getVisibleChildren(container)).toEqual(
    
  6589.       <div>
    
  6590.         {'Loading1'}
    
  6591.         {'Loading2'}
    
  6592.         {'Loading4'}
    
  6593.       </div>,
    
  6594.     );
    
  6595. 
    
  6596.     await act(() => {
    
  6597.       resumed.pipe(writable);
    
  6598.     });
    
  6599. 
    
  6600.     expect(prerenderErrors).toEqual([]);
    
  6601. 
    
  6602.     expect(ssrErrors).toEqual(['server error', 'server error', 'replay error']);
    
  6603. 
    
  6604.     // Still loading...
    
  6605.     expect(getVisibleChildren(container)).toEqual(
    
  6606.       <div>
    
  6607.         {'Loading1'}
    
  6608.         {'Hello'}
    
  6609.         {'Loading3'}
    
  6610.         {'Loading4'}
    
  6611.       </div>,
    
  6612.     );
    
  6613. 
    
  6614.     const recoverableErrors = [];
    
  6615. 
    
  6616.     ssr = false;
    
  6617. 
    
  6618.     await clientAct(() => {
    
  6619.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  6620.         onRecoverableError(x) {
    
  6621.           recoverableErrors.push(x.message);
    
  6622.         },
    
  6623.       });
    
  6624.     });
    
  6625. 
    
  6626.     expect(recoverableErrors).toEqual(
    
  6627.       __DEV__
    
  6628.         ? ['server error', 'replay error', 'server error']
    
  6629.         : [
    
  6630.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6631.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6632.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6633.           ],
    
  6634.     );
    
  6635.     expect(getVisibleChildren(container)).toEqual(
    
  6636.       <div>
    
  6637.         {'Hello'}
    
  6638.         {'Hello'}
    
  6639.         {'Hello'}
    
  6640.         {'Hello'}
    
  6641.       </div>,
    
  6642.     );
    
  6643.   });
    
  6644. 
    
  6645.   // @gate enablePostpone
    
  6646.   it('client renders a component if we abort before resuming', async () => {
    
  6647.     let prerendering = true;
    
  6648.     let ssr = true;
    
  6649.     const promise = new Promise(() => {});
    
  6650.     function PostponeAndSuspend() {
    
  6651.       if (prerendering) {
    
  6652.         React.unstable_postpone();
    
  6653.       }
    
  6654.       if (ssr) {
    
  6655.         React.use(promise);
    
  6656.       }
    
  6657.       return 'Hello';
    
  6658.     }
    
  6659. 
    
  6660.     function Postpone() {
    
  6661.       if (prerendering) {
    
  6662.         React.unstable_postpone();
    
  6663.       }
    
  6664.       return 'Hello';
    
  6665.     }
    
  6666. 
    
  6667.     function DelayedBoundary() {
    
  6668.       if (!prerendering && ssr) {
    
  6669.         // We delay discovery of the boundary so we can abort before finding it.
    
  6670.         React.use(promise);
    
  6671.       }
    
  6672.       return (
    
  6673.         <Suspense fallback="Loading3">
    
  6674.           <Postpone />
    
  6675.         </Suspense>
    
  6676.       );
    
  6677.     }
    
  6678. 
    
  6679.     function App() {
    
  6680.       return (
    
  6681.         <div>
    
  6682.           <Suspense fallback="Loading1">
    
  6683.             <PostponeAndSuspend />
    
  6684.           </Suspense>
    
  6685.           <Suspense fallback="Loading2">
    
  6686.             <Postpone />
    
  6687.           </Suspense>
    
  6688.           <Suspense fallback="Not used">
    
  6689.             <DelayedBoundary />
    
  6690.           </Suspense>
    
  6691.         </div>
    
  6692.       );
    
  6693.     }
    
  6694. 
    
  6695.     const prerenderErrors = [];
    
  6696.     const prerendered = await ReactDOMFizzStatic.prerenderToNodeStream(
    
  6697.       <App />,
    
  6698.       {
    
  6699.         onError(x) {
    
  6700.           prerenderErrors.push(x.message);
    
  6701.         },
    
  6702.       },
    
  6703.     );
    
  6704.     expect(prerendered.postponed).not.toBe(null);
    
  6705. 
    
  6706.     prerendering = false;
    
  6707. 
    
  6708.     const ssrErrors = [];
    
  6709. 
    
  6710.     const resumed = ReactDOMFizzServer.resumeToPipeableStream(
    
  6711.       <App />,
    
  6712.       JSON.parse(JSON.stringify(prerendered.postponed)),
    
  6713.       {
    
  6714.         onError(x) {
    
  6715.           ssrErrors.push(x.message);
    
  6716.         },
    
  6717.       },
    
  6718.     );
    
  6719. 
    
  6720.     // Create a separate stream so it doesn't close the writable. I.e. simple concat.
    
  6721.     const preludeWritable = new Stream.PassThrough();
    
  6722.     preludeWritable.setEncoding('utf8');
    
  6723.     preludeWritable.on('data', chunk => {
    
  6724.       writable.write(chunk);
    
  6725.     });
    
  6726. 
    
  6727.     await act(() => {
    
  6728.       prerendered.prelude.pipe(preludeWritable);
    
  6729.     });
    
  6730. 
    
  6731.     expect(getVisibleChildren(container)).toEqual(
    
  6732.       <div>
    
  6733.         {'Loading1'}
    
  6734.         {'Loading2'}
    
  6735.         {'Loading3'}
    
  6736.       </div>,
    
  6737.     );
    
  6738. 
    
  6739.     await act(() => {
    
  6740.       resumed.pipe(writable);
    
  6741.     });
    
  6742. 
    
  6743.     const recoverableErrors = [];
    
  6744. 
    
  6745.     ssr = false;
    
  6746. 
    
  6747.     await clientAct(() => {
    
  6748.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  6749.         onRecoverableError(x) {
    
  6750.           recoverableErrors.push(x.message);
    
  6751.         },
    
  6752.       });
    
  6753.     });
    
  6754. 
    
  6755.     expect(recoverableErrors).toEqual([]);
    
  6756.     expect(prerenderErrors).toEqual([]);
    
  6757.     expect(ssrErrors).toEqual([]);
    
  6758. 
    
  6759.     // Still loading...
    
  6760.     expect(getVisibleChildren(container)).toEqual(
    
  6761.       <div>
    
  6762.         {'Loading1'}
    
  6763.         {'Hello'}
    
  6764.         {'Loading3'}
    
  6765.       </div>,
    
  6766.     );
    
  6767. 
    
  6768.     await clientAct(async () => {
    
  6769.       await act(() => {
    
  6770.         resumed.abort(new Error('aborted'));
    
  6771.       });
    
  6772.     });
    
  6773. 
    
  6774.     expect(getVisibleChildren(container)).toEqual(
    
  6775.       <div>
    
  6776.         {'Hello'}
    
  6777.         {'Hello'}
    
  6778.         {'Hello'}
    
  6779.       </div>,
    
  6780.     );
    
  6781. 
    
  6782.     expect(prerenderErrors).toEqual([]);
    
  6783.     expect(ssrErrors).toEqual(['aborted', 'aborted']);
    
  6784.     expect(recoverableErrors).toEqual(
    
  6785.       __DEV__
    
  6786.         ? [
    
  6787.             'The server did not finish this Suspense boundary: aborted',
    
  6788.             'The server did not finish this Suspense boundary: aborted',
    
  6789.           ]
    
  6790.         : [
    
  6791.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6792.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6793.           ],
    
  6794.     );
    
  6795.   });
    
  6796. 
    
  6797.   // @gate enablePostpone
    
  6798.   it('client renders remaining boundaries below the error in shell', async () => {
    
  6799.     let prerendering = true;
    
  6800.     let ssr = true;
    
  6801.     function Postpone() {
    
  6802.       if (prerendering) {
    
  6803.         React.unstable_postpone();
    
  6804.       }
    
  6805.       return 'Hello';
    
  6806.     }
    
  6807. 
    
  6808.     function ReplayError({children}) {
    
  6809.       if (!prerendering && ssr) {
    
  6810.         throw new Error('replay error');
    
  6811.       }
    
  6812.       return children;
    
  6813.     }
    
  6814. 
    
  6815.     function App() {
    
  6816.       return (
    
  6817.         <div>
    
  6818.           <div>
    
  6819.             <Suspense fallback="Loading1">
    
  6820.               <Postpone />
    
  6821.             </Suspense>
    
  6822.             <ReplayError>
    
  6823.               <Suspense fallback="Loading2">
    
  6824.                 <Postpone />
    
  6825.               </Suspense>
    
  6826.             </ReplayError>
    
  6827.             <Suspense fallback="Loading3">
    
  6828.               <Postpone />
    
  6829.             </Suspense>
    
  6830.           </div>
    
  6831.           <Suspense fallback="Not used">
    
  6832.             <div>
    
  6833.               <Suspense fallback="Loading4">
    
  6834.                 <Postpone />
    
  6835.               </Suspense>
    
  6836.             </div>
    
  6837.           </Suspense>
    
  6838.           <Suspense fallback="Loading5">
    
  6839.             <Postpone />
    
  6840.             <ReplayError>
    
  6841.               <Suspense fallback="Loading6">
    
  6842.                 <Postpone />
    
  6843.               </Suspense>
    
  6844.             </ReplayError>
    
  6845.           </Suspense>
    
  6846.         </div>
    
  6847.       );
    
  6848.     }
    
  6849. 
    
  6850.     const prerenderErrors = [];
    
  6851.     const prerendered = await ReactDOMFizzStatic.prerenderToNodeStream(
    
  6852.       <App />,
    
  6853.       {
    
  6854.         onError(x) {
    
  6855.           prerenderErrors.push(x.message);
    
  6856.         },
    
  6857.       },
    
  6858.     );
    
  6859.     expect(prerendered.postponed).not.toBe(null);
    
  6860. 
    
  6861.     prerendering = false;
    
  6862. 
    
  6863.     const ssrErrors = [];
    
  6864. 
    
  6865.     const resumed = ReactDOMFizzServer.resumeToPipeableStream(
    
  6866.       <App />,
    
  6867.       JSON.parse(JSON.stringify(prerendered.postponed)),
    
  6868.       {
    
  6869.         onError(x) {
    
  6870.           ssrErrors.push(x.message);
    
  6871.         },
    
  6872.       },
    
  6873.     );
    
  6874. 
    
  6875.     // Create a separate stream so it doesn't close the writable. I.e. simple concat.
    
  6876.     const preludeWritable = new Stream.PassThrough();
    
  6877.     preludeWritable.setEncoding('utf8');
    
  6878.     preludeWritable.on('data', chunk => {
    
  6879.       writable.write(chunk);
    
  6880.     });
    
  6881. 
    
  6882.     await act(() => {
    
  6883.       prerendered.prelude.pipe(preludeWritable);
    
  6884.     });
    
  6885. 
    
  6886.     expect(getVisibleChildren(container)).toEqual(
    
  6887.       <div>
    
  6888.         <div>
    
  6889.           {'Loading1'}
    
  6890.           {'Loading2'}
    
  6891.           {'Loading3'}
    
  6892.         </div>
    
  6893.         <div>{'Loading4'}</div>
    
  6894.         {'Loading5'}
    
  6895.       </div>,
    
  6896.     );
    
  6897. 
    
  6898.     await act(() => {
    
  6899.       resumed.pipe(writable);
    
  6900.     });
    
  6901. 
    
  6902.     expect(getVisibleChildren(container)).toEqual(
    
  6903.       <div>
    
  6904.         <div>
    
  6905.           {'Hello' /* This was matched and completed before the error */}
    
  6906.           {
    
  6907.             'Loading2' /* This will be client rendered because its parent errored during replay */
    
  6908.           }
    
  6909.           {
    
  6910.             'Hello' /* This should be renderable since we matched which previous sibling errored */
    
  6911.           }
    
  6912.         </div>
    
  6913.         <div>
    
  6914.           {
    
  6915.             'Hello' /* This should be able to resume because it's in a different parent. */
    
  6916.           }
    
  6917.         </div>
    
  6918.         {'Hello'}
    
  6919.         {'Loading6' /* The parent could resolve even if the child didn't */}
    
  6920.       </div>,
    
  6921.     );
    
  6922. 
    
  6923.     const recoverableErrors = [];
    
  6924. 
    
  6925.     ssr = false;
    
  6926. 
    
  6927.     await clientAct(() => {
    
  6928.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  6929.         onRecoverableError(x) {
    
  6930.           recoverableErrors.push(x.message);
    
  6931.         },
    
  6932.       });
    
  6933.     });
    
  6934. 
    
  6935.     expect(getVisibleChildren(container)).toEqual(
    
  6936.       <div>
    
  6937.         <div>
    
  6938.           {'Hello'}
    
  6939.           {'Hello'}
    
  6940.           {'Hello'}
    
  6941.         </div>
    
  6942.         <div>{'Hello'}</div>
    
  6943.         {'Hello'}
    
  6944.         {'Hello'}
    
  6945.       </div>,
    
  6946.     );
    
  6947. 
    
  6948.     // We should've logged once for each boundary that this affected.
    
  6949.     expect(prerenderErrors).toEqual([]);
    
  6950.     expect(ssrErrors).toEqual([
    
  6951.       // This error triggered in two replay components.
    
  6952.       'replay error',
    
  6953.       'replay error',
    
  6954.     ]);
    
  6955.     expect(recoverableErrors).toEqual(
    
  6956.       // It surfaced in two different suspense boundaries.
    
  6957.       __DEV__
    
  6958.         ? [
    
  6959.             'The server did not finish this Suspense boundary: replay error',
    
  6960.             'The server did not finish this Suspense boundary: replay error',
    
  6961.           ]
    
  6962.         : [
    
  6963.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6964.             'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',
    
  6965.           ],
    
  6966.     );
    
  6967.   });
    
  6968. 
    
  6969.   // @gate enablePostpone
    
  6970.   it('can postpone in fallback', async () => {
    
  6971.     let prerendering = true;
    
  6972.     function Postpone() {
    
  6973.       if (prerendering) {
    
  6974.         React.unstable_postpone();
    
  6975.       }
    
  6976.       return 'Hello';
    
  6977.     }
    
  6978. 
    
  6979.     let resolve;
    
  6980.     const promise = new Promise(r => (resolve = r));
    
  6981. 
    
  6982.     function PostponeAndDelay() {
    
  6983.       if (prerendering) {
    
  6984.         React.unstable_postpone();
    
  6985.       }
    
  6986.       return React.use(promise);
    
  6987.     }
    
  6988. 
    
  6989.     const Lazy = React.lazy(async () => {
    
  6990.       await 0;
    
  6991.       return {default: Postpone};
    
  6992.     });
    
  6993. 
    
  6994.     function App() {
    
  6995.       return (
    
  6996.         <div>
    
  6997.           <Suspense fallback="Outer">
    
  6998.             <Suspense fallback={<Postpone />}>
    
  6999.               <PostponeAndDelay /> World
    
  7000.             </Suspense>
    
  7001.             <Suspense fallback={<Postpone />}>
    
  7002.               <Lazy />
    
  7003.             </Suspense>
    
  7004.           </Suspense>
    
  7005.         </div>
    
  7006.       );
    
  7007.     }
    
  7008. 
    
  7009.     const prerendered = await ReactDOMFizzStatic.prerenderToNodeStream(<App />);
    
  7010.     expect(prerendered.postponed).not.toBe(null);
    
  7011. 
    
  7012.     prerendering = false;
    
  7013. 
    
  7014.     // Create a separate stream so it doesn't close the writable. I.e. simple concat.
    
  7015.     const preludeWritable = new Stream.PassThrough();
    
  7016.     preludeWritable.setEncoding('utf8');
    
  7017.     preludeWritable.on('data', chunk => {
    
  7018.       writable.write(chunk);
    
  7019.     });
    
  7020. 
    
  7021.     await act(() => {
    
  7022.       prerendered.prelude.pipe(preludeWritable);
    
  7023.     });
    
  7024. 
    
  7025.     const resumed = await ReactDOMFizzServer.resumeToPipeableStream(
    
  7026.       <App />,
    
  7027.       JSON.parse(JSON.stringify(prerendered.postponed)),
    
  7028.     );
    
  7029. 
    
  7030.     expect(getVisibleChildren(container)).toEqual(<div>Outer</div>);
    
  7031. 
    
  7032.     // Read what we've completed so far
    
  7033.     await act(() => {
    
  7034.       resumed.pipe(writable);
    
  7035.     });
    
  7036. 
    
  7037.     // Should have now resolved the postponed loading state, but not the promise
    
  7038.     expect(getVisibleChildren(container)).toEqual(
    
  7039.       <div>
    
  7040.         {'Hello'}
    
  7041.         {'Hello'}
    
  7042.       </div>,
    
  7043.     );
    
  7044. 
    
  7045.     // Resolve the final promise
    
  7046.     await act(() => {
    
  7047.       resolve('Hi');
    
  7048.     });
    
  7049. 
    
  7050.     expect(getVisibleChildren(container)).toEqual(
    
  7051.       <div>
    
  7052.         {'Hi'}
    
  7053.         {' World'}
    
  7054.         {'Hello'}
    
  7055.       </div>,
    
  7056.     );
    
  7057.   });
    
  7058. 
    
  7059.   // @gate enablePostpone
    
  7060.   it('can discover new suspense boundaries in the resume', async () => {
    
  7061.     let prerendering = true;
    
  7062.     let resolveA;
    
  7063.     const promiseA = new Promise(r => (resolveA = r));
    
  7064.     let resolveB;
    
  7065.     const promiseB = new Promise(r => (resolveB = r));
    
  7066. 
    
  7067.     function WaitA() {
    
  7068.       return React.use(promiseA);
    
  7069.     }
    
  7070.     function WaitB() {
    
  7071.       return React.use(promiseB);
    
  7072.     }
    
  7073.     function Postpone() {
    
  7074.       if (prerendering) {
    
  7075.         React.unstable_postpone();
    
  7076.       }
    
  7077.       return (
    
  7078.         <span>
    
  7079.           <Suspense fallback="Loading again...">
    
  7080.             <WaitA />
    
  7081.           </Suspense>
    
  7082.           <WaitB />
    
  7083.         </span>
    
  7084.       );
    
  7085.     }
    
  7086. 
    
  7087.     function App() {
    
  7088.       return (
    
  7089.         <div>
    
  7090.           <Suspense fallback="Loading...">
    
  7091.             <p>
    
  7092.               <Postpone />
    
  7093.             </p>
    
  7094.           </Suspense>
    
  7095.         </div>
    
  7096.       );
    
  7097.     }
    
  7098. 
    
  7099.     const prerendered = await ReactDOMFizzStatic.prerenderToNodeStream(<App />);
    
  7100.     expect(prerendered.postponed).not.toBe(null);
    
  7101. 
    
  7102.     prerendering = false;
    
  7103. 
    
  7104.     // Create a separate stream so it doesn't close the writable. I.e. simple concat.
    
  7105.     const preludeWritable = new Stream.PassThrough();
    
  7106.     preludeWritable.setEncoding('utf8');
    
  7107.     preludeWritable.on('data', chunk => {
    
  7108.       writable.write(chunk);
    
  7109.     });
    
  7110. 
    
  7111.     await act(() => {
    
  7112.       prerendered.prelude.pipe(preludeWritable);
    
  7113.     });
    
  7114. 
    
  7115.     const resumed = await ReactDOMFizzServer.resumeToPipeableStream(
    
  7116.       <App />,
    
  7117.       JSON.parse(JSON.stringify(prerendered.postponed)),
    
  7118.     );
    
  7119. 
    
  7120.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  7121. 
    
  7122.     // Read what we've completed so far
    
  7123.     await act(() => {
    
  7124.       resumed.pipe(writable);
    
  7125.     });
    
  7126. 
    
  7127.     // Still blocked
    
  7128.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  7129. 
    
  7130.     // Resolve the first promise, this unblocks the inner boundary
    
  7131.     await act(() => {
    
  7132.       resolveA('Hello');
    
  7133.     });
    
  7134. 
    
  7135.     // Still blocked
    
  7136.     expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
    
  7137. 
    
  7138.     // Resolve the second promise, this unblocks the outer boundary
    
  7139.     await act(() => {
    
  7140.       resolveB('World');
    
  7141.     });
    
  7142. 
    
  7143.     expect(getVisibleChildren(container)).toEqual(
    
  7144.       <div>
    
  7145.         <p>
    
  7146.           <span>
    
  7147.             {'Hello'}
    
  7148.             {'World'}
    
  7149.           </span>
    
  7150.         </p>
    
  7151.       </div>,
    
  7152.     );
    
  7153.   });
    
  7154. });