'use strict';
let clientExports;
let turbopackMap;
let turbopackModules;
let React;
let ReactServer;
let ReactDOMServer;
let ReactServerDOMServer;
let ReactServerDOMClient;
let use;
let serverAct;
describe('ReactFlightTurbopackDOMEdge', () => {
beforeEach(() => {
jest.resetModules();
serverAct = require('internal-test-utils').serverAct;
jest.mock('react', () => require('react/react.react-server'));
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.edge'),
);
const TurbopackMock = require('./utils/TurbopackMock');
clientExports = TurbopackMock.clientExports;
turbopackMap = TurbopackMock.turbopackMap;
turbopackModules = TurbopackMock.turbopackModules;
ReactServer = require('react');
ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
jest.resetModules();
__unmockReact();
React = require('react');
ReactDOMServer = require('react-dom/server.edge');
ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
use = React.use;
});
async function readResult(stream) {
const reader = stream.getReader();
let result = '';
while (true) {
const {done, value} = await reader.read();
if (done) {
return result;
}
result += Buffer.from(value).toString('utf8');
}
}
function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
})
);
}
function createDelayedStream(
stream: ReadableStream<Uint8Array>,
): ReadableStream<Uint8Array> {
return new ReadableStream({
async start(controller) {
const reader = stream.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) {
controller.close();
} else {
await new Promise(resolve => setTimeout(resolve));
controller.enqueue(value);
}
}
},
});
}
it('should allow an alternative module mapping to be used for SSR', async () => {
function ClientComponent() {
return <span>Client Component</span>;
}
const ClientComponentOnTheClient = clientExports(ClientComponent);
const ClientComponentOnTheServer = clientExports(ClientComponent);
const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
delete turbopackModules[clientId];
const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
const translationMap = {
[clientId]: {
'*': ssrMetadata,
},
};
function App() {
return <ClientComponentOnTheClient />;
}
const stream = await serverAct(() =>
ReactServerDOMServer.renderToReadableStream(<App />, turbopackMap),
);
const response = ReactServerDOMClient.createFromReadableStream(stream, {
serverConsumerManifest: {
moduleMap: translationMap,
moduleLoading: null,
},
});
function ClientRoot() {
return use(response);
}
const ssrStream = await serverAct(() =>
ReactDOMServer.renderToReadableStream(<ClientRoot />),
);
const result = await readResult(ssrStream);
expect(result).toEqual('<span>Client Component</span>');
});
it('can transport debug info through a separate debug channel', async () => {
function Thrower() {
throw new Error('ssr-throw');
}
const ClientComponentOnTheClient = clientExports(
Thrower,
123,
'path/to/chunk.js',
);
const ClientComponentOnTheServer = clientExports(Thrower);
function App() {
return ReactServer.createElement(
ReactServer.Suspense,
null,
ReactServer.createElement(ClientComponentOnTheClient, null),
);
}
let debugReadableStreamController;
const debugReadableStream = new ReadableStream({
start(controller) {
debugReadableStreamController = controller;
},
});
const rscStream = await serverAct(() =>
ReactServerDOMServer.renderToReadableStream(
ReactServer.createElement(App, null),
turbopackMap,
{
debugChannel: {
writable: new WritableStream({
write(chunk) {
debugReadableStreamController.enqueue(chunk);
},
close() {
debugReadableStreamController.close();
},
}),
},
},
),
);
function ClientRoot({response}) {
return use(response);
}
const serverConsumerManifest = {
moduleMap: {
[turbopackMap[ClientComponentOnTheClient.$$id].id]: {
'*': turbopackMap[ClientComponentOnTheServer.$$id],
},
},
moduleLoading: null,
};
const response = ReactServerDOMClient.createFromReadableStream(
createDelayedStream(rscStream),
{
serverConsumerManifest,
debugChannel: {readable: debugReadableStream},
},
);
let ownerStack;
const ssrStream = await serverAct(() =>
ReactDOMServer.renderToReadableStream(
<ClientRoot response={response} />,
{
onError(err, errorInfo) {
ownerStack = React.captureOwnerStack
? React.captureOwnerStack()
: null;
},
},
),
);
const result = await readResult(ssrStream);
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
expect(result).toContain(
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
);
});
it('can transport debug info through a slow debug channel', async () => {
function Thrower() {
throw new Error('ssr-throw');
}
const ClientComponentOnTheClient = clientExports(
Thrower,
123,
'path/to/chunk.js',
);
const ClientComponentOnTheServer = clientExports(Thrower);
function App() {
return ReactServer.createElement(
ReactServer.Suspense,
null,
ReactServer.createElement(ClientComponentOnTheClient, null),
);
}
let debugReadableStreamController;
const debugReadableStream = new ReadableStream({
start(controller) {
debugReadableStreamController = controller;
},
});
const rscStream = await serverAct(() =>
ReactServerDOMServer.renderToReadableStream(
ReactServer.createElement(App, null),
turbopackMap,
{
debugChannel: {
writable: new WritableStream({
write(chunk) {
debugReadableStreamController.enqueue(chunk);
},
close() {
debugReadableStreamController.close();
},
}),
},
},
),
);
function ClientRoot({response}) {
return use(response);
}
const serverConsumerManifest = {
moduleMap: {
[turbopackMap[ClientComponentOnTheClient.$$id].id]: {
'*': turbopackMap[ClientComponentOnTheServer.$$id],
},
},
moduleLoading: null,
};
const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
serverConsumerManifest,
debugChannel: {
readable:
createDelayedStream(debugReadableStream),
},
});
let ownerStack;
const ssrStream = await serverAct(() =>
ReactDOMServer.renderToReadableStream(
<ClientRoot response={response} />,
{
onError(err, errorInfo) {
ownerStack = React.captureOwnerStack
? React.captureOwnerStack()
: null;
},
},
),
);
const result = await readResult(ssrStream);
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
expect(result).toContain(
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
);
});
});