/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment */ 'use strict'; import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel'; import { getVisibleChildren, insertNodesAndExecuteScripts, } from '../test-utils/FizzTestUtils'; // Polyfills for test environment global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextEncoder = require('util').TextEncoder; global.TextDecoder = require('util').TextDecoder; let JSDOM; let React; let ReactDOM; let ReactDOMFizzServer; let ReactDOMFizzStatic; let Suspense; let SuspenseList; let container; let serverAct; describe('ReactDOMFizzStaticBrowser', () => { beforeEach(() => { jest.resetModules(); JSDOM = require('jsdom').JSDOM; // We need the mocked version of setTimeout inside the document. window.setTimeout = setTimeout; patchMessageChannel(); serverAct = require('internal-test-utils').serverAct; React = require('react'); ReactDOM = require('react-dom'); ReactDOMFizzServer = require('react-dom/server.browser'); ReactDOMFizzStatic = require('react-dom/static.browser'); Suspense = React.Suspense; SuspenseList = React.unstable_SuspenseList; container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { if (typeof global.window.__restoreGlobalScope === 'function') { global.window.__restoreGlobalScope(); } document.body.removeChild(container); }); const theError = new Error('This is an error'); function Throw() { throw theError; } const theInfinitePromise = new Promise(() => {}); function InfiniteSuspend() { throw theInfinitePromise; } function concat(streamA, streamB) { const readerA = streamA.getReader(); const readerB = streamB.getReader(); return new ReadableStream({ start(controller) { function readA() { readerA.read().then(({done, value}) => { if (done) { readB(); return; } controller.enqueue(value); readA(); }); } function readB() { readerB.read().then(({done, value}) => { if (done) { controller.close(); return; } controller.enqueue(value); readB(); }); } readA(); }, }); } async function readContent(stream) { const reader = stream.getReader(); let content = ''; while (true) { const {done, value} = await reader.read(); if (done) { return content; } content += Buffer.from(value).toString('utf8'); } } async function readIntoContainer(stream) { const reader = stream.getReader(); let result = ''; while (true) { const {done, value} = await reader.read(); if (done) { break; } result += Buffer.from(value).toString('utf8'); } const temp = document.createElement('div'); temp.innerHTML = result; await insertNodesAndExecuteScripts(temp, container, null); jest.runAllTimers(); } async function readIntoNewDocument(stream) { const content = await readContent(stream); const jsdom = new JSDOM( // The Fizz runtime assumes requestAnimationFrame exists so we need to polyfill it. '' + content, { runScripts: 'dangerously', }, ); const originalWindow = global.window; const originalDocument = global.document; const originalNavigator = global.navigator; const originalNode = global.Node; const originalAddEventListener = global.addEventListener; const originalMutationObserver = global.MutationObserver; global.window = jsdom.window; global.document = global.window.document; global.navigator = global.window.navigator; global.Node = global.window.Node; global.addEventListener = global.window.addEventListener; global.MutationObserver = global.window.MutationObserver; global.window.__restoreGlobalScope = () => { global.window = originalWindow; global.document = originalDocument; global.navigator = originalNavigator; global.Node = originalNode; global.addEventListener = originalAddEventListener; global.MutationObserver = originalMutationObserver; }; } async function readIntoCurrentDocument(stream) { const content = await readContent(stream); const temp = document.createElement('div'); temp.innerHTML = content; await insertNodesAndExecuteScripts(temp, document.body, null); jest.runAllTimers(); } it('should call prerender', async () => { const result = await serverAct(() => ReactDOMFizzStatic.prerender(
hello world
), ); const prelude = await readContent(result.prelude); expect(prelude).toMatchInlineSnapshot(`"
hello world
"`); }); it('should emit DOCTYPE at the root of the document', async () => { const result = await serverAct(() => ReactDOMFizzStatic.prerender( hello world , ), ); const prelude = await readContent(result.prelude); if (gate(flags => flags.enableFizzBlockingRender)) { expect(prelude).toMatchInlineSnapshot( `"hello world"`, ); } else { expect(prelude).toMatchInlineSnapshot( `"hello world"`, ); } }); it('should emit bootstrap script src at the end', async () => { const result = await serverAct(() => ReactDOMFizzStatic.prerender(
hello world
, { bootstrapScriptContent: 'INIT();', bootstrapScripts: ['init.js'], bootstrapModules: ['init.mjs'], }), ); const prelude = await readContent(result.prelude); expect(prelude).toMatchInlineSnapshot( `"
hello world
"`, ); }); it('emits all HTML as one unit', async () => { let hasLoaded = false; let resolve; const promise = new Promise(r => (resolve = r)); function Wait() { if (!hasLoaded) { throw promise; } return 'Done'; } const resultPromise = serverAct(() => ReactDOMFizzStatic.prerender(
, ), ); await jest.runAllTimers(); // Resolve the loading. hasLoaded = true; await resolve(); const result = await resultPromise; const prelude = await readContent(result.prelude); expect(prelude).toMatchInlineSnapshot(`"
Done
"`); }); it('should reject the promise when an error is thrown at the root', async () => { const reportedErrors = []; let caughtError = null; try { await serverAct(() => ReactDOMFizzStatic.prerender(
, { onError(x) { reportedErrors.push(x); }, }, ), ); } catch (error) { caughtError = error; } expect(caughtError).toBe(theError); expect(reportedErrors).toEqual([theError]); }); it('should reject the promise when an error is thrown inside a fallback', async () => { const reportedErrors = []; let caughtError = null; try { await serverAct(() => ReactDOMFizzStatic.prerender(
}>
, { onError(x) { reportedErrors.push(x); }, }, ), ); } catch (error) { caughtError = error; } expect(caughtError).toBe(theError); expect(reportedErrors).toEqual([theError]); }); it('should not error the stream when an error is thrown inside suspense boundary', async () => { const reportedErrors = []; const result = await serverAct(() => ReactDOMFizzStatic.prerender(
Loading
}> , { onError(x) { reportedErrors.push(x); }, }, ), ); const prelude = await readContent(result.prelude); expect(prelude).toContain('Loading'); expect(reportedErrors).toEqual([theError]); }); it('should be able to complete by aborting even if the promise never resolves', async () => { const errors = []; const controller = new AbortController(); let resultPromise; await serverAct(() => { resultPromise = ReactDOMFizzStatic.prerender(
Loading
}> , { signal: controller.signal, onError(x) { errors.push(x.message); }, }, ); }); controller.abort(); const result = await resultPromise; const prelude = await readContent(result.prelude); expect(prelude).toContain('Loading'); expect(errors).toEqual(['This operation was aborted']); }); // @gate !enableHalt it('should reject if aborting before the shell is complete and enableHalt is disabled', async () => { const errors = []; const controller = new AbortController(); const promise = serverAct(() => ReactDOMFizzStatic.prerender(
, { signal: controller.signal, onError(x) { errors.push(x.message); }, }, ), ); await jest.runAllTimers(); const theReason = new Error('aborted for reasons'); controller.abort(theReason); let caughtError = null; try { await promise; } catch (error) { caughtError = error; } expect(caughtError).toBe(theReason); expect(errors).toEqual(['aborted for reasons']); }); // @gate enableHalt it('should resolve an empty prelude if aborting before the shell is complete', async () => { const errors = []; const controller = new AbortController(); const promise = serverAct(() => ReactDOMFizzStatic.prerender(
, { signal: controller.signal, onError(x) { errors.push(x.message); }, }, ), ); await jest.runAllTimers(); const theReason = new Error('aborted for reasons'); controller.abort(theReason); let rejected = false; let prelude; try { ({prelude} = await promise); } catch (error) { rejected = true; } expect(rejected).toBe(false); expect(errors).toEqual(['aborted for reasons']); const content = await readContent(prelude); expect(content).toBe(''); }); it('should be able to abort before something suspends', async () => { const errors = []; const controller = new AbortController(); function App() { controller.abort(); return ( Loading}> ); } const streamPromise = serverAct(() => ReactDOMFizzStatic.prerender(
, { signal: controller.signal, onError(x) { errors.push(x.message); }, }, ), ); if (gate(flags => flags.enableHalt)) { const {prelude} = await streamPromise; const content = await readContent(prelude); expect(errors).toEqual(['This operation was aborted']); expect(content).toBe(''); } else { let caughtError = null; try { await streamPromise; } catch (error) { caughtError = error; } expect(caughtError.message).toBe('This operation was aborted'); expect(errors).toEqual(['This operation was aborted']); } }); // @gate !enableHalt it('should reject if passing an already aborted signal and enableHalt is disabled', async () => { const errors = []; const controller = new AbortController(); const theReason = new Error('aborted for reasons'); controller.abort(theReason); const promise = serverAct(() => ReactDOMFizzStatic.prerender(
Loading
}> , { signal: controller.signal, onError(x) { errors.push(x.message); }, }, ), ); // Technically we could still continue rendering the shell but currently the // semantics mean that we also abort any pending CPU work. let caughtError = null; try { await promise; } catch (error) { caughtError = error; } expect(caughtError).toBe(theReason); expect(errors).toEqual(['aborted for reasons']); }); // @gate enableHalt it('should resolve an empty prelude if passing an already aborted signal', async () => { const errors = []; const controller = new AbortController(); const theReason = new Error('aborted for reasons'); controller.abort(theReason); const promise = serverAct(() => ReactDOMFizzStatic.prerender(
Loading
}> , { signal: controller.signal, onError(x) { errors.push(x.message); }, }, ), ); // Technically we could still continue rendering the shell but currently the // semantics mean that we also abort any pending CPU work. let didThrow = false; let prelude; try { ({prelude} = await promise); } catch (error) { didThrow = true; } expect(didThrow).toBe(false); expect(errors).toEqual(['aborted for reasons']); const content = await readContent(prelude); expect(content).toBe(''); }); it('supports custom abort reasons with a string', async () => { const promise = new Promise(r => {}); function Wait() { throw promise; } function App() { return (

); } const errors = []; const controller = new AbortController(); let resultPromise; await serverAct(() => { resultPromise = ReactDOMFizzStatic.prerender(, { signal: controller.signal, onError(x) { errors.push(x); return 'a digest'; }, }); }); controller.abort('foobar'); await resultPromise; expect(errors).toEqual(['foobar', 'foobar']); }); it('supports custom abort reasons with an Error', async () => { const promise = new Promise(r => {}); function Wait() { throw promise; } function App() { return (

); } const errors = []; const controller = new AbortController(); let resultPromise; await serverAct(() => { resultPromise = ReactDOMFizzStatic.prerender(, { signal: controller.signal, onError(x) { errors.push(x.message); return 'a digest'; }, }); }); controller.abort(new Error('uh oh')); await resultPromise; expect(errors).toEqual(['uh oh', 'uh oh']); }); // @gate enablePostpone it('supports postponing in prerender and resuming later', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return ['Hello', 'World']; } function App() { return (
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
{['Hello', 'World']}
, ); }); // @gate enablePostpone it('supports postponing in prerender and resuming with a prefix', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'World'; } function App() { return (
Hello
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
{['Hello', 'World']}
, ); }); // @gate enablePostpone it('supports postponing in lazy in prerender and resuming later', async () => { let prerendering = true; const Hole = React.lazy(async () => { React.unstable_postpone(); }); function App() { return (
Hi {prerendering ? Hole : 'Hello'}
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
{'Hi'} {'Hello'}
, ); }); // @gate enablePostpone it('supports postponing in a nested array', async () => { let prerendering = true; const Hole = React.lazy(async () => { React.unstable_postpone(); }); function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } function App() { return (
Hi {[, prerendering ? Hole : 'World']}
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
{['Hi', 'Hello', 'World']}
, ); }); // @gate enablePostpone it('supports postponing in lazy as a direct child', async () => { let prerendering = true; const Hole = React.lazy(async () => { React.unstable_postpone(); }); function Postpone() { return prerendering ? Hole : 'Hello'; } function App() { return (
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
Hello
); }); // @gate enablePostpone it('only emits end tags once when resuming', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } function App() { return ( ); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const content = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); const html = await readContent(concat(prerendered.prelude, content)); const htmlEndTags = /<\/html\s*>/gi; const bodyEndTags = /<\/body\s*>/gi; expect(Array.from(html.matchAll(htmlEndTags)).length).toBe(1); expect(Array.from(html.matchAll(bodyEndTags)).length).toBe(1); }); // @gate enablePostpone it('can prerender various hoistables and deduped resources', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return ( <> ); } function App() { ReactDOM.preconnect('example.com'); ReactDOM.preload('my-font', {as: 'font', type: 'font/woff2'}); ReactDOM.preload('my-style0', {as: 'style'}); // This should transfer the props in to the style that loads later. ReactDOM.preload('my-style2', { as: 'style', crossOrigin: 'use-credentials', }); return (
Hello World
); } let calledInit = false; jest.mock( 'init.js', () => { calledInit = true; }, {virtual: true}, ); const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(, { bootstrapScripts: ['init.js'], }), ); expect(prerendered.postponed).not.toBe(null); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual([ , , , , , , , Hello World,
Loading...
, ]); prerendering = false; const content = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(content); expect(calledInit).toBe(true); // Dispatch load event to injected stylesheet const link = document.querySelector( 'link[rel="stylesheet"][href="my-style2"]', ); const event = document.createEvent('Events'); event.initEvent('load', true, true); link.dispatchEvent(event); // Wait for the instruction microtasks to flush. await 0; await 0; jest.runAllTimers(); expect(getVisibleChildren(container)).toEqual([ , , , , , , , , , Hello World,
, ]); }); // @gate enablePostpone it('can postpone a boundary after it has already been added', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } function App() { return (
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
{['Hello', 'Hello', 'Hello']}
, ); }); // @gate enablePostpone it('can postpone in fallback', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } const Lazy = React.lazy(async () => { await 0; return {default: Postpone}; }); function App() { return (
}> World }>
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Outer
); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
{'Hello'} {' World'} {'Hello'}
, ); }); // @gate enablePostpone it('can postpone in fallback without postponing the tree', async () => { function Postpone() { React.unstable_postpone(); } const lazyText = React.lazy(async () => { await 0; // causes the fallback to start work return {default: 'Hello'}; }); function App() { return (
}>{lazyText}
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); // TODO: This should actually be null because we should've been able to fully // resolve the render on the server eventually, even though the fallback postponed. // So we should not need to resume. expect(prerendered.postponed).not.toBe(null); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Outer
); const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await readIntoContainer(resumed); expect(getVisibleChildren(container)).toEqual(
Hello
); }); // @gate enablePostpone it('errors if the replay does not line up', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } function Wrapper({children}) { return children; } const lazySpan = React.lazy(async () => { await 0; return {default: }; }); function App() { const children = ( ); return ( <>
{prerendering ? {children} : children}
{prerendering ? (
) : ( lazySpan )}
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual([
Loading...
,
Loading...
, ]); prerendering = false; const errors = []; const resumed = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), { onError(x) { errors.push(x.message); }, }, ), ); expect(errors).toEqual([ 'Expected the resume to render in this slot but instead it rendered . ' + "The tree doesn't match so React will fallback to client rendering.", 'Expected the resume to render in this slot but instead it rendered . ' + "The tree doesn't match so React will fallback to client rendering.", ]); // TODO: Test the component stack but we don't expose it to the server yet. await readIntoContainer(resumed); // Client rendered expect(getVisibleChildren(container)).toEqual([
Loading...
,
Loading...
, ]); }); // @gate enablePostpone it('can abort the resume', async () => { let prerendering = true; const infinitePromise = new Promise(() => {}); function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } function App() { if (!prerendering) { React.use(infinitePromise); } return (
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); await readIntoContainer(prerendered.prelude); expect(getVisibleChildren(container)).toEqual(
Loading...
); prerendering = false; const controller = new AbortController(); const errors = []; const resumedPromise = serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), { signal: controller.signal, onError(x) { errors.push(x); }, }, ), ); controller.abort('abort'); const resumed = await resumedPromise; await resumed.allReady; expect(errors).toEqual(['abort']); await readIntoContainer(resumed); // Client rendered expect(getVisibleChildren(container)).toEqual(
Loading...
); }); // @gate enablePostpone it('can suspend in a replayed component several layers deep', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } let resolve; const promise = new Promise(r => (resolve = r)); function Delay({children}) { if (!prerendering) { React.use(promise); } return children; } // This wrapper will cause us to do one destructive render past this. function Outer({children}) { return children; } function App() { return (
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); await readIntoContainer(prerendered.prelude); prerendering = false; const resumedPromise = serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); await jest.runAllTimers(); expect(getVisibleChildren(container)).toEqual(
Loading...
); await resolve(); await readIntoContainer(await resumedPromise); expect(getVisibleChildren(container)).toEqual(
Hello
); }); // @gate enablePostpone it('emits an empty prelude and resumes at the root if we postpone in the shell', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } function App() { return ( ); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; expect(await readContent(prerendered.prelude)).toBe(''); const content = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); expect(await readContent(content)).toBe( '' + '' + (gate(flags => flags.enableFizzBlockingRender) ? '' : '') + '' + 'Hello' + (gate(flags => flags.enableFizzBlockingRender) ? '' : '') + '', ); }); // @gate enablePostpone it('emits an empty prelude if we have not rendered html or head tags yet', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return ( Hello ); } function App() { return ( <> ); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; expect(await readContent(prerendered.prelude)).toBe(''); const content = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); expect(await readContent(content)).toBe( '' + '' + '' + 'Hello', ); }); // @gate enablePostpone it('emits an empty prelude if a postpone in a promise in the shell', async () => { let prerendering = true; function Postpone() { if (prerendering) { React.unstable_postpone(); } return 'Hello'; } const Lazy = React.lazy(async () => { await 0; return {default: Postpone}; }); function App() { return (
); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; expect(await readContent(prerendered.prelude)).toBe(''); const content = await serverAct(() => ReactDOMFizzServer.resume( , JSON.parse(JSON.stringify(prerendered.postponed)), ), ); expect(await readContent(content)).toBe( '' + '' + '' + '
Hello
', ); }); // @gate enablePostpone it('does not emit preloads during resume for Resources preloaded through onHeaders', async () => { let prerendering = true; let hasLoaded = false; let resolve; const promise = new Promise(r => (resolve = r)); function WaitIfResuming({children}) { if (!prerendering && !hasLoaded) { throw promise; } return children; } function Postpone() { if (prerendering) { React.unstable_postpone(); } return null; } let headers; function onHeaders(x) { headers = x; } function App() { ReactDOM.preload('image', {as: 'image', fetchPriority: 'high'}); return ( hello world ); } const prerendered = await serverAct(() => ReactDOMFizzStatic.prerender(, { onHeaders, }), ); expect(prerendered.postponed).not.toBe(null); prerendering = false; expect(await readContent(prerendered.prelude)).toBe(''); expect(headers).toEqual( new Headers({ Link: ` ; rel=preload; as="image"; fetchpriority="high",