diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
deleted file mode 100644
index 0a3773fa6f3e4..0000000000000
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
+++ /dev/null
@@ -1,1572 +0,0 @@
-/**
- * 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';
-
-// Polyfills for test environment
-global.ReadableStream =
- require('web-streams-polyfill/ponyfill/es6').ReadableStream;
-global.TextEncoder = require('util').TextEncoder;
-global.TextDecoder = require('util').TextDecoder;
-
-// Don't wait before processing work on the server.
-// TODO: we can replace this with FlightServer.act().
-global.setImmediate = cb => cb();
-
-let act;
-let use;
-let clientExports;
-let clientModuleError;
-let turbopackMap;
-let Stream;
-let FlightReact;
-let React;
-let FlightReactDOM;
-let ReactDOMClient;
-let ReactServerDOMServer;
-let ReactServerDOMClient;
-let ReactDOMFizzServer;
-let Suspense;
-let ErrorBoundary;
-let JSDOM;
-
-describe('ReactFlightDOM', () => {
- beforeEach(() => {
- // For this first reset we are going to load the dom-node version of react-server-dom-turbopack/server
- // This can be thought of as essentially being the React Server Components scope with react-server
- // condition
- jest.resetModules();
-
- JSDOM = require('jsdom').JSDOM;
-
- // Simulate the condition resolution
- jest.mock('react-server-dom-turbopack/server', () =>
- require('react-server-dom-turbopack/server.node.unbundled'),
- );
- jest.mock('react', () => require('react/react.shared-subset'));
-
- const TurbopackMock = require('./utils/TurbopackMock');
- clientExports = TurbopackMock.clientExports;
- clientModuleError = TurbopackMock.clientModuleError;
- turbopackMap = TurbopackMock.turbopackMap;
-
- ReactServerDOMServer = require('react-server-dom-turbopack/server');
- FlightReact = require('react');
- FlightReactDOM = require('react-dom');
-
- // This reset is to load modules for the SSR/Browser scope.
- jest.resetModules();
- __unmockReact();
- act = require('internal-test-utils').act;
- Stream = require('stream');
- React = require('react');
- use = React.use;
- Suspense = React.Suspense;
- ReactDOMClient = require('react-dom/client');
- ReactDOMFizzServer = require('react-dom/server.node');
- ReactServerDOMClient = require('react-server-dom-turbopack/client');
-
- ErrorBoundary = class extends React.Component {
- state = {hasError: false, error: null};
- static getDerivedStateFromError(error) {
- return {
- hasError: true,
- error,
- };
- }
- render() {
- if (this.state.hasError) {
- return this.props.fallback(this.state.error);
- }
- return this.props.children;
- }
- };
- });
-
- function getTestStream() {
- const writable = new Stream.PassThrough();
- const readable = new ReadableStream({
- start(controller) {
- writable.on('data', chunk => {
- controller.enqueue(chunk);
- });
- writable.on('end', () => {
- controller.close();
- });
- },
- });
- return {
- readable,
- writable,
- };
- }
-
- const theInfinitePromise = new Promise(() => {});
- function InfiniteSuspend() {
- throw theInfinitePromise;
- }
-
- function getMeaningfulChildren(element) {
- const children = [];
- let node = element.firstChild;
- while (node) {
- if (node.nodeType === 1) {
- if (
- // some tags are ambiguous and might be hidden because they look like non-meaningful children
- // so we have a global override where if this data attribute is included we also include the node
- node.hasAttribute('data-meaningful') ||
- (node.tagName === 'SCRIPT' &&
- node.hasAttribute('src') &&
- node.hasAttribute('async')) ||
- (node.tagName !== 'SCRIPT' &&
- node.tagName !== 'TEMPLATE' &&
- node.tagName !== 'template' &&
- !node.hasAttribute('hidden') &&
- !node.hasAttribute('aria-hidden'))
- ) {
- const props = {};
- const attributes = node.attributes;
- for (let i = 0; i < attributes.length; i++) {
- if (
- attributes[i].name === 'id' &&
- attributes[i].value.includes(':')
- ) {
- // We assume this is a React added ID that's a non-visual implementation detail.
- continue;
- }
- props[attributes[i].name] = attributes[i].value;
- }
- props.children = getMeaningfulChildren(node);
- children.push(React.createElement(node.tagName.toLowerCase(), props));
- }
- } else if (node.nodeType === 3) {
- children.push(node.data);
- }
- node = node.nextSibling;
- }
- return children.length === 0
- ? undefined
- : children.length === 1
- ? children[0]
- : children;
- }
-
- it('should resolve HTML using Node streams', async () => {
- function Text({children}) {
- return {children} ;
- }
- function HTML() {
- return (
-
- hello
- world
-
- );
- }
-
- function App() {
- const model = {
- html: {getResponse()}
-
- );
- }
-
- await act(async () => {
- ReactDOMFizzServer.renderToPipeableStream( ).pipe(fizzWritable);
- });
-
- const decoder = new TextDecoder();
- const reader = fizzReadable.getReader();
- let content = '';
- while (true) {
- const {done, value} = await reader.read();
- if (done) {
- content += decoder.decode();
- break;
- }
- content += decoder.decode(value, {stream: true});
- }
-
- const doc = new JSDOM(content).window.document;
- expect(getMeaningfulChildren(doc)).toEqual(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- hello world
-
- ,
- );
- });
-
- it('supports Float hints from concurrent Flight -> Fizz renders', async () => {
- function Component() {
- return hello world
;
- }
-
- const ClientComponent = clientExports(Component);
-
- async function ServerComponent1() {
- FlightReactDOM.preload('before1', {as: 'style'});
- await 1;
- FlightReactDOM.preload('after1', {as: 'style'});
- return ;
- }
-
- async function ServerComponent2() {
- FlightReactDOM.preload('before2', {as: 'style'});
- await 1;
- FlightReactDOM.preload('after2', {as: 'style'});
- return ;
- }
-
- const {writable: flightWritable1, readable: flightReadable1} =
- getTestStream();
- const {writable: flightWritable2, readable: flightReadable2} =
- getTestStream();
-
- ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- ).pipe(flightWritable1);
-
- ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- ).pipe(flightWritable2);
-
- const responses = new Map();
- function getResponse(stream) {
- let response = responses.get(stream);
- if (!response) {
- response = ReactServerDOMClient.createFromReadableStream(stream);
- responses.set(stream, response);
- }
- return response;
- }
-
- function App({stream}) {
- return (
-
- {getResponse(stream)}
-
- );
- }
-
- // pausing to let Flight runtime tick. This is a test only artifact of the fact that
- // we aren't operating separate module graphs for flight and fiber. In a real app
- // each would have their own dispatcher and there would be no cross dispatching.
- await 1;
-
- const {writable: fizzWritable1, readable: fizzReadable1} = getTestStream();
- const {writable: fizzWritable2, readable: fizzReadable2} = getTestStream();
- await act(async () => {
- ReactDOMFizzServer.renderToPipeableStream(
- ,
- ).pipe(fizzWritable1);
- ReactDOMFizzServer.renderToPipeableStream(
- ,
- ).pipe(fizzWritable2);
- });
-
- async function read(stream) {
- const decoder = new TextDecoder();
- const reader = stream.getReader();
- let buffer = '';
- while (true) {
- const {done, value} = await reader.read();
- if (done) {
- buffer += decoder.decode();
- break;
- }
- buffer += decoder.decode(value, {stream: true});
- }
- return buffer;
- }
-
- const [content1, content2] = await Promise.all([
- read(fizzReadable1),
- read(fizzReadable2),
- ]);
-
- expect(content1).toEqual(
- ' ' +
- 'hello world
',
- );
- expect(content2).toEqual(
- ' ' +
- 'hello world
',
- );
- });
-
- it('supports deduping hints by Float key', async () => {
- function Component() {
- return hello world
;
- }
-
- const ClientComponent = clientExports(Component);
-
- async function ServerComponent() {
- FlightReactDOM.prefetchDNS('dns');
- FlightReactDOM.preconnect('preconnect');
- FlightReactDOM.preload('load', {as: 'style'});
- FlightReactDOM.preinit('init', {as: 'script'});
- // again but vary preconnect to demonstrate crossOrigin participates in the key
- FlightReactDOM.prefetchDNS('dns');
- FlightReactDOM.preconnect('preconnect', {crossOrigin: 'anonymous'});
- FlightReactDOM.preload('load', {as: 'style'});
- FlightReactDOM.preinit('init', {as: 'script'});
- await 1;
- // after an async point
- FlightReactDOM.prefetchDNS('dns');
- FlightReactDOM.preconnect('preconnect', {crossOrigin: 'use-credentials'});
- FlightReactDOM.preload('load', {as: 'style'});
- FlightReactDOM.preinit('init', {as: 'script'});
- return ;
- }
-
- const {writable, readable} = getTestStream();
-
- ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- ).pipe(writable);
-
- const hintRows = [];
- async function collectHints(stream) {
- const decoder = new TextDecoder();
- const reader = stream.getReader();
- let buffer = '';
- while (true) {
- const {done, value} = await reader.read();
- if (done) {
- buffer += decoder.decode();
- if (buffer.includes(':H')) {
- hintRows.push(buffer);
- }
- break;
- }
- buffer += decoder.decode(value, {stream: true});
- let line;
- while ((line = buffer.indexOf('\n')) > -1) {
- const row = buffer.slice(0, line);
- buffer = buffer.slice(line + 1);
- if (row.includes(':H')) {
- hintRows.push(row);
- }
- }
- }
- }
-
- await collectHints(readable);
- expect(hintRows.length).toEqual(6);
- });
-});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
deleted file mode 100644
index eebabfce5d0de..0000000000000
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
+++ /dev/null
@@ -1,256 +0,0 @@
-/**
- * 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';
-
-// Don't wait before processing work on the server.
-// TODO: we can replace this with FlightServer.act().
-global.setImmediate = cb => cb();
-
-let clientExports;
-let turbopackMap;
-let turbopackModules;
-let turbopackModuleLoading;
-let React;
-let ReactDOMServer;
-let ReactServerDOMServer;
-let ReactServerDOMClient;
-let Stream;
-let use;
-
-describe('ReactFlightDOMNode', () => {
- beforeEach(() => {
- jest.resetModules();
-
- // Simulate the condition resolution
- jest.mock('react', () => require('react/react.shared-subset'));
- jest.mock('react-server-dom-turbopack/server', () =>
- require('react-server-dom-turbopack/server.node'),
- );
- ReactServerDOMServer = require('react-server-dom-turbopack/server');
-
- const TurbopackMock = require('./utils/TurbopackMock');
- clientExports = TurbopackMock.clientExports;
- turbopackMap = TurbopackMock.turbopackMap;
- turbopackModules = TurbopackMock.turbopackModules;
- turbopackModuleLoading = TurbopackMock.moduleLoading;
-
- jest.resetModules();
- __unmockReact();
- jest.unmock('react-server-dom-turbopack/server');
- jest.mock('react-server-dom-turbopack/client', () =>
- require('react-server-dom-turbopack/client.node'),
- );
-
- React = require('react');
- ReactDOMServer = require('react-dom/server.node');
- ReactServerDOMClient = require('react-server-dom-turbopack/client');
- Stream = require('stream');
- use = React.use;
- });
-
- function readResult(stream) {
- return new Promise((resolve, reject) => {
- let buffer = '';
- const writable = new Stream.PassThrough();
- writable.setEncoding('utf8');
- writable.on('data', chunk => {
- buffer += chunk;
- });
- writable.on('error', error => {
- reject(error);
- });
- writable.on('end', () => {
- resolve(buffer);
- });
- stream.pipe(writable);
- });
- }
-
- it('should allow an alternative module mapping to be used for SSR', async () => {
- function ClientComponent() {
- return Client Component ;
- }
- // The Client build may not have the same IDs as the Server bundles for the same
- // component.
- const ClientComponentOnTheClient = clientExports(
- ClientComponent,
- 'path/to/chunk.js',
- );
- const ClientComponentOnTheServer = clientExports(ClientComponent);
-
- // In the SSR bundle this module won't exist. We simulate this by deleting it.
- const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
- delete turbopackModules[clientId];
-
- // Instead, we have to provide a translation from the client meta data to the SSR
- // meta data.
- const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
- const translationMap = {
- [clientId]: {
- '*': ssrMetadata,
- },
- };
-
- function App() {
- return ;
- }
-
- const stream = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- const readable = new Stream.PassThrough();
-
- stream.pipe(readable);
-
- let response;
- function ClientRoot() {
- if (!response) {
- response = ReactServerDOMClient.createFromNodeStream(readable, {
- moduleMap: translationMap,
- moduleLoading: turbopackModuleLoading,
- });
- }
- return use(response);
- }
-
- const ssrStream = await ReactDOMServer.renderToPipeableStream(
- ,
- );
- const result = await readResult(ssrStream);
- expect(result).toEqual(
- 'Client Component ',
- );
- });
-
- it('should encode long string in a compact format', async () => {
- const testString = '"\n\t'.repeat(500) + '🙃';
-
- const stream = ReactServerDOMServer.renderToPipeableStream({
- text: testString,
- });
-
- const readable = new Stream.PassThrough();
-
- const stringResult = readResult(readable);
- const parsedResult = ReactServerDOMClient.createFromNodeStream(readable, {
- moduleMap: turbopackMap,
- moduleLoading: turbopackModuleLoading,
- });
-
- stream.pipe(readable);
-
- const serializedContent = await stringResult;
- // The content should be compact an unescaped
- expect(serializedContent.length).toBeLessThan(2000);
- expect(serializedContent).not.toContain('\\n');
- expect(serializedContent).not.toContain('\\t');
- expect(serializedContent).not.toContain('\\"');
- expect(serializedContent).toContain('\t');
-
- const result = await parsedResult;
- // Should still match the result when parsed
- expect(result.text).toBe(testString);
- });
-
- // @gate enableBinaryFlight
- it('should be able to serialize any kind of typed array', async () => {
- const buffer = new Uint8Array([
- 123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
- ]).buffer;
- const buffers = [
- buffer,
- new Int8Array(buffer, 1),
- new Uint8Array(buffer, 2),
- new Uint8ClampedArray(buffer, 2),
- new Int16Array(buffer, 2),
- new Uint16Array(buffer, 2),
- new Int32Array(buffer, 4),
- new Uint32Array(buffer, 4),
- new Float32Array(buffer, 4),
- new Float64Array(buffer, 0),
- new BigInt64Array(buffer, 0),
- new BigUint64Array(buffer, 0),
- new DataView(buffer, 3),
- ];
- const stream = ReactServerDOMServer.renderToPipeableStream(buffers);
- const readable = new Stream.PassThrough();
- const promise = ReactServerDOMClient.createFromNodeStream(readable, {
- moduleMap: turbopackMap,
- moduleLoading: turbopackModuleLoading,
- });
- stream.pipe(readable);
- const result = await promise;
- expect(result).toEqual(buffers);
- });
-
- it('should allow accept a nonce option for Flight preinitialized scripts', async () => {
- function ClientComponent() {
- return Client Component ;
- }
- // The Client build may not have the same IDs as the Server bundles for the same
- // component.
- const ClientComponentOnTheClient = clientExports(
- ClientComponent,
- 'path/to/chunk.js',
- );
- const ClientComponentOnTheServer = clientExports(ClientComponent);
-
- // In the SSR bundle this module won't exist. We simulate this by deleting it.
- const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
- delete turbopackModules[clientId];
-
- // Instead, we have to provide a translation from the client meta data to the SSR
- // meta data.
- const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
- const translationMap = {
- [clientId]: {
- '*': ssrMetadata,
- },
- };
- const ssrManifest = {
- moduleMap: translationMap,
- moduleLoading: turbopackModuleLoading,
- };
-
- function App() {
- return ;
- }
-
- const stream = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- const readable = new Stream.PassThrough();
- let response;
-
- stream.pipe(readable);
-
- function ClientRoot() {
- if (response) return use(response);
- response = ReactServerDOMClient.createFromNodeStream(
- readable,
- ssrManifest,
- {
- nonce: 'r4nd0m',
- },
- );
- return use(response);
- }
-
- const ssrStream = await ReactDOMServer.renderToPipeableStream(
- ,
- );
- const result = await readResult(ssrStream);
- expect(result).toEqual(
- 'Client Component ',
- );
- });
-});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
deleted file mode 100644
index d8475a8762f23..0000000000000
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
+++ /dev/null
@@ -1,234 +0,0 @@
-/**
- * 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';
-
-// Polyfills for test environment
-global.ReadableStream =
- require('web-streams-polyfill/ponyfill/es6').ReadableStream;
-global.TextEncoder = require('util').TextEncoder;
-global.TextDecoder = require('util').TextDecoder;
-
-// let serverExports;
-let turbopackServerMap;
-let ReactServerDOMServer;
-let ReactServerDOMClient;
-
-describe('ReactFlightDOMReply', () => {
- beforeEach(() => {
- jest.resetModules();
- // Simulate the condition resolution
- jest.mock('react', () => require('react/react.shared-subset'));
- jest.mock('react-server-dom-turbopack/server', () =>
- require('react-server-dom-turbopack/server.browser'),
- );
- const TurbopackMock = require('./utils/TurbopackMock');
- // serverExports = TurbopackMock.serverExports;
- turbopackServerMap = TurbopackMock.turbopackServerMap;
- ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
- jest.resetModules();
- ReactServerDOMClient = require('react-server-dom-turbopack/client');
- });
-
- // This method should exist on File but is not implemented in JSDOM
- async function arrayBuffer(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = function () {
- return resolve(reader.result);
- };
- reader.onerror = function () {
- return reject(reader.error);
- };
- reader.readAsArrayBuffer(file);
- });
- }
-
- it('can pass undefined as a reply', async () => {
- const body = await ReactServerDOMClient.encodeReply(undefined);
- const missing = await ReactServerDOMServer.decodeReply(
- body,
- turbopackServerMap,
- );
- expect(missing).toBe(undefined);
-
- const body2 = await ReactServerDOMClient.encodeReply({
- array: [undefined, null, undefined],
- prop: undefined,
- });
- const object = await ReactServerDOMServer.decodeReply(
- body2,
- turbopackServerMap,
- );
- expect(object.array.length).toBe(3);
- expect(object.array[0]).toBe(undefined);
- expect(object.array[1]).toBe(null);
- expect(object.array[3]).toBe(undefined);
- expect(object.prop).toBe(undefined);
- // These should really be true but our deserialization doesn't currently deal with it.
- expect('3' in object.array).toBe(false);
- expect('prop' in object).toBe(false);
- });
-
- it('can pass an iterable as a reply', async () => {
- const body = await ReactServerDOMClient.encodeReply({
- [Symbol.iterator]: function* () {
- yield 'A';
- yield 'B';
- yield 'C';
- },
- });
- const iterable = await ReactServerDOMServer.decodeReply(
- body,
- turbopackServerMap,
- );
- const items = [];
- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
- for (const item of iterable) {
- items.push(item);
- }
- expect(items).toEqual(['A', 'B', 'C']);
- });
-
- it('can pass weird numbers as a reply', async () => {
- const nums = [0, -0, Infinity, -Infinity, NaN];
- const body = await ReactServerDOMClient.encodeReply(nums);
- const nums2 = await ReactServerDOMServer.decodeReply(
- body,
- turbopackServerMap,
- );
-
- expect(nums).toEqual(nums2);
- expect(nums.every((n, i) => Object.is(n, nums2[i]))).toBe(true);
- });
-
- it('can pass a BigInt as a reply', async () => {
- const body = await ReactServerDOMClient.encodeReply(90071992547409910000n);
- const n = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
-
- expect(n).toEqual(90071992547409910000n);
- });
-
- it('can pass FormData as a reply', async () => {
- const formData = new FormData();
- formData.set('hello', 'world');
- formData.append('list', '1');
- formData.append('list', '2');
- formData.append('list', '3');
- const typedArray = new Uint8Array([0, 1, 2, 3]);
- const blob = new Blob([typedArray]);
- formData.append('blob', blob, 'filename.blob');
-
- const body = await ReactServerDOMClient.encodeReply(formData);
- const formData2 = await ReactServerDOMServer.decodeReply(
- body,
- turbopackServerMap,
- );
-
- expect(formData2).not.toBe(formData);
- expect(Array.from(formData2).length).toBe(5);
- expect(formData2.get('hello')).toBe('world');
- expect(formData2.getAll('list')).toEqual(['1', '2', '3']);
- const blob2 = formData.get('blob');
- expect(blob2.size).toBe(4);
- expect(blob2.name).toBe('filename.blob');
- expect(blob2.type).toBe('');
- const typedArray2 = new Uint8Array(await arrayBuffer(blob2));
- expect(typedArray2).toEqual(typedArray);
- });
-
- it('can pass multiple Files in FormData', async () => {
- const typedArrayA = new Uint8Array([0, 1, 2, 3]);
- const typedArrayB = new Uint8Array([4, 5]);
- const blobA = new Blob([typedArrayA]);
- const blobB = new Blob([typedArrayB]);
- const formData = new FormData();
- formData.append('filelist', 'string');
- formData.append('filelist', blobA);
- formData.append('filelist', blobB);
-
- const body = await ReactServerDOMClient.encodeReply(formData);
- const formData2 = await ReactServerDOMServer.decodeReply(
- body,
- turbopackServerMap,
- );
-
- const filelist2 = formData2.getAll('filelist');
- expect(filelist2.length).toBe(3);
- expect(filelist2[0]).toBe('string');
- const blobA2 = filelist2[1];
- expect(blobA2.size).toBe(4);
- expect(blobA2.name).toBe('blob');
- expect(blobA2.type).toBe('');
- const typedArrayA2 = new Uint8Array(await arrayBuffer(blobA2));
- expect(typedArrayA2).toEqual(typedArrayA);
- const blobB2 = filelist2[2];
- expect(blobB2.size).toBe(2);
- expect(blobB2.name).toBe('blob');
- expect(blobB2.type).toBe('');
- const typedArrayB2 = new Uint8Array(await arrayBuffer(blobB2));
- expect(typedArrayB2).toEqual(typedArrayB);
- });
-
- it('can pass two independent FormData with same keys', async () => {
- const formDataA = new FormData();
- formDataA.set('greeting', 'hello');
- const formDataB = new FormData();
- formDataB.set('greeting', 'hi');
-
- const body = await ReactServerDOMClient.encodeReply({
- a: formDataA,
- b: formDataB,
- });
- const {a: formDataA2, b: formDataB2} =
- await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
-
- expect(Array.from(formDataA2).length).toBe(1);
- expect(Array.from(formDataB2).length).toBe(1);
- expect(formDataA2.get('greeting')).toBe('hello');
- expect(formDataB2.get('greeting')).toBe('hi');
- });
-
- it('can pass a Date as a reply', async () => {
- const d = new Date(1234567890123);
- const body = await ReactServerDOMClient.encodeReply(d);
- const d2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
-
- expect(d).toEqual(d2);
- expect(d % 1000).toEqual(123); // double-check the milliseconds made it through
- });
-
- it('can pass a Map as a reply', async () => {
- const objKey = {obj: 'key'};
- const m = new Map([
- ['hi', {greet: 'world'}],
- [objKey, 123],
- ]);
- const body = await ReactServerDOMClient.encodeReply(m);
- const m2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
-
- expect(m2 instanceof Map).toBe(true);
- expect(m2.size).toBe(2);
- expect(m2.get('hi').greet).toBe('world');
- expect(m2).toEqual(m);
- });
-
- it('can pass a Set as a reply', async () => {
- const objKey = {obj: 'key'};
- const s = new Set(['hi', objKey]);
-
- const body = await ReactServerDOMClient.encodeReply(s);
- const s2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
-
- expect(s2 instanceof Set).toBe(true);
- expect(s2.size).toBe(2);
- expect(s2.has('hi')).toBe(true);
- expect(s2).toEqual(s);
- });
-});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js
new file mode 100644
index 0000000000000..81cd49b6cf19a
--- /dev/null
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js
@@ -0,0 +1,208 @@
+/**
+ * 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';
+
+// Polyfills for test environment
+global.ReadableStream =
+ require('web-streams-polyfill/ponyfill/es6').ReadableStream;
+global.TextEncoder = require('util').TextEncoder;
+global.TextDecoder = require('util').TextDecoder;
+
+// Don't wait before processing work on the server.
+// TODO: we can replace this with FlightServer.act().
+global.setImmediate = cb => cb();
+
+let act;
+let use;
+let clientExports;
+let turbopackMap;
+let Stream;
+let React;
+let ReactDOMClient;
+let ReactServerDOMServer;
+let ReactServerDOMClient;
+let Suspense;
+
+describe('ReactFlightDOM', () => {
+ beforeEach(() => {
+ // For this first reset we are going to load the dom-node version of react-server-dom-turbopack/server
+ // This can be thought of as essentially being the React Server Components scope with react-server
+ // condition
+ jest.resetModules();
+
+ // Simulate the condition resolution
+ jest.mock('react-server-dom-turbopack/server', () =>
+ require('react-server-dom-turbopack/server.node.unbundled'),
+ );
+ jest.mock('react', () => require('react/react.shared-subset'));
+
+ const TurbopackMock = require('./utils/TurbopackMock');
+ clientExports = TurbopackMock.clientExports;
+ turbopackMap = TurbopackMock.turbopackMap;
+
+ ReactServerDOMServer = require('react-server-dom-turbopack/server');
+
+ // This reset is to load modules for the SSR/Browser scope.
+ jest.resetModules();
+ __unmockReact();
+ act = require('internal-test-utils').act;
+ Stream = require('stream');
+ React = require('react');
+ use = React.use;
+ Suspense = React.Suspense;
+ ReactDOMClient = require('react-dom/client');
+ ReactServerDOMClient = require('react-server-dom-turbopack/client');
+ });
+
+ function getTestStream() {
+ const writable = new Stream.PassThrough();
+ const readable = new ReadableStream({
+ start(controller) {
+ writable.on('data', chunk => {
+ controller.enqueue(chunk);
+ });
+ writable.on('end', () => {
+ controller.close();
+ });
+ },
+ });
+ return {
+ readable,
+ writable,
+ };
+ }
+
+ it('should resolve HTML using Node streams', async () => {
+ function Text({children}) {
+ return {children} ;
+ }
+ function HTML() {
+ return (
+
+ hello
+ world
+
+ );
+ }
+
+ function App() {
+ const model = {
+ html: ,
- };
- return model;
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
- const model = await response;
- expect(model).toEqual({
- html: (
-
- hello
- world
-
- ),
- });
- });
-
- it('should resolve the root', async () => {
- // Model
- function Text({children}) {
- return {children} ;
- }
- function HTML() {
- return (
-
- hello
- world
-
- );
- }
- function RootModel() {
- return {
- html:
,
- };
- }
-
- // View
- function Message({response}) {
- return ;
- }
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe(
- '',
- );
- });
-
- it('should not get confused by $', async () => {
- // Model
- function RootModel() {
- return {text: '$1'};
- }
-
- // View
- function Message({response}) {
- return
{use(response).text}
;
- }
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
$1
');
- });
-
- it('should not get confused by @', async () => {
- // Model
- function RootModel() {
- return {text: '@div'};
- }
-
- // View
- function Message({response}) {
- return
{use(response).text}
;
- }
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
@div
');
- });
-
- it('should be able to esm compat test module references', async () => {
- const ESMCompatModule = {
- __esModule: true,
- default: function ({greeting}) {
- return greeting + ' World';
- },
- hi: 'Hello',
- };
-
- function Print({response}) {
- return
{use(response)}
;
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- function interopWebpack(obj) {
- // Basically what Webpack's ESM interop feature testing does.
- if (typeof obj === 'object' && obj.__esModule) {
- return obj;
- }
- return Object.assign({default: obj}, obj);
- }
-
- const {default: Component, hi} = interopWebpack(
- clientExports(ESMCompatModule),
- );
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
Hello World
');
- });
-
- it('should be able to render a named component export', async () => {
- const Module = {
- Component: function ({greeting}) {
- return greeting + ' World';
- },
- };
-
- function Print({response}) {
- return
{use(response)}
;
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const {Component} = clientExports(Module);
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
Hello World
');
- });
-
- it('should be able to render a module split named component export', async () => {
- const Module = {
- // This gets split into a separate module from the original one.
- split: function ({greeting}) {
- return greeting + ' World';
- },
- };
-
- function Print({response}) {
- return
{use(response)}
;
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const {split: Component} = clientExports(Module);
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
Hello World
');
- });
-
- it('should unwrap async module references', async () => {
- const AsyncModule = Promise.resolve(function AsyncModule({text}) {
- return 'Async: ' + text;
- });
-
- const AsyncModule2 = Promise.resolve({
- exportName: 'Module',
- });
-
- function Print({response}) {
- return
{use(response)}
;
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const AsyncModuleRef = await clientExports(AsyncModule);
- const AsyncModuleRef2 = await clientExports(AsyncModule2);
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
Async: Module
');
- });
-
- it('should unwrap async module references using use', async () => {
- const AsyncModule = Promise.resolve('Async Text');
-
- function Print({response}) {
- return use(response);
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const AsyncModuleRef = clientExports(AsyncModule);
-
- function ServerComponent() {
- const text = FlightReact.use(AsyncModuleRef);
- return
{text}
;
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
Async Text
');
- });
-
- it('should be able to import a name called "then"', async () => {
- const thenExports = {
- then: function then() {
- return 'and then';
- },
- };
-
- function Print({response}) {
- return
{use(response)}
;
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const ThenRef = clientExports(thenExports).then;
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
and then
');
- });
-
- it('throws when accessing a member below the client exports', () => {
- const ClientModule = clientExports({
- Component: {deep: 'thing'},
- });
- function dotting() {
- return ClientModule.Component.deep;
- }
- expect(dotting).toThrowError(
- 'Cannot access Component.deep on the server. ' +
- 'You cannot dot into a client module from a server component. ' +
- 'You can only pass the imported name through.',
- );
- });
-
- it('does not throw when React inspects any deep props', () => {
- const ClientModule = clientExports({
- Component: function () {},
- });
- ;
- });
-
- it('throws when accessing a Context.Provider below the client exports', () => {
- const Context = React.createContext();
- const ClientModule = clientExports({
- Context,
- });
- function dotting() {
- return ClientModule.Context.Provider;
- }
- expect(dotting).toThrowError(
- `Cannot render a Client Context Provider on the Server. ` +
- `Instead, you can export a Client Component wrapper ` +
- `that itself renders a Client Context Provider.`,
- );
- });
-
- it('should progressively reveal server components', async () => {
- let reportedErrors = [];
-
- // Client Components
-
- function MyErrorBoundary({children}) {
- return (
- (
-
- {__DEV__ ? e.message + ' + ' : null}
- {e.digest}
-
- )}>
- {children}
-
- );
- }
-
- // Model
- function Text({children}) {
- return children;
- }
-
- function makeDelayedText() {
- let _resolve, _reject;
- let promise = new Promise((resolve, reject) => {
- _resolve = () => {
- promise = null;
- resolve();
- };
- _reject = e => {
- promise = null;
- reject(e);
- };
- });
- async function DelayedText({children}) {
- await promise;
- return {children} ;
- }
- return [DelayedText, _resolve, _reject];
- }
-
- const [Friends, resolveFriends] = makeDelayedText();
- const [Name, resolveName] = makeDelayedText();
- const [Posts, resolvePosts] = makeDelayedText();
- const [Photos, resolvePhotos] = makeDelayedText();
- const [Games, , rejectGames] = makeDelayedText();
-
- // View
- function ProfileDetails({avatar}) {
- return (
-
- :name:
- {avatar}
-
- );
- }
- function ProfileSidebar({friends}) {
- return (
-
- );
- }
- function ProfilePosts({posts}) {
- return
{posts}
;
- }
- function ProfileGames({games}) {
- return
{games}
;
- }
-
- const MyErrorBoundaryClient = clientExports(MyErrorBoundary);
-
- function ProfileContent() {
- return (
- <>
- :avatar:} />
- (loading sidebar)
}>
- :friends:} />
-
- (loading posts)
}>
- :posts:} />
-
-
- (loading games)
}>
- :games:} />
-
-
- >
- );
- }
-
- const model = {
- rootContent: ,
- };
-
- function ProfilePage({response}) {
- return use(response).rootContent;
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- model,
- turbopackMap,
- {
- onError(x) {
- reportedErrors.push(x);
- return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
- },
- },
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render(
- (loading)
}>
-
- ,
- );
- });
- expect(container.innerHTML).toBe('
(loading)
');
-
- // This isn't enough to show anything.
- await act(() => {
- resolveFriends();
- });
- expect(container.innerHTML).toBe('
(loading)
');
-
- // We can now show the details. Sidebar and posts are still loading.
- await act(() => {
- resolveName();
- });
- // Advance time enough to trigger a nested fallback.
- await act(() => {
- jest.advanceTimersByTime(500);
- });
- expect(container.innerHTML).toBe(
- '
:name::avatar:
' +
- '
(loading sidebar)
' +
- '
(loading posts)
' +
- '
(loading games)
',
- );
-
- expect(reportedErrors).toEqual([]);
-
- const theError = new Error('Game over');
- // Let's *fail* loading games.
- await act(async () => {
- await rejectGames(theError);
- await 'the inner async function';
- });
- const expectedGamesValue = __DEV__
- ? '
Game over + a dev digest
'
- : '
digest("Game over")
';
- expect(container.innerHTML).toBe(
- '
:name::avatar:
' +
- '
(loading sidebar)
' +
- '
(loading posts)
' +
- expectedGamesValue,
- );
-
- expect(reportedErrors).toEqual([theError]);
- reportedErrors = [];
-
- // We can now show the sidebar.
- await act(async () => {
- await resolvePhotos();
- await 'the inner async function';
- });
- expect(container.innerHTML).toBe(
- '
:name::avatar:
' +
- '
:photos::friends:
' +
- '
(loading posts)
' +
- expectedGamesValue,
- );
-
- // Show everything.
- await act(async () => {
- await resolvePosts();
- await 'the inner async function';
- });
- expect(container.innerHTML).toBe(
- '
:name::avatar:
' +
- '
:photos::friends:
' +
- '
:posts:
' +
- expectedGamesValue,
- );
-
- expect(reportedErrors).toEqual([]);
- });
-
- it('should preserve state of client components on refetch', async () => {
- // Client
-
- function Page({response}) {
- return use(response);
- }
-
- function Input() {
- return ;
- }
-
- const InputClient = clientExports(Input);
-
- // Server
-
- function App({color}) {
- // Verify both DOM and Client children.
- return (
-
-
-
-
- );
- }
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
-
- const stream1 = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(stream1.writable);
- const response1 = ReactServerDOMClient.createFromReadableStream(
- stream1.readable,
- );
- await act(() => {
- root.render(
- (loading)
}>
-
- ,
- );
- });
- expect(container.children.length).toBe(1);
- expect(container.children[0].tagName).toBe('DIV');
- expect(container.children[0].style.color).toBe('red');
-
- // Change the DOM state for both inputs.
- const inputA = container.children[0].children[0];
- expect(inputA.tagName).toBe('INPUT');
- inputA.value = 'hello';
- const inputB = container.children[0].children[1];
- expect(inputB.tagName).toBe('INPUT');
- inputB.value = 'goodbye';
-
- const stream2 = getTestStream();
- const {pipe: pipe2} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe2(stream2.writable);
- const response2 = ReactServerDOMClient.createFromReadableStream(
- stream2.readable,
- );
- await act(() => {
- root.render(
- (loading)
}>
-
- ,
- );
- });
- expect(container.children.length).toBe(1);
- expect(container.children[0].tagName).toBe('DIV');
- expect(container.children[0].style.color).toBe('blue');
-
- // Verify we didn't destroy the DOM for either input.
- expect(inputA === container.children[0].children[0]).toBe(true);
- expect(inputA.tagName).toBe('INPUT');
- expect(inputA.value).toBe('hello');
- expect(inputB === container.children[0].children[1]).toBe(true);
- expect(inputB.tagName).toBe('INPUT');
- expect(inputB.value).toBe('goodbye');
- });
-
- it('should be able to complete after aborting and throw the reason client-side', async () => {
- const reportedErrors = [];
-
- const {writable, readable} = getTestStream();
- const {pipe, abort} = ReactServerDOMServer.renderToPipeableStream(
-
-
-
,
- turbopackMap,
- {
- onError(x) {
- reportedErrors.push(x);
- const message = typeof x === 'string' ? x : x.message;
- return __DEV__ ? 'a dev digest' : `digest("${message}")`;
- },
- },
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
-
- function App({res}) {
- return use(res);
- }
-
- await act(() => {
- root.render(
- (
-
- {__DEV__ ? e.message + ' + ' : null}
- {e.digest}
-
- )}>
- (loading)
}>
-
-
- ,
- );
- });
- expect(container.innerHTML).toBe('
(loading)
');
-
- await act(() => {
- abort('for reasons');
- });
- if (__DEV__) {
- expect(container.innerHTML).toBe(
- '
Error: for reasons + a dev digest
',
- );
- } else {
- expect(container.innerHTML).toBe('
digest("for reasons")
');
- }
-
- expect(reportedErrors).toEqual(['for reasons']);
- });
-
- it('should be able to recover from a direct reference erroring client-side', async () => {
- const reportedErrors = [];
-
- const ClientComponent = clientExports(function ({prop}) {
- return 'This should never render';
- });
-
- const ClientReference = clientModuleError(new Error('module init error'));
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
-
-
-
,
- turbopackMap,
- {
- onError(x) {
- reportedErrors.push(x);
- },
- },
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
-
- function App({res}) {
- return use(res);
- }
-
- await act(() => {
- root.render(
- {e.message}
}>
- (loading)
}>
-
-
- ,
- );
- });
- expect(container.innerHTML).toBe('
module init error
');
-
- expect(reportedErrors).toEqual([]);
- });
-
- it('should be able to recover from a direct reference erroring client-side async', async () => {
- const reportedErrors = [];
-
- const ClientComponent = clientExports(function ({prop}) {
- return 'This should never render';
- });
-
- let rejectPromise;
- const ClientReference = await clientExports(
- new Promise((resolve, reject) => {
- rejectPromise = reject;
- }),
- );
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
-
-
-
,
- turbopackMap,
- {
- onError(x) {
- reportedErrors.push(x);
- },
- },
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
-
- function App({res}) {
- return use(res);
- }
-
- await act(() => {
- root.render(
- {e.message}
}>
- (loading)
}>
-
-
- ,
- );
- });
-
- expect(container.innerHTML).toBe('
(loading)
');
-
- await act(() => {
- rejectPromise(new Error('async module init error'));
- });
-
- expect(container.innerHTML).toBe('
async module init error
');
-
- expect(reportedErrors).toEqual([]);
- });
-
- it('should be able to recover from a direct reference erroring server-side', async () => {
- const reportedErrors = [];
-
- const ClientComponent = clientExports(function ({prop}) {
- return 'This should never render';
- });
-
- // We simulate a bug in the Webpack bundler which causes an error on the server.
- for (const id in turbopackMap) {
- Object.defineProperty(turbopackMap, id, {
- get: () => {
- throw new Error('bug in the bundler');
- },
- });
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
-
-
-
,
- turbopackMap,
- {
- onError(x) {
- reportedErrors.push(x.message);
- return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
- },
- },
- );
- pipe(writable);
-
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
-
- function App({res}) {
- return use(res);
- }
-
- await act(() => {
- root.render(
- (
-
- {__DEV__ ? e.message + ' + ' : null}
- {e.digest}
-
- )}>
- (loading)
}>
-
-
- ,
- );
- });
- if (__DEV__) {
- expect(container.innerHTML).toBe(
- '
bug in the bundler + a dev digest
',
- );
- } else {
- expect(container.innerHTML).toBe('
digest("bug in the bundler")
');
- }
-
- expect(reportedErrors).toEqual(['bug in the bundler']);
- });
-
- it('should pass a Promise through props and be able use() it on the client', async () => {
- async function getData() {
- return 'async hello';
- }
-
- function Component({data}) {
- const text = use(data);
- return
{text}
;
- }
-
- const ClientComponent = clientExports(Component);
-
- function ServerComponent() {
- const data = getData(); // no await here
- return ;
- }
-
- function Print({response}) {
- return use(response);
- }
-
- function App({response}) {
- return (
- Loading...}>
-
-
- );
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe('
async hello
');
- });
-
- it('should throw on the client if a passed promise eventually rejects', async () => {
- const reportedErrors = [];
- const theError = new Error('Server throw');
-
- async function getData() {
- throw theError;
- }
-
- function Component({data}) {
- const text = use(data);
- return
{text}
;
- }
-
- const ClientComponent = clientExports(Component);
-
- function ServerComponent() {
- const data = getData(); // no await here
- return ;
- }
-
- function Await({response}) {
- return use(response);
- }
-
- function App({response}) {
- return (
- Loading...}>
- (
-
- {__DEV__ ? e.message + ' + ' : null}
- {e.digest}
-
- )}>
-
-
-
- );
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- {
- onError(x) {
- reportedErrors.push(x);
- return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
- },
- },
- );
- pipe(writable);
- const response = ReactServerDOMClient.createFromReadableStream(readable);
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
- expect(container.innerHTML).toBe(
- __DEV__
- ? '
Server throw + a dev digest
'
- : '
digest("Server throw")
',
- );
- expect(reportedErrors).toEqual([theError]);
- });
-
- it('should support float methods when rendering in Fiber', async () => {
- function Component() {
- return
hello world
;
- }
-
- const ClientComponent = clientExports(Component);
-
- async function ServerComponent() {
- FlightReactDOM.prefetchDNS('d before');
- FlightReactDOM.preconnect('c before');
- FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
- FlightReactDOM.preload('l before', {as: 'style'});
- FlightReactDOM.preloadModule('lm before');
- FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
- FlightReactDOM.preinit('i before', {as: 'script'});
- FlightReactDOM.preinitModule('m before');
- FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
- await 1;
- FlightReactDOM.prefetchDNS('d after');
- FlightReactDOM.preconnect('c after');
- FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
- FlightReactDOM.preload('l after', {as: 'style'});
- FlightReactDOM.preloadModule('lm after');
- FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
- FlightReactDOM.preinit('i after', {as: 'script'});
- FlightReactDOM.preinitModule('m after');
- FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
- return ;
- }
-
- const {writable, readable} = getTestStream();
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(writable);
-
- let response = null;
- function getResponse() {
- if (response === null) {
- response = ReactServerDOMClient.createFromReadableStream(readable);
- }
- return response;
- }
-
- function App() {
- return getResponse();
- }
-
- // We pause to allow the float call after the await point to process before the
- // HostDispatcher gets set for Fiber by createRoot. This is only needed in testing
- // because the module graphs are not different and the HostDispatcher is shared.
- // In a real environment the Fiber and Flight code would each have their own independent
- // dispatcher.
- // @TODO consider what happens when Server-Components-On-The-Client exist. we probably
- // want to use the Fiber HostDispatcher there too since it is more about the host than the runtime
- // but we need to make sure that actually makes sense
- await 1;
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(() => {
- root.render( );
- });
-
- expect(getMeaningfulChildren(document)).toEqual(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
- );
- expect(getMeaningfulChildren(container)).toEqual(
hello world
);
- });
-
- it('should support float methods when rendering in Fizz', async () => {
- function Component() {
- return
hello world
;
- }
-
- const ClientComponent = clientExports(Component);
-
- async function ServerComponent() {
- FlightReactDOM.prefetchDNS('d before');
- FlightReactDOM.preconnect('c before');
- FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
- FlightReactDOM.preload('l before', {as: 'style'});
- FlightReactDOM.preloadModule('lm before');
- FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
- FlightReactDOM.preinit('i before', {as: 'script'});
- FlightReactDOM.preinitModule('m before');
- FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
- await 1;
- FlightReactDOM.prefetchDNS('d after');
- FlightReactDOM.preconnect('c after');
- FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
- FlightReactDOM.preload('l after', {as: 'style'});
- FlightReactDOM.preloadModule('lm after');
- FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
- FlightReactDOM.preinit('i after', {as: 'script'});
- FlightReactDOM.preinitModule('m after');
- FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
- return ;
- }
-
- const {writable: flightWritable, readable: flightReadable} =
- getTestStream();
- const {writable: fizzWritable, readable: fizzReadable} = getTestStream();
-
- // In a real environment you would want to call the render during the Fizz render.
- // The reason we cannot do this in our test is because we don't actually have two separate
- // module graphs and we are contriving the sequencing to work in a way where
- // the right HostDispatcher is in scope during the Flight Server Float calls and the
- // Flight Client hint dispatches
- const {pipe} = ReactServerDOMServer.renderToPipeableStream(
- ,
- turbopackMap,
- );
- pipe(flightWritable);
-
- let response = null;
- function getResponse() {
- if (response === null) {
- response =
- ReactServerDOMClient.createFromReadableStream(flightReadable);
- }
- return response;
- }
-
- function App() {
- return (
-
-
,
+ };
+ return model;
+ }
+
+ const {writable, readable} = getTestStream();
+ const {pipe} = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ turbopackMap,
+ );
+ pipe(writable);
+ const response = ReactServerDOMClient.createFromReadableStream(readable);
+ const model = await response;
+ expect(model).toEqual({
+ html: (
+
+ hello
+ world
+
+ ),
+ });
+ });
+
+ it('should resolve the root', async () => {
+ // Model
+ function Text({children}) {
+ return {children} ;
+ }
+ function HTML() {
+ return (
+
+ hello
+ world
+
+ );
+ }
+ function RootModel() {
+ return {
+ html:
,
+ };
+ }
+
+ // View
+ function Message({response}) {
+ return ;
+ }
+ function App({response}) {
+ return (
+ Loading...}>
+
+
+ );
+ }
+
+ const {writable, readable} = getTestStream();
+ const {pipe} = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ turbopackMap,
+ );
+ pipe(writable);
+ const response = ReactServerDOMClient.createFromReadableStream(readable);
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+ expect(container.innerHTML).toBe(
+ '',
+ );
+ });
+
+ it('should unwrap async module references', async () => {
+ const AsyncModule = Promise.resolve(function AsyncModule({text}) {
+ return 'Async: ' + text;
+ });
+
+ const AsyncModule2 = Promise.resolve({
+ exportName: 'Module',
+ });
+
+ function Print({response}) {
+ return
{use(response)}
;
+ }
+
+ function App({response}) {
+ return (
+ Loading...}>
+
+
+ );
+ }
+
+ const AsyncModuleRef = await clientExports(AsyncModule);
+ const AsyncModuleRef2 = await clientExports(AsyncModule2);
+
+ const {writable, readable} = getTestStream();
+ const {pipe} = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ turbopackMap,
+ );
+ pipe(writable);
+ const response = ReactServerDOMClient.createFromReadableStream(readable);
+
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+ expect(container.innerHTML).toBe('
Async: Module
');
+ });
+});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMBrowser-test.js
similarity index 100%
rename from packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
rename to packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMBrowser-test.js
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMEdge-test.js
similarity index 52%
rename from packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
rename to packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMEdge-test.js
index 3538b4f7a60d9..d0d14915bcda8 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMEdge-test.js
@@ -54,37 +54,6 @@ describe('ReactFlightDOMEdge', () => {
use = React.use;
});
- function passThrough(stream) {
- // Simulate more realistic network by splitting up and rejoining some chunks.
- // This lets us test that we don't accidentally rely on particular bounds of the chunks.
- return new ReadableStream({
- async start(controller) {
- const reader = stream.getReader();
- let prevChunk = new Uint8Array(0);
- function push() {
- reader.read().then(({done, value}) => {
- if (done) {
- controller.enqueue(prevChunk);
- controller.close();
- return;
- }
- const chunk = new Uint8Array(prevChunk.length + value.length);
- chunk.set(prevChunk, 0);
- chunk.set(value, prevChunk.length);
- if (chunk.length > 50) {
- controller.enqueue(chunk.subarray(0, chunk.length - 50));
- prevChunk = chunk.subarray(chunk.length - 50);
- } else {
- prevChunk = chunk;
- }
- push();
- });
- }
- push();
- },
- });
- }
-
async function readResult(stream) {
const reader = stream.getReader();
let result = '';
@@ -144,68 +113,4 @@ describe('ReactFlightDOMEdge', () => {
const result = await readResult(ssrStream);
expect(result).toEqual('Client Component ');
});
-
- it('should encode long string in a compact format', async () => {
- const testString = '"\n\t'.repeat(500) + '🙃';
- const testString2 = 'hello'.repeat(400);
-
- const stream = ReactServerDOMServer.renderToReadableStream({
- text: testString,
- text2: testString2,
- });
- const [stream1, stream2] = passThrough(stream).tee();
-
- const serializedContent = await readResult(stream1);
- // The content should be compact an unescaped
- expect(serializedContent.length).toBeLessThan(4000);
- expect(serializedContent).not.toContain('\\n');
- expect(serializedContent).not.toContain('\\t');
- expect(serializedContent).not.toContain('\\"');
- expect(serializedContent).toContain('\t');
-
- const result = await ReactServerDOMClient.createFromReadableStream(
- stream2,
- {
- ssrManifest: {
- moduleMap: null,
- moduleLoading: null,
- },
- },
- );
- // Should still match the result when parsed
- expect(result.text).toBe(testString);
- expect(result.text2).toBe(testString2);
- });
-
- // @gate enableBinaryFlight
- it('should be able to serialize any kind of typed array', async () => {
- const buffer = new Uint8Array([
- 123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
- ]).buffer;
- const buffers = [
- buffer,
- new Int8Array(buffer, 1),
- new Uint8Array(buffer, 2),
- new Uint8ClampedArray(buffer, 2),
- new Int16Array(buffer, 2),
- new Uint16Array(buffer, 2),
- new Int32Array(buffer, 4),
- new Uint32Array(buffer, 4),
- new Float32Array(buffer, 4),
- new Float64Array(buffer, 0),
- new BigInt64Array(buffer, 0),
- new BigUint64Array(buffer, 0),
- new DataView(buffer, 3),
- ];
- const stream = passThrough(
- ReactServerDOMServer.renderToReadableStream(buffers),
- );
- const result = await ReactServerDOMClient.createFromReadableStream(stream, {
- ssrManifest: {
- moduleMap: null,
- moduleLoading: null,
- },
- });
- expect(result).toEqual(buffers);
- });
});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMForm-test.js
similarity index 58%
rename from packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
rename to packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMForm-test.js
index 176003eb4c822..ad7e0d0feed76 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMForm-test.js
@@ -145,113 +145,4 @@ describe('ReactFlightDOMForm', () => {
expect(result).toBe('hello');
expect(foo).toBe('bar');
});
-
- // @gate enableFormActions
- it('can submit an imported server action without hydrating it', async () => {
- let foo = null;
-
- const ServerModule = serverExports(function action(formData) {
- foo = formData.get('foo');
- return 'hi';
- });
- const serverAction = ReactServerDOMClient.createServerReference(
- ServerModule.$$id,
- );
- function App() {
- return (
-
- );
- }
-
- const ssrStream = await ReactDOMServer.renderToReadableStream( );
- await readIntoContainer(ssrStream);
-
- const form = container.firstChild;
-
- expect(foo).toBe(null);
-
- const result = await submit(form);
-
- expect(result).toBe('hi');
-
- expect(foo).toBe('bar');
- });
-
- // @gate enableFormActions
- it('can submit a complex closure server action without hydrating it', async () => {
- let foo = null;
-
- const serverAction = serverExports(function action(bound, formData) {
- foo = formData.get('foo') + bound.complex;
- return 'hello';
- });
- function App() {
- return (
-
- );
- }
- const rscStream = ReactServerDOMServer.renderToReadableStream( );
- const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
- ssrManifest: {
- moduleMap: null,
- moduleLoading: null,
- },
- });
- const ssrStream = await ReactDOMServer.renderToReadableStream(response);
- await readIntoContainer(ssrStream);
-
- const form = container.firstChild;
-
- expect(foo).toBe(null);
-
- const result = await submit(form);
-
- expect(result).toBe('hello');
- expect(foo).toBe('barobject');
- });
-
- // @gate enableFormActions
- it('can submit a multiple complex closure server action without hydrating it', async () => {
- let foo = null;
-
- const serverAction = serverExports(function action(bound, formData) {
- foo = formData.get('foo') + bound.complex;
- return 'hello' + bound.complex;
- });
- function App() {
- return (
-
- );
- }
- const rscStream = ReactServerDOMServer.renderToReadableStream( );
- const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
- ssrManifest: {
- moduleMap: null,
- moduleLoading: null,
- },
- });
- const ssrStream = await ReactDOMServer.renderToReadableStream(response);
- await readIntoContainer(ssrStream);
-
- const form = container.firstChild;
-
- expect(foo).toBe(null);
-
- const result = await submit(form.getElementsByTagName('button')[1]);
-
- expect(result).toBe('helloc');
- expect(foo).toBe('barc');
- });
});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js
new file mode 100644
index 0000000000000..a3068003db864
--- /dev/null
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js
@@ -0,0 +1,132 @@
+/**
+ * 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';
+
+// Don't wait before processing work on the server.
+// TODO: we can replace this with FlightServer.act().
+global.setImmediate = cb => cb();
+
+let clientExports;
+let turbopackMap;
+let turbopackModules;
+let turbopackModuleLoading;
+let React;
+let ReactDOMServer;
+let ReactServerDOMServer;
+let ReactServerDOMClient;
+let Stream;
+let use;
+
+describe('ReactFlightDOMNode', () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ // Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
+ jest.mock('react-server-dom-turbopack/server', () =>
+ require('react-server-dom-turbopack/server.node'),
+ );
+ ReactServerDOMServer = require('react-server-dom-turbopack/server');
+
+ const TurbopackMock = require('./utils/TurbopackMock');
+ clientExports = TurbopackMock.clientExports;
+ turbopackMap = TurbopackMock.turbopackMap;
+ turbopackModules = TurbopackMock.turbopackModules;
+ turbopackModuleLoading = TurbopackMock.moduleLoading;
+
+ jest.resetModules();
+ __unmockReact();
+ jest.unmock('react-server-dom-turbopack/server');
+ jest.mock('react-server-dom-turbopack/client', () =>
+ require('react-server-dom-turbopack/client.node'),
+ );
+
+ React = require('react');
+ ReactDOMServer = require('react-dom/server.node');
+ ReactServerDOMClient = require('react-server-dom-turbopack/client');
+ Stream = require('stream');
+ use = React.use;
+ });
+
+ function readResult(stream) {
+ return new Promise((resolve, reject) => {
+ let buffer = '';
+ const writable = new Stream.PassThrough();
+ writable.setEncoding('utf8');
+ writable.on('data', chunk => {
+ buffer += chunk;
+ });
+ writable.on('error', error => {
+ reject(error);
+ });
+ writable.on('end', () => {
+ resolve(buffer);
+ });
+ stream.pipe(writable);
+ });
+ }
+
+ it('should allow an alternative module mapping to be used for SSR', async () => {
+ function ClientComponent() {
+ return Client Component ;
+ }
+ // The Client build may not have the same IDs as the Server bundles for the same
+ // component.
+ const ClientComponentOnTheClient = clientExports(
+ ClientComponent,
+ 'path/to/chunk.js',
+ );
+ const ClientComponentOnTheServer = clientExports(ClientComponent);
+
+ // In the SSR bundle this module won't exist. We simulate this by deleting it.
+ const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
+ delete turbopackModules[clientId];
+
+ // Instead, we have to provide a translation from the client meta data to the SSR
+ // meta data.
+ const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
+ const translationMap = {
+ [clientId]: {
+ '*': ssrMetadata,
+ },
+ };
+
+ function App() {
+ return ;
+ }
+
+ const stream = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ turbopackMap,
+ );
+ const readable = new Stream.PassThrough();
+
+ stream.pipe(readable);
+
+ let response;
+ function ClientRoot() {
+ if (!response) {
+ response = ReactServerDOMClient.createFromNodeStream(readable, {
+ moduleMap: translationMap,
+ moduleLoading: turbopackModuleLoading,
+ });
+ }
+ return use(response);
+ }
+
+ const ssrStream = await ReactDOMServer.renderToPipeableStream(
+ ,
+ );
+ const result = await readResult(ssrStream);
+ expect(result).toEqual(
+ 'Client Component ',
+ );
+ });
+});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReply-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReply-test.js
new file mode 100644
index 0000000000000..f089789598850
--- /dev/null
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReply-test.js
@@ -0,0 +1,48 @@
+/**
+ * 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';
+
+// Polyfills for test environment
+global.ReadableStream =
+ require('web-streams-polyfill/ponyfill/es6').ReadableStream;
+global.TextEncoder = require('util').TextEncoder;
+global.TextDecoder = require('util').TextDecoder;
+
+// let serverExports;
+let turbopackServerMap;
+let ReactServerDOMServer;
+let ReactServerDOMClient;
+
+describe('ReactFlightDOMReply', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ // Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
+ jest.mock('react-server-dom-turbopack/server', () =>
+ require('react-server-dom-turbopack/server.browser'),
+ );
+ const TurbopackMock = require('./utils/TurbopackMock');
+ // serverExports = TurbopackMock.serverExports;
+ turbopackServerMap = TurbopackMock.turbopackServerMap;
+ ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
+ jest.resetModules();
+ ReactServerDOMClient = require('react-server-dom-turbopack/client');
+ });
+
+ it('can encode a reply', async () => {
+ const body = await ReactServerDOMClient.encodeReply({some: 'object'});
+ const decoded = await ReactServerDOMServer.decodeReply(
+ body,
+ turbopackServerMap,
+ );
+
+ expect(decoded).toEqual({some: 'object'});
+ });
+});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReplyEdge-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReplyEdge-test.js
similarity index 100%
rename from packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReplyEdge-test.js
rename to packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReplyEdge-test.js