/** * 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 */ 'use strict'; import { insertNodesAndExecuteScripts, getVisibleChildren, } from '../test-utils/FizzTestUtils'; import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel'; // Polyfills for test environment global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextEncoder = require('util').TextEncoder; let act; let assertLog; let waitForPaint; let container; let React; let Scheduler; let ReactDOMServer; let ReactDOMClient; let useDeferredValue; let Suspense; describe('ReactDOMFizzForm', () => { beforeEach(() => { jest.resetModules(); Scheduler = require('scheduler'); patchMessageChannel(Scheduler); act = require('internal-test-utils').act; React = require('react'); ReactDOMServer = require('react-dom/server.browser'); ReactDOMClient = require('react-dom/client'); useDeferredValue = React.useDeferredValue; Suspense = React.Suspense; assertLog = require('internal-test-utils').assertLog; waitForPaint = require('internal-test-utils').waitForPaint; container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { document.body.removeChild(container); }); async function serverAct(callback) { let maybePromise; await act(() => { maybePromise = callback(); if (maybePromise && typeof maybePromise.catch === 'function') { maybePromise.catch(() => {}); } }); return maybePromise; } 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; insertNodesAndExecuteScripts(temp, container, null); } function Text({text}) { Scheduler.log(text); return text; } it('returns initialValue argument, if provided', async () => { function App() { return useDeferredValue('Final', 'Initial'); } const stream = await serverAct(() => ReactDOMServer.renderToReadableStream(), ); await readIntoContainer(stream); expect(container.textContent).toEqual('Initial'); // After hydration, it's updated to the final value await act(() => ReactDOMClient.hydrateRoot(container, )); expect(container.textContent).toEqual('Final'); }); // @gate enablePostpone it( 'if initial value postpones during hydration, it will switch to the ' + 'final value instead', async () => { function Content() { const isInitial = useDeferredValue(false, true); if (isInitial) { React.unstable_postpone(); } return ; } function App() { return (
}>
); } const stream = await serverAct(() => ReactDOMServer.renderToReadableStream(), ); await readIntoContainer(stream); expect(container.textContent).toEqual('Loading...'); assertLog(['Loading...']); // After hydration, it's updated to the final value await act(() => ReactDOMClient.hydrateRoot(container, )); expect(container.textContent).toEqual('Final'); assertLog(['Loading...', 'Final']); }, ); it( 'useDeferredValue during hydration has higher priority than remaining ' + 'incremental hydration', async () => { function B() { const text = useDeferredValue('B [Final]', 'B [Initial]'); return ; } function App() { return (
}>
}>
); } const cRef = React.createRef(); const stream = await serverAct(() => ReactDOMServer.renderToReadableStream(), ); await readIntoContainer(stream); assertLog(['A', 'B [Initial]', 'C']); expect(getVisibleChildren(container)).toEqual(
A B [Initial]
C
, ); const serverRenderedC = document.getElementById('C'); // On the client, we first hydrate the initial value, then upgrade // to final. await act(async () => { ReactDOMClient.hydrateRoot(container, ); // First the outermost Suspense boundary hydrates. await waitForPaint(['A']); expect(cRef.current).toBe(null); // Then the next level hydrates. This level includes a useDeferredValue, // so we should prioritize upgrading it before we proceed to hydrating // additional levels. await waitForPaint(['B [Initial]']); expect(getVisibleChildren(container)).toEqual(
A B [Initial]
C
, ); expect(cRef.current).toBe(null); // This paint should only update B. C should still be dehydrated. await waitForPaint(['B [Final]']); expect(getVisibleChildren(container)).toEqual(
A B [Final]
C
, ); expect(cRef.current).toBe(null); }); // Finally we can hydrate C assertLog(['C']); expect(getVisibleChildren(container)).toEqual(
A B [Final]
C
, ); expect(cRef.current).toBe(serverRenderedC); }, ); });