mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[Flight][Fizz] schedule work async (#29551)
While most builds of Flight and Fizz schedule work in new tasks some do execute work synchronously. While this is necessary for legacy APIs like renderToString for modern APIs there really isn't a great reason to do this synchronously. We could schedule works as microtasks but we actually want to yield so the runtime can run events and other things that will unblock additional work before starting the next work loop. This change updates all non-legacy uses to be async using the best availalble macrotask scheduler. Browser now uses postMessage Bun uses setTimeout because while it also supports setImmediate the scheduling is not as eager as the same API in node the FB build also uses setTimeout This change required a number of changes to tests which were utilizing the sync nature of work in the Browser builds to avoid having to manage timers and tasks. I added a patch to install MessageChannel which is required by the browser builds and made this patched version integrate with the Scheduler mock. This way we can effectively use `act` to flush flight and fizz work similar to how we do this on the client.
This commit is contained in:
+22
-4
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
import {insertNodesAndExecuteScripts} from '../test-utils/FizzTestUtils';
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
@@ -21,12 +22,16 @@ let ReactDOMServer;
|
||||
let Scheduler;
|
||||
let assertLog;
|
||||
let container;
|
||||
let act;
|
||||
|
||||
describe('ReactClassComponentPropResolutionFizz', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
Scheduler = require('scheduler');
|
||||
patchMessageChannel(Scheduler);
|
||||
act = require('internal-test-utils').act;
|
||||
|
||||
React = require('react');
|
||||
ReactDOMServer = require('react-dom/server.browser');
|
||||
assertLog = require('internal-test-utils').assertLog;
|
||||
container = document.createElement('div');
|
||||
@@ -37,6 +42,17 @@ describe('ReactClassComponentPropResolutionFizz', () => {
|
||||
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 = '';
|
||||
@@ -57,7 +73,7 @@ describe('ReactClassComponentPropResolutionFizz', () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
test('resolves ref and default props before calling lifecycle methods', async () => {
|
||||
it('resolves ref and default props before calling lifecycle methods', async () => {
|
||||
function getPropKeys(props) {
|
||||
return Object.keys(props).join(', ');
|
||||
}
|
||||
@@ -80,11 +96,13 @@ describe('ReactClassComponentPropResolutionFizz', () => {
|
||||
};
|
||||
|
||||
// `ref` should never appear as a prop. `default` always should.
|
||||
|
||||
const ref = React.createRef();
|
||||
const stream = await ReactDOMServer.renderToReadableStream(
|
||||
<Component text="Yay" ref={ref} />,
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<Component text="Yay" ref={ref} />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
assertLog([
|
||||
'constructor: text, default',
|
||||
'componentWillMount: text, default',
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
insertNodesAndExecuteScripts,
|
||||
getVisibleChildren,
|
||||
} from '../test-utils/FizzTestUtils';
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
@@ -33,13 +34,14 @@ let Suspense;
|
||||
describe('ReactDOMFizzForm', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
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;
|
||||
act = require('internal-test-utils').act;
|
||||
assertLog = require('internal-test-utils').assertLog;
|
||||
waitForPaint = require('internal-test-utils').waitForPaint;
|
||||
container = document.createElement('div');
|
||||
@@ -50,6 +52,17 @@ describe('ReactDOMFizzForm', () => {
|
||||
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 = '';
|
||||
@@ -76,7 +89,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
return useDeferredValue('Final', 'Initial');
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
expect(container.textContent).toEqual('Initial');
|
||||
|
||||
@@ -107,7 +122,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
@@ -153,8 +170,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
|
||||
const cRef = React.createRef();
|
||||
|
||||
// The server renders using the "initial" value for B.
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
assertLog(['A', 'B [Initial]', 'C']);
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
|
||||
+57
-15
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
import {insertNodesAndExecuteScripts} from '../test-utils/FizzTestUtils';
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
@@ -24,10 +25,13 @@ let ReactDOMClient;
|
||||
let useFormStatus;
|
||||
let useOptimistic;
|
||||
let useActionState;
|
||||
let Scheduler;
|
||||
|
||||
describe('ReactDOMFizzForm', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
Scheduler = require('scheduler');
|
||||
patchMessageChannel(Scheduler);
|
||||
React = require('react');
|
||||
ReactDOMServer = require('react-dom/server.browser');
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
@@ -48,6 +52,14 @@ describe('ReactDOMFizzForm', () => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await act(() => {
|
||||
maybePromise = callback();
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
function submit(submitter) {
|
||||
const form = submitter.form || submitter;
|
||||
if (!submitter.form) {
|
||||
@@ -96,7 +108,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
await act(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
@@ -143,7 +157,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
await act(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
@@ -175,7 +191,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
@@ -197,7 +215,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
// This should ideally warn because only the client provides a function that doesn't line up.
|
||||
await act(async () => {
|
||||
@@ -231,7 +251,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
let root;
|
||||
await act(async () => {
|
||||
@@ -278,7 +300,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
let root;
|
||||
await act(async () => {
|
||||
@@ -334,7 +358,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
// Specifying the extra form fields are a DEV error, but we expect it
|
||||
// to eventually still be patched up after an update.
|
||||
await expect(async () => {
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
}).toErrorDev([
|
||||
'Cannot specify a encType or method for a form that specifies a function as the action.',
|
||||
@@ -379,7 +405,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
return 'Pending: ' + pending;
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
expect(container.textContent).toBe('Pending: false');
|
||||
|
||||
@@ -400,7 +428,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
// Dispatch an event before hydration
|
||||
@@ -441,7 +471,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
submit(container.getElementsByTagName('input')[1]);
|
||||
@@ -463,7 +495,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
return optimisticState;
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
expect(container.textContent).toBe('hi');
|
||||
|
||||
@@ -484,7 +518,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
return state;
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
expect(container.textContent).toBe('0');
|
||||
|
||||
@@ -521,7 +557,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
const form = container.firstChild;
|
||||
@@ -581,7 +619,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
const input = container.getElementsByTagName('input')[1];
|
||||
@@ -651,7 +691,9 @@ describe('ReactDOMFizzForm', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await ReactDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
await readIntoContainer(stream);
|
||||
|
||||
const barField = container.querySelector('[name=bar]');
|
||||
|
||||
+191
-148
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
@@ -17,15 +19,33 @@ global.TextEncoder = require('util').TextEncoder;
|
||||
let React;
|
||||
let ReactDOMFizzServer;
|
||||
let Suspense;
|
||||
let Scheduler;
|
||||
let act;
|
||||
|
||||
describe('ReactDOMFizzServerBrowser', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
Scheduler = require('scheduler');
|
||||
patchMessageChannel(Scheduler);
|
||||
act = require('internal-test-utils').act;
|
||||
|
||||
React = require('react');
|
||||
ReactDOMFizzServer = require('react-dom/server.browser');
|
||||
Suspense = React.Suspense;
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await act(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
const theError = new Error('This is an error');
|
||||
function Throw() {
|
||||
throw theError;
|
||||
@@ -48,18 +68,20 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
}
|
||||
|
||||
it('should call renderToReadableStream', async () => {
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>hello world</div>,
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(<div>hello world</div>),
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(`"<div>hello world</div>"`);
|
||||
});
|
||||
|
||||
it('should emit DOCTYPE at the root of the document', async () => {
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<html>
|
||||
<body>hello world</body>
|
||||
</html>,
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<html>
|
||||
<body>hello world</body>
|
||||
</html>,
|
||||
),
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
@@ -68,13 +90,12 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
});
|
||||
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>hello world</div>,
|
||||
{
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(<div>hello world</div>, {
|
||||
bootstrapScriptContent: 'INIT();',
|
||||
bootstrapScripts: ['init.js'],
|
||||
bootstrapModules: ['init.mjs'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
@@ -93,23 +114,22 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
return 'Done';
|
||||
}
|
||||
let isComplete = false;
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback="Loading">
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback="Loading">
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
),
|
||||
);
|
||||
|
||||
stream.allReady.then(() => (isComplete = true));
|
||||
|
||||
await jest.runAllTimers();
|
||||
expect(isComplete).toBe(false);
|
||||
// Resolve the loading.
|
||||
hasLoaded = true;
|
||||
await resolve();
|
||||
|
||||
await jest.runAllTimers();
|
||||
await serverAct(() => resolve());
|
||||
|
||||
expect(isComplete).toBe(true);
|
||||
|
||||
@@ -123,15 +143,17 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Throw />
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Throw />
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
@@ -144,17 +166,19 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<Throw />}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<Throw />}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
@@ -165,17 +189,19 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
|
||||
const reportedErrors = [];
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Throw />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Throw />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await readResult(stream);
|
||||
@@ -186,18 +212,20 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
it('should be able to complete by aborting even if the promise never resolves', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
controller.abort();
|
||||
@@ -211,20 +239,20 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
it('should reject if aborting before the shell is complete', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const promise = ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const promise = serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
const theReason = new Error('aborted for reasons');
|
||||
controller.abort(theReason);
|
||||
|
||||
@@ -249,16 +277,18 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
const streamPromise = ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const streamPromise = serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
let caughtError = null;
|
||||
@@ -277,18 +307,20 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
const theReason = new Error('aborted for reasons');
|
||||
controller.abort(theReason);
|
||||
|
||||
const promise = ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const promise = serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Technically we could still continue rendering the shell but currently the
|
||||
@@ -317,17 +349,19 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
return 'Done';
|
||||
}
|
||||
const errors = [];
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
stream.allReady.then(() => (isComplete = true));
|
||||
@@ -344,9 +378,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
]);
|
||||
|
||||
hasLoaded = true;
|
||||
resolve();
|
||||
|
||||
await jest.runAllTimers();
|
||||
await serverAct(() => resolve());
|
||||
|
||||
expect(rendered).toBe(false);
|
||||
expect(isComplete).toBe(true);
|
||||
@@ -366,14 +398,16 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
// as such for now. I don't think it needs to be maintained if in the future
|
||||
// the view sizes change or become dynamic becasue of the use of byobRequest
|
||||
let stream;
|
||||
stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<>
|
||||
<div>
|
||||
<span>{''}</span>
|
||||
</div>
|
||||
<div>{str492}</div>
|
||||
<div>{str492}</div>
|
||||
</>,
|
||||
stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<>
|
||||
<div>
|
||||
<span>{''}</span>
|
||||
</div>
|
||||
<div>{str492}</div>
|
||||
<div>{str492}</div>
|
||||
</>,
|
||||
),
|
||||
);
|
||||
|
||||
let result;
|
||||
@@ -385,10 +419,12 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
// this size 2049 was chosen to be a couple base 2 orders larger than the current view
|
||||
// size. if the size changes in the future hopefully this will still exercise
|
||||
// a chunk that is too large for the view size.
|
||||
stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<>
|
||||
<div>{str2049}</div>
|
||||
</>,
|
||||
stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<>
|
||||
<div>{str2049}</div>
|
||||
</>,
|
||||
),
|
||||
);
|
||||
|
||||
result = await readResult(stream);
|
||||
@@ -419,13 +455,15 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
await ReactDOMFizzServer.renderToReadableStream(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return 'a digest';
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
controller.abort('foobar');
|
||||
|
||||
@@ -456,13 +494,15 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
await ReactDOMFizzServer.renderToReadableStream(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
return 'a digest';
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
controller.abort(new Error('uh oh'));
|
||||
|
||||
@@ -471,13 +511,15 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
// https://github.com/facebook/react/pull/25534/files - fix transposed escape functions
|
||||
it('should encode title properly', async () => {
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<html>
|
||||
<head>
|
||||
<title>foo</title>
|
||||
</head>
|
||||
<body>bar</body>
|
||||
</html>,
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(
|
||||
<html>
|
||||
<head>
|
||||
<title>foo</title>
|
||||
</head>
|
||||
<body>bar</body>
|
||||
</html>,
|
||||
),
|
||||
);
|
||||
|
||||
const result = await readResult(stream);
|
||||
@@ -488,14 +530,13 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
it('should support nonce attribute for bootstrap scripts', async () => {
|
||||
const nonce = 'R4nd0m';
|
||||
const stream = await ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>hello world</div>,
|
||||
{
|
||||
const stream = await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(<div>hello world</div>, {
|
||||
nonce,
|
||||
bootstrapScriptContent: 'INIT();',
|
||||
bootstrapScripts: ['init.js'],
|
||||
bootstrapModules: ['init.mjs'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
@@ -523,14 +564,16 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzServer.renderToReadableStream(<App />, {
|
||||
onError(error) {
|
||||
errors.push(error.message);
|
||||
},
|
||||
onPostpone(reason) {
|
||||
postponed.push(reason);
|
||||
},
|
||||
});
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzServer.renderToReadableStream(<App />, {
|
||||
onError(error) {
|
||||
errors.push(error.message);
|
||||
},
|
||||
onPostpone(reason) {
|
||||
postponed.push(reason);
|
||||
},
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
|
||||
+315
-198
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
import {
|
||||
getVisibleChildren,
|
||||
insertNodesAndExecuteScripts,
|
||||
@@ -26,10 +28,17 @@ let ReactDOMFizzServer;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
let container;
|
||||
let Scheduler;
|
||||
let act;
|
||||
|
||||
describe('ReactDOMFizzStaticBrowser', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
Scheduler = require('scheduler');
|
||||
patchMessageChannel(Scheduler);
|
||||
act = require('internal-test-utils').act;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMFizzServer = require('react-dom/server.browser');
|
||||
@@ -45,6 +54,17 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await act(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
const theError = new Error('This is an error');
|
||||
function Throw() {
|
||||
throw theError;
|
||||
@@ -113,17 +133,21 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
// @gate experimental
|
||||
it('should call prerender', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerender(<div>hello world</div>);
|
||||
const result = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<div>hello world</div>),
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(`"<div>hello world</div>"`);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should emit DOCTYPE at the root of the document', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerender(
|
||||
<html>
|
||||
<body>hello world</body>
|
||||
</html>,
|
||||
const result = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<html>
|
||||
<body>hello world</body>
|
||||
</html>,
|
||||
),
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
@@ -133,11 +157,13 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
// @gate experimental
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerender(<div>hello world</div>, {
|
||||
bootstrapScriptContent: 'INIT();',
|
||||
bootstrapScripts: ['init.js'],
|
||||
bootstrapModules: ['init.mjs'],
|
||||
});
|
||||
const result = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<div>hello world</div>, {
|
||||
bootstrapScriptContent: 'INIT();',
|
||||
bootstrapScripts: ['init.js'],
|
||||
bootstrapModules: ['init.mjs'],
|
||||
}),
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
@@ -155,12 +181,14 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
}
|
||||
return 'Done';
|
||||
}
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback="Loading">
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
const resultPromise = serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback="Loading">
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
),
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
@@ -171,9 +199,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
const result = await resultPromise;
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<div><!--$-->Done<!-- --><!--/$--></div>"`,
|
||||
);
|
||||
expect(prelude).toMatchInlineSnapshot(`"<div><!--$-->Done<!--/$--></div>"`);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
@@ -181,15 +207,17 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Throw />
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Throw />
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
@@ -203,17 +231,19 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<Throw />}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<Throw />}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
@@ -225,17 +255,19 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
// @gate experimental
|
||||
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
|
||||
const reportedErrors = [];
|
||||
const result = await ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Throw />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
const result = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Throw />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const prelude = await readContent(result.prelude);
|
||||
@@ -247,21 +279,22 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
it('should be able to complete by aborting even if the promise never resolves', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
let resultPromise;
|
||||
await serverAct(() => {
|
||||
resultPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
);
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
|
||||
@@ -277,16 +310,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
it('should reject if aborting before the shell is complete', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const promise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const promise = serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
@@ -316,16 +351,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
const streamPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const streamPromise = serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
let caughtError = null;
|
||||
@@ -345,18 +382,20 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
const theReason = new Error('aborted for reasons');
|
||||
controller.abort(theReason);
|
||||
|
||||
const promise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const promise = serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Technically we could still continue rendering the shell but currently the
|
||||
@@ -396,12 +435,15 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return 'a digest';
|
||||
},
|
||||
let resultPromise;
|
||||
await serverAct(() => {
|
||||
resultPromise = ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
controller.abort('foobar');
|
||||
@@ -436,12 +478,15 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
return 'a digest';
|
||||
},
|
||||
let resultPromise;
|
||||
await serverAct(() => {
|
||||
resultPromise = ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
controller.abort(new Error('uh oh'));
|
||||
@@ -471,14 +516,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -513,14 +562,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -552,14 +605,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -600,14 +657,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -641,14 +702,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -682,14 +747,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
const html = await readContent(concat(prerendered.prelude, content));
|
||||
@@ -748,9 +817,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
{virtual: true},
|
||||
);
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />, {
|
||||
bootstrapScripts: ['init.js'],
|
||||
});
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />, {
|
||||
bootstrapScripts: ['init.js'],
|
||||
}),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -779,9 +850,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
]);
|
||||
|
||||
prerendering = false;
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(content);
|
||||
@@ -860,14 +933,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -911,14 +988,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -957,7 +1038,9 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
// 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.
|
||||
@@ -967,9 +1050,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Outer</div>);
|
||||
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(resumed);
|
||||
@@ -1020,7 +1105,9 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -1033,14 +1120,16 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
prerendering = false;
|
||||
|
||||
const errors = [];
|
||||
const resumed = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const resumed = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(errors).toEqual([
|
||||
@@ -1085,7 +1174,9 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -1098,15 +1189,17 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
const errors = [];
|
||||
|
||||
const resumedPromise = ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
const resumedPromise = serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
controller.abort('abort');
|
||||
@@ -1160,16 +1253,20 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const resumedPromise = ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const resumedPromise = serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
@@ -1204,16 +1301,20 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
expect(await readContent(prerendered.prelude)).toBe('');
|
||||
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
expect(await readContent(content)).toBe(
|
||||
@@ -1246,16 +1347,20 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
expect(await readContent(prerendered.prelude)).toBe('');
|
||||
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
expect(await readContent(content)).toBe(
|
||||
@@ -1293,16 +1398,20 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
|
||||
expect(await readContent(prerendered.prelude)).toBe('');
|
||||
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
expect(await readContent(content)).toBe(
|
||||
@@ -1356,9 +1465,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />, {
|
||||
onHeaders,
|
||||
});
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />, {
|
||||
onHeaders,
|
||||
}),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
prerendering = false;
|
||||
@@ -1375,9 +1486,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
@@ -1391,7 +1504,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
await 1;
|
||||
hasLoaded = true;
|
||||
resolve();
|
||||
await serverAct(resolve);
|
||||
|
||||
while (true) {
|
||||
({value, done} = await reader.read());
|
||||
@@ -1425,10 +1538,12 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
throw new Error('bad onHeaders');
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<div>hello</div>, {
|
||||
onHeaders,
|
||||
onError,
|
||||
});
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<div>hello</div>, {
|
||||
onHeaders,
|
||||
onError,
|
||||
}),
|
||||
);
|
||||
expect(prerendered.postponed).toBe(null);
|
||||
expect(errors).toEqual(['bad onHeaders']);
|
||||
|
||||
@@ -1469,9 +1584,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
{virtual: true},
|
||||
);
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />, {
|
||||
bootstrapScripts: ['init.js'],
|
||||
});
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />, {
|
||||
bootstrapScripts: ['init.js'],
|
||||
}),
|
||||
);
|
||||
|
||||
const postponedSerializedState = JSON.stringify(prerendered.postponed);
|
||||
|
||||
@@ -1497,9 +1614,8 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
prerendering = false;
|
||||
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(postponedSerializedState),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(<App />, JSON.parse(postponedSerializedState)),
|
||||
);
|
||||
|
||||
await readIntoContainer(content);
|
||||
@@ -1542,7 +1658,9 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
const postponedState = JSON.stringify(prerendered.postponed);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -1550,9 +1668,8 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
|
||||
isPrerendering = false;
|
||||
|
||||
const dynamic = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(postponedState),
|
||||
const dynamic = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(<App />, JSON.parse(postponedState)),
|
||||
);
|
||||
|
||||
await readIntoContainer(dynamic);
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
import {
|
||||
getVisibleChildren,
|
||||
insertNodesAndExecuteScripts,
|
||||
@@ -25,10 +27,16 @@ let ReactDOMFizzServer;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
let container;
|
||||
let Scheduler;
|
||||
let act;
|
||||
|
||||
describe('ReactDOMFizzStaticFloat', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
Scheduler = require('scheduler');
|
||||
patchMessageChannel(Scheduler);
|
||||
act = require('internal-test-utils').act;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMFizzServer = require('react-dom/server.browser');
|
||||
@@ -44,6 +52,17 @@ describe('ReactDOMFizzStaticFloat', () => {
|
||||
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 = '';
|
||||
@@ -135,7 +154,9 @@ describe('ReactDOMFizzStaticFloat', () => {
|
||||
virtual: true,
|
||||
});
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
const prerendered = await serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />),
|
||||
);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
@@ -171,28 +192,28 @@ describe('ReactDOMFizzStaticFloat', () => {
|
||||
]);
|
||||
|
||||
prerendering = false;
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
const content = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
),
|
||||
);
|
||||
|
||||
await readIntoContainer(content);
|
||||
|
||||
// Dispatch load event to injected stylesheet
|
||||
const linkCreds = document.querySelector(
|
||||
'link[rel="stylesheet"][href="style creds"]',
|
||||
);
|
||||
const linkAnon = document.querySelector(
|
||||
'link[rel="stylesheet"][href="style anon"]',
|
||||
);
|
||||
const event = document.createEvent('Events');
|
||||
event.initEvent('load', true, true);
|
||||
linkCreds.dispatchEvent(event);
|
||||
linkAnon.dispatchEvent(event);
|
||||
|
||||
// Wait for the instruction microtasks to flush.
|
||||
await 0;
|
||||
await 0;
|
||||
await act(() => {
|
||||
// Dispatch load event to injected stylesheet
|
||||
const linkCreds = document.querySelector(
|
||||
'link[rel="stylesheet"][href="style creds"]',
|
||||
);
|
||||
const linkAnon = document.querySelector(
|
||||
'link[rel="stylesheet"][href="style anon"]',
|
||||
);
|
||||
const event = document.createEvent('Events');
|
||||
event.initEvent('load', true, true);
|
||||
linkCreds.dispatchEvent(event);
|
||||
linkAnon.dispatchEvent(event);
|
||||
});
|
||||
|
||||
expect(getVisibleChildren(document)).toEqual(
|
||||
<html>
|
||||
|
||||
+28
-13
@@ -9,16 +9,14 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate';
|
||||
|
||||
// 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;
|
||||
@@ -29,6 +27,8 @@ let ReactDOMClient;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let Suspense;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOM', () => {
|
||||
beforeEach(() => {
|
||||
@@ -37,6 +37,10 @@ describe('ReactFlightDOM', () => {
|
||||
// condition
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchSetImmediate(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
require('react-server-dom-turbopack/server.node.unbundled'),
|
||||
@@ -61,6 +65,17 @@ describe('ReactFlightDOM', () => {
|
||||
ReactServerDOMClient = require('react-server-dom-turbopack/client');
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
function getTestStream() {
|
||||
const writable = new Stream.PassThrough();
|
||||
const readable = new ReadableStream({
|
||||
@@ -100,9 +115,8 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
turbopackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<App />, turbopackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -149,9 +163,8 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<RootModel />,
|
||||
turbopackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<RootModel />, turbopackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -191,9 +204,11 @@ describe('ReactFlightDOM', () => {
|
||||
const AsyncModuleRef2 = await clientExports(AsyncModule2);
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<AsyncModuleRef text={AsyncModuleRef2.exportName} />,
|
||||
turbopackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<AsyncModuleRef text={AsyncModuleRef2.exportName} />,
|
||||
turbopackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
|
||||
+22
-1
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
@@ -18,11 +20,17 @@ global.TextDecoder = require('util').TextDecoder;
|
||||
let React;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOMBrowser', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchMessageChannel(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
@@ -38,6 +46,17 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
ReactServerDOMClient = require('react-server-dom-turbopack/client');
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
it('should resolve HTML using W3C streams', async () => {
|
||||
function Text({children}) {
|
||||
return <span>{children}</span>;
|
||||
@@ -58,7 +77,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return model;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
const model = await response;
|
||||
expect(model).toEqual({
|
||||
|
||||
+22
-8
@@ -9,9 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// Don't wait before processing work on the server.
|
||||
// TODO: we can replace this with FlightServer.act().
|
||||
global.setImmediate = cb => cb();
|
||||
import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate';
|
||||
|
||||
let clientExports;
|
||||
let turbopackMap;
|
||||
@@ -23,11 +21,17 @@ let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let Stream;
|
||||
let use;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOMNode', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchSetImmediate(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
@@ -55,6 +59,17 @@ describe('ReactFlightDOMNode', () => {
|
||||
use = React.use;
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
function readResult(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = '';
|
||||
@@ -102,9 +117,8 @@ describe('ReactFlightDOMNode', () => {
|
||||
return <ClientComponentOnTheClient />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
turbopackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<App />, turbopackMap),
|
||||
);
|
||||
const readable = new Stream.PassThrough();
|
||||
|
||||
@@ -121,8 +135,8 @@ describe('ReactFlightDOMNode', () => {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot />,
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToPipeableStream(<ClientRoot />),
|
||||
);
|
||||
const result = await readResult(ssrStream);
|
||||
expect(result).toEqual(
|
||||
|
||||
+7
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
@@ -19,10 +21,15 @@ global.TextDecoder = require('util').TextDecoder;
|
||||
let turbopackServerMap;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let ReactServerScheduler;
|
||||
|
||||
describe('ReactFlightDOMReply', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchMessageChannel(ReactServerScheduler);
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
|
||||
+211
-153
@@ -9,16 +9,14 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate';
|
||||
|
||||
// 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;
|
||||
@@ -36,6 +34,8 @@ let ReactDOMStaticServer;
|
||||
let Suspense;
|
||||
let ErrorBoundary;
|
||||
let JSDOM;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOM', () => {
|
||||
beforeEach(() => {
|
||||
@@ -46,6 +46,10 @@ describe('ReactFlightDOM', () => {
|
||||
|
||||
JSDOM = require('jsdom').JSDOM;
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchSetImmediate(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
FlightReact = require('react');
|
||||
@@ -92,6 +96,17 @@ describe('ReactFlightDOM', () => {
|
||||
};
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
function getTestStream() {
|
||||
const writable = new Stream.PassThrough();
|
||||
const readable = new ReadableStream({
|
||||
@@ -181,9 +196,8 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<App />, webpackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -230,9 +244,8 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<RootModel />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<RootModel />, webpackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -266,9 +279,8 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<RootModel />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<RootModel />, webpackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -300,9 +312,8 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<RootModel />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<RootModel />, webpackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -349,9 +360,11 @@ describe('ReactFlightDOM', () => {
|
||||
);
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<Component greeting={hi} />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<Component greeting={hi} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -386,9 +399,11 @@ describe('ReactFlightDOM', () => {
|
||||
const {Component} = clientExports(Module);
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<Component greeting={'Hello'} />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<Component greeting={'Hello'} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -424,9 +439,11 @@ describe('ReactFlightDOM', () => {
|
||||
const {split: Component} = clientExports(Module);
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<Component greeting={'Hello'} />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<Component greeting={'Hello'} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -464,9 +481,11 @@ describe('ReactFlightDOM', () => {
|
||||
const AsyncModuleRef2 = await clientExports(AsyncModule2);
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<AsyncModuleRef text={AsyncModuleRef2.exportName} />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<AsyncModuleRef text={AsyncModuleRef2.exportName} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -502,9 +521,11 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -539,9 +560,8 @@ describe('ReactFlightDOM', () => {
|
||||
const ThenRef = clientExports(thenExports).then;
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<ThenRef />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<ThenRef />, webpackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -719,15 +739,13 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
model,
|
||||
webpackMap,
|
||||
{
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(model, webpackMap, {
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -744,14 +762,18 @@ describe('ReactFlightDOM', () => {
|
||||
expect(container.innerHTML).toBe('<p>(loading)</p>');
|
||||
|
||||
// This isn't enough to show anything.
|
||||
await act(() => {
|
||||
resolveFriends();
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
resolveFriends();
|
||||
});
|
||||
});
|
||||
expect(container.innerHTML).toBe('<p>(loading)</p>');
|
||||
|
||||
// We can now show the details. Sidebar and posts are still loading.
|
||||
await act(() => {
|
||||
resolveName();
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
resolveName();
|
||||
});
|
||||
});
|
||||
// Advance time enough to trigger a nested fallback.
|
||||
await act(() => {
|
||||
@@ -768,9 +790,11 @@ describe('ReactFlightDOM', () => {
|
||||
|
||||
const theError = new Error('Game over');
|
||||
// Let's *fail* loading games.
|
||||
await act(async () => {
|
||||
await rejectGames(theError);
|
||||
await 'the inner async function';
|
||||
await serverAct(async () => {
|
||||
await act(async () => {
|
||||
await rejectGames(theError);
|
||||
await 'the inner async function';
|
||||
});
|
||||
});
|
||||
const expectedGamesValue = __DEV__
|
||||
? '<p>Game over + a dev digest</p>'
|
||||
@@ -786,9 +810,11 @@ describe('ReactFlightDOM', () => {
|
||||
reportedErrors = [];
|
||||
|
||||
// We can now show the sidebar.
|
||||
await act(async () => {
|
||||
await resolvePhotos();
|
||||
await 'the inner async function';
|
||||
await serverAct(async () => {
|
||||
await act(async () => {
|
||||
await resolvePhotos();
|
||||
await 'the inner async function';
|
||||
});
|
||||
});
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div>:name::avatar:</div>' +
|
||||
@@ -798,9 +824,11 @@ describe('ReactFlightDOM', () => {
|
||||
);
|
||||
|
||||
// Show everything.
|
||||
await act(async () => {
|
||||
await resolvePosts();
|
||||
await 'the inner async function';
|
||||
await serverAct(async () => {
|
||||
await act(async () => {
|
||||
await resolvePosts();
|
||||
await 'the inner async function';
|
||||
});
|
||||
});
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div>:name::avatar:</div>' +
|
||||
@@ -867,14 +895,16 @@ describe('ReactFlightDOM', () => {
|
||||
const [Photos, resolvePhotosData] = makeDelayedText();
|
||||
const suspendedChunk = createSuspendedChunk(<p>loading</p>);
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
suspendedChunk.row,
|
||||
webpackMap,
|
||||
{
|
||||
onError(error) {
|
||||
reportedErrors.push(error);
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
suspendedChunk.row,
|
||||
webpackMap,
|
||||
{
|
||||
onError(error) {
|
||||
reportedErrors.push(error);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -900,16 +930,20 @@ describe('ReactFlightDOM', () => {
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
suspendedChunk.resolve({value, done: false, next: donePromise.promise});
|
||||
donePromise.resolve({value, done: true});
|
||||
await serverAct(async () => {
|
||||
await act(async () => {
|
||||
suspendedChunk.resolve({value, done: false, next: donePromise.promise});
|
||||
donePromise.resolve({value, done: true});
|
||||
});
|
||||
});
|
||||
|
||||
expect(container.innerHTML).toBe('<p>loading posts and photos</p>');
|
||||
|
||||
await act(async () => {
|
||||
await resolvePostsData('posts');
|
||||
await resolvePhotosData('photos');
|
||||
await serverAct(async () => {
|
||||
await act(async () => {
|
||||
await resolvePostsData('posts');
|
||||
await resolvePhotosData('photos');
|
||||
});
|
||||
});
|
||||
|
||||
expect(container.innerHTML).toBe('<div>posts</div><div>photos</div>');
|
||||
@@ -945,9 +979,11 @@ describe('ReactFlightDOM', () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
const stream1 = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App color="red" />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<App color="red" />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(stream1.writable);
|
||||
const response1 = ReactServerDOMClient.createFromReadableStream(
|
||||
@@ -973,9 +1009,11 @@ describe('ReactFlightDOM', () => {
|
||||
inputB.value = 'goodbye';
|
||||
|
||||
const stream2 = getTestStream();
|
||||
const {pipe: pipe2} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App color="blue" />,
|
||||
webpackMap,
|
||||
const {pipe: pipe2} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<App color="blue" />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe2(stream2.writable);
|
||||
const response2 = ReactServerDOMClient.createFromReadableStream(
|
||||
@@ -1005,18 +1043,20 @@ describe('ReactFlightDOM', () => {
|
||||
const reportedErrors = [];
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe, abort} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
const message = typeof x === 'string' ? x : x.message;
|
||||
return __DEV__ ? 'a dev digest' : `digest("${message}")`;
|
||||
const {pipe, abort} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
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);
|
||||
@@ -1067,16 +1107,18 @@ describe('ReactFlightDOM', () => {
|
||||
const ClientReference = clientModuleError(new Error('module init error'));
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent prop={ClientReference} />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent prop={ClientReference} />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -1117,16 +1159,18 @@ describe('ReactFlightDOM', () => {
|
||||
);
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent prop={ClientReference} />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent prop={ClientReference} />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -1176,17 +1220,19 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x.message);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x.message);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
|
||||
@@ -1255,9 +1301,11 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -1311,15 +1359,17 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
@@ -1368,9 +1418,11 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
|
||||
@@ -1463,9 +1515,8 @@ describe('ReactFlightDOM', () => {
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<App />, webpackMap),
|
||||
);
|
||||
pipe(writable);
|
||||
|
||||
@@ -1485,11 +1536,10 @@ describe('ReactFlightDOM', () => {
|
||||
function onError(error, errorInfo) {
|
||||
errors.push(error, errorInfo);
|
||||
}
|
||||
const result = await ReactDOMStaticServer.prerenderToNodeStream(
|
||||
<Response />,
|
||||
{
|
||||
const result = await serverAct(() =>
|
||||
ReactDOMStaticServer.prerenderToNodeStream(<Response />, {
|
||||
onError,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const prelude = await new Promise((resolve, reject) => {
|
||||
@@ -1554,9 +1604,11 @@ describe('ReactFlightDOM', () => {
|
||||
// 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(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(flightWritable);
|
||||
|
||||
@@ -1577,7 +1629,7 @@ describe('ReactFlightDOM', () => {
|
||||
);
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
await serverAct(async () => {
|
||||
ReactDOMFizzServer.renderToPipeableStream(<App />).pipe(fizzWritable);
|
||||
});
|
||||
|
||||
@@ -1680,11 +1732,11 @@ describe('ReactFlightDOM', () => {
|
||||
// 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;
|
||||
await serverAct(() => {});
|
||||
|
||||
const {writable: fizzWritable1, readable: fizzReadable1} = getTestStream();
|
||||
const {writable: fizzWritable2, readable: fizzReadable2} = getTestStream();
|
||||
await act(async () => {
|
||||
await serverAct(async () => {
|
||||
ReactDOMFizzServer.renderToPipeableStream(
|
||||
<App stream={flightReadable1} />,
|
||||
).pipe(fizzWritable1);
|
||||
@@ -1751,10 +1803,12 @@ describe('ReactFlightDOM', () => {
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
).pipe(writable);
|
||||
await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
).pipe(writable),
|
||||
);
|
||||
|
||||
const hintRows = [];
|
||||
async function collectHints(stream) {
|
||||
@@ -1798,16 +1852,18 @@ describe('ReactFlightDOM', () => {
|
||||
class InvalidValue {}
|
||||
|
||||
const {writable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent prop={ClientReference} invalid={InvalidValue} />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<div>
|
||||
<ClientComponent prop={ClientReference} invalid={InvalidValue} />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
|
||||
@@ -1839,9 +1895,11 @@ describe('ReactFlightDOM', () => {
|
||||
}
|
||||
|
||||
const {writable, readable} = getTestStream();
|
||||
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const {pipe} = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
pipe(writable);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(readable);
|
||||
|
||||
+312
-209
@@ -15,6 +15,10 @@ global.ReadableStream =
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
global.TextDecoder = require('util').TextDecoder;
|
||||
|
||||
const {
|
||||
patchMessageChannel,
|
||||
} = require('../../../../scripts/jest/patchMessageChannel');
|
||||
|
||||
let clientExports;
|
||||
let serverExports;
|
||||
let webpackMap;
|
||||
@@ -30,11 +34,18 @@ let Suspense;
|
||||
let use;
|
||||
let ReactServer;
|
||||
let ReactServerDOM;
|
||||
let Scheduler;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOMBrowser', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchMessageChannel(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
@@ -54,6 +65,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
__unmockReact();
|
||||
jest.resetModules();
|
||||
|
||||
Scheduler = require('scheduler');
|
||||
patchMessageChannel(Scheduler);
|
||||
|
||||
act = require('internal-test-utils').act;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
@@ -64,6 +78,17 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
use = React.use;
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
function makeDelayedText(Model) {
|
||||
let error, _resolve, _reject;
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
@@ -152,7 +177,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return model;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
const model = await response;
|
||||
expect(model).toEqual({
|
||||
@@ -185,7 +212,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return model;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<App />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<App />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
const model = await response;
|
||||
expect(model).toEqual({
|
||||
@@ -221,9 +250,8 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return <ClientOuter Component={ClientInner}>Hello, World!</ClientOuter>;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<Server />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<Server />, webpackMap),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
@@ -270,9 +298,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
const shared = [1, 2, 3];
|
||||
const value = [shared, shared];
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<Server value={value} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<Server value={value} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
@@ -319,9 +349,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
const shared = [1, 2, 3];
|
||||
const value = [shared, shared];
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<Server value={value} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<Server value={value} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
@@ -457,15 +489,13 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return use(response).rootContent;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
model,
|
||||
webpackMap,
|
||||
{
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(model, webpackMap, {
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? `a dev digest` : `digest("${x.message}")`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
@@ -481,14 +511,18 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
expect(container.innerHTML).toBe('<p>(loading)</p>');
|
||||
|
||||
// This isn't enough to show anything.
|
||||
await act(() => {
|
||||
resolveFriends();
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
resolveFriends();
|
||||
});
|
||||
});
|
||||
expect(container.innerHTML).toBe('<p>(loading)</p>');
|
||||
|
||||
// We can now show the details. Sidebar and posts are still loading.
|
||||
await act(() => {
|
||||
resolveName();
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
resolveName();
|
||||
});
|
||||
});
|
||||
// Advance time enough to trigger a nested fallback.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -503,8 +537,10 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
const theError = new Error('Game over');
|
||||
// Let's *fail* loading games.
|
||||
await act(() => {
|
||||
rejectGames(theError);
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
rejectGames(theError);
|
||||
});
|
||||
});
|
||||
|
||||
const gamesExpectedValue = __DEV__
|
||||
@@ -522,8 +558,10 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
reportedErrors = [];
|
||||
|
||||
// We can now show the sidebar.
|
||||
await act(() => {
|
||||
resolvePhotos();
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
resolvePhotos();
|
||||
});
|
||||
});
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div>:name::avatar:</div>' +
|
||||
@@ -533,8 +571,10 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
);
|
||||
|
||||
// Show everything.
|
||||
await act(() => {
|
||||
resolvePosts();
|
||||
await serverAct(async () => {
|
||||
await act(() => {
|
||||
resolvePosts();
|
||||
});
|
||||
});
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div>:name::avatar:</div>' +
|
||||
@@ -596,9 +636,8 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
rootContent: <ProfileContent />,
|
||||
};
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
model,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(model, webpackMap),
|
||||
);
|
||||
|
||||
const reader = stream.getReader();
|
||||
@@ -621,7 +660,7 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
// Advance time enough to trigger a nested fallback.
|
||||
jest.advanceTimersByTime(500);
|
||||
|
||||
await act(() => {});
|
||||
await serverAct(() => {});
|
||||
|
||||
expect(flightResponse).toContain('(loading everything)');
|
||||
expect(flightResponse).toContain('(loading sidebar)');
|
||||
@@ -629,25 +668,25 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
expect(flightResponse).not.toContain(':friends:');
|
||||
expect(flightResponse).not.toContain(':name:');
|
||||
|
||||
await act(() => {
|
||||
await serverAct(() => {
|
||||
resolveFriends();
|
||||
});
|
||||
|
||||
expect(flightResponse).toContain(':friends:');
|
||||
|
||||
await act(() => {
|
||||
await serverAct(() => {
|
||||
resolveName();
|
||||
});
|
||||
|
||||
expect(flightResponse).toContain(':name:');
|
||||
|
||||
await act(() => {
|
||||
await serverAct(() => {
|
||||
resolvePhotos();
|
||||
});
|
||||
|
||||
expect(flightResponse).toContain(':photos:');
|
||||
|
||||
await act(() => {
|
||||
await serverAct(() => {
|
||||
resolvePosts();
|
||||
});
|
||||
|
||||
@@ -695,19 +734,21 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
const message = typeof x === 'string' ? x : x.message;
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${message}")`;
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
webpackMap,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
const message = typeof x === 'string' ? x : x.message;
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${message}")`;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
@@ -751,17 +792,20 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await expect(async () => {
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<>
|
||||
<Parent>{Array(6).fill(<div>no key</div>)}</Parent>
|
||||
<ParentModule.Parent>
|
||||
{Array(6).fill(<div>no key</div>)}
|
||||
</ParentModule.Parent>
|
||||
</>,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<>
|
||||
<Parent>{Array(6).fill(<div>no key</div>)}</Parent>
|
||||
<ParentModule.Parent>
|
||||
{Array(6).fill(<div>no key</div>)}
|
||||
</ParentModule.Parent>
|
||||
</>,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
const result =
|
||||
await ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
await act(() => {
|
||||
root.render(result);
|
||||
});
|
||||
@@ -777,7 +821,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<Server />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
function Client() {
|
||||
@@ -816,7 +862,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<Parent />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<Parent />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
function Client() {
|
||||
@@ -853,15 +901,13 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
}
|
||||
|
||||
const reportedErrors = [];
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<Server />,
|
||||
webpackMap,
|
||||
{
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<Server />, webpackMap, {
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
@@ -912,7 +958,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return ReactServer.use(thenable);
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<Server />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
function Client() {
|
||||
@@ -947,7 +995,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
// Because the thenable resolves synchronously, we should be able to finish
|
||||
// rendering synchronously, with no fallback.
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(<Server />),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
function Client() {
|
||||
@@ -988,9 +1038,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
const boundFn = ServerModuleA.greet.bind(null, ServerModuleB.upper);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={boundFn} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={boundFn} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
@@ -1035,9 +1087,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
});
|
||||
const ClientRef = clientExports(Client);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={ServerModule.split} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={ServerModule.split} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
@@ -1100,9 +1154,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
const ClientRef = clientExports(Client);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={ServerModuleA.greet} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={ServerModuleA.greet} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
@@ -1140,9 +1196,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
});
|
||||
const ClientRef = clientExports(Client);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={greet.bind(null, 'Hello').bind(null, 'World')} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={greet.bind(null, 'Hello').bind(null, 'World')} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
@@ -1178,26 +1236,29 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
}
|
||||
|
||||
async function send(text) {
|
||||
return Promise.reject(new Error(`Error for ${text}`));
|
||||
throw new Error(`Error for ${text}`);
|
||||
}
|
||||
|
||||
const ServerModule = serverExports({send});
|
||||
const ClientRef = clientExports(Client);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={ServerModule.send} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action={ServerModule.send} />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
async callServer(actionId, args) {
|
||||
const body = await ReactServerDOMClient.encodeReply(args);
|
||||
const result = callServer(actionId, body);
|
||||
// Flight doesn't attach error handlers early enough. we suppress the warning
|
||||
// by putting a dummy catch on the result here
|
||||
result.catch(() => {});
|
||||
return ReactServerDOMClient.createFromReadableStream(
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
callServer(actionId, body),
|
||||
null,
|
||||
{onError: error => 'test-error-digest'},
|
||||
),
|
||||
ReactServerDOMServer.renderToReadableStream(result, null, {
|
||||
onError: error => 'test-error-digest',
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1212,17 +1273,17 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
let thrownError;
|
||||
|
||||
try {
|
||||
await serverAct(() => actionProxy('test'));
|
||||
} catch (error) {
|
||||
thrownError = error;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
await expect(actionProxy('test')).rejects.toThrow('Error for test');
|
||||
expect(thrownError).toEqual(new Error('Error for test'));
|
||||
} else {
|
||||
let thrownError;
|
||||
|
||||
try {
|
||||
await actionProxy('test');
|
||||
} catch (error) {
|
||||
thrownError = error;
|
||||
}
|
||||
|
||||
expect(thrownError).toEqual(
|
||||
new Error(
|
||||
'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.',
|
||||
@@ -1253,9 +1314,14 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
});
|
||||
const ClientRef = clientExports(Client);
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef action1={ServerModule.greet} action2={ServerModule.greet2} />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientRef
|
||||
action1={ServerModule.greet}
|
||||
action2={ServerModule.greet2}
|
||||
/>,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
@@ -1298,9 +1364,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
);
|
||||
|
||||
// Send the action to the client
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
{action: serverModule.action},
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
{action: serverModule.action},
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
const response =
|
||||
await ReactServerDOMClient.createFromReadableStream(stream);
|
||||
@@ -1340,9 +1408,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return <ClientComponent />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
let response = null;
|
||||
@@ -1406,9 +1476,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return <ClientComponent />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ServerComponent />,
|
||||
webpackMap,
|
||||
),
|
||||
);
|
||||
|
||||
let response = null;
|
||||
@@ -1427,15 +1499,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
let fizzStream;
|
||||
let fizzPromise;
|
||||
await act(async () => {
|
||||
fizzStream = await ReactDOMFizzServer.renderToReadableStream(<App />);
|
||||
fizzPromise = ReactDOMFizzServer.renderToReadableStream(<App />);
|
||||
});
|
||||
const fizzStream = await fizzPromise;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const reader = fizzStream.getReader();
|
||||
@@ -1464,16 +1532,18 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
let postponed = null;
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<Suspense fallback="Loading...">
|
||||
<Server />
|
||||
</Suspense>,
|
||||
null,
|
||||
{
|
||||
onPostpone(reason) {
|
||||
postponed = reason;
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<Suspense fallback="Loading...">
|
||||
<Server />
|
||||
</Suspense>,
|
||||
null,
|
||||
{
|
||||
onPostpone(reason) {
|
||||
postponed = reason;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream);
|
||||
|
||||
@@ -1512,18 +1582,20 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
return 'Done';
|
||||
}
|
||||
const errors = [];
|
||||
const stream = await ReactServerDOMServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
null,
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
null,
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered).toBe(false);
|
||||
@@ -1559,20 +1631,22 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
let error = null;
|
||||
|
||||
const controller = new AbortController();
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
<Suspense fallback="Loading...">
|
||||
<Server />
|
||||
</Suspense>,
|
||||
null,
|
||||
{
|
||||
onError(x) {
|
||||
error = x;
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<Suspense fallback="Loading...">
|
||||
<Server />
|
||||
</Suspense>,
|
||||
null,
|
||||
{
|
||||
onError(x) {
|
||||
error = x;
|
||||
},
|
||||
onPostpone(reason) {
|
||||
postponed = reason;
|
||||
},
|
||||
signal: controller.signal,
|
||||
},
|
||||
onPostpone(reason) {
|
||||
postponed = reason;
|
||||
},
|
||||
signal: controller.signal,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -1589,7 +1663,7 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(async () => {
|
||||
await act(() => {
|
||||
root.render(
|
||||
<div>
|
||||
Shell: <Client />
|
||||
@@ -1643,27 +1717,33 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
controller2 = c;
|
||||
},
|
||||
});
|
||||
const rscStream = ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
s1,
|
||||
s2,
|
||||
},
|
||||
{},
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x;
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
s1,
|
||||
s2,
|
||||
},
|
||||
},
|
||||
{},
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
passThrough(rscStream),
|
||||
);
|
||||
|
||||
const reader1 = result.s1.getReader();
|
||||
const reader2 = result.s2.getReader();
|
||||
|
||||
controller1.enqueue({hello: 'world'});
|
||||
controller2.enqueue({hi: 'there'});
|
||||
await serverAct(() => {
|
||||
controller1.enqueue({hello: 'world'});
|
||||
controller2.enqueue({hi: 'there'});
|
||||
});
|
||||
|
||||
expect(await reader1.read()).toEqual({
|
||||
value: {hello: 'world'},
|
||||
done: false,
|
||||
@@ -1673,10 +1753,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
done: false,
|
||||
});
|
||||
|
||||
controller1.enqueue('text1');
|
||||
controller2.enqueue('text2');
|
||||
controller1.close();
|
||||
controller2.error('rejected');
|
||||
await serverAct(async () => {
|
||||
controller1.enqueue('text1');
|
||||
controller2.enqueue('text2');
|
||||
controller1.close();
|
||||
});
|
||||
|
||||
expect(await reader1.read()).toEqual({
|
||||
value: 'text1',
|
||||
@@ -1690,6 +1771,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
value: 'text2',
|
||||
done: false,
|
||||
});
|
||||
await serverAct(async () => {
|
||||
controller2.error('rejected');
|
||||
});
|
||||
let error = null;
|
||||
try {
|
||||
await reader2.read();
|
||||
@@ -1713,14 +1797,16 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
},
|
||||
});
|
||||
let loggedReason;
|
||||
const rscStream = ReactServerDOMServer.renderToReadableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
onError(reason) {
|
||||
loggedReason = reason;
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
onError(reason) {
|
||||
loggedReason = reason;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const reader = rscStream.getReader();
|
||||
controller.enqueue('hi');
|
||||
@@ -1745,21 +1831,25 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
cancelReason = r;
|
||||
},
|
||||
});
|
||||
const rscStream = ReactServerDOMServer.renderToReadableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
signal: abortController.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x.message;
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
signal: abortController.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x.message;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
passThrough(rscStream),
|
||||
);
|
||||
const reader = result.getReader();
|
||||
|
||||
controller.enqueue('hi');
|
||||
|
||||
await 0;
|
||||
@@ -1808,18 +1898,20 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
throw 'F';
|
||||
})();
|
||||
|
||||
const rscStream = ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
multiShotIterable,
|
||||
singleShotIterator,
|
||||
},
|
||||
{},
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x;
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
multiShotIterable,
|
||||
singleShotIterator,
|
||||
},
|
||||
},
|
||||
{},
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
passThrough(rscStream),
|
||||
@@ -1840,7 +1932,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
done: false,
|
||||
});
|
||||
|
||||
await resolve();
|
||||
await serverAct(() => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
expect(await iterator1.next()).toEqual({
|
||||
value: {hi: 'B'},
|
||||
@@ -1914,16 +2008,21 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
yield 'c';
|
||||
})();
|
||||
let loggedReason;
|
||||
const rscStream = ReactServerDOMServer.renderToReadableStream(
|
||||
iterator,
|
||||
{},
|
||||
{
|
||||
onError(reason) {
|
||||
loggedReason = reason;
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
iterator,
|
||||
{},
|
||||
{
|
||||
onError(reason) {
|
||||
loggedReason = reason;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const reader = rscStream.getReader();
|
||||
|
||||
const reason = new Error('aborted');
|
||||
reader.cancel(reason);
|
||||
await resolve();
|
||||
@@ -1949,16 +2048,18 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
}
|
||||
yield 'c';
|
||||
})();
|
||||
const rscStream = ReactServerDOMServer.renderToReadableStream(
|
||||
iterator,
|
||||
{},
|
||||
{
|
||||
signal: abortController.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x.message;
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
iterator,
|
||||
{},
|
||||
{
|
||||
signal: abortController.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x.message;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
passThrough(rscStream),
|
||||
@@ -1967,7 +2068,9 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
const reason = new Error('aborted');
|
||||
abortController.abort(reason);
|
||||
|
||||
await resolve();
|
||||
await serverAct(() => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
// We should be able to read the part we already emitted before the abort
|
||||
expect(await result.next()).toEqual({
|
||||
|
||||
+54
-33
@@ -9,13 +9,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate';
|
||||
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
|
||||
// Don't wait before processing work on the server.
|
||||
// TODO: we can replace this with FlightServer.act().
|
||||
global.setImmediate = cb => cb();
|
||||
|
||||
let clientExports;
|
||||
let webpackMap;
|
||||
let webpackModules;
|
||||
@@ -26,11 +24,17 @@ let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let Stream;
|
||||
let use;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOMNode', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchSetImmediate(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-webpack/server', () =>
|
||||
@@ -58,6 +62,17 @@ describe('ReactFlightDOMNode', () => {
|
||||
use = React.use;
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
function readResult(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = '';
|
||||
@@ -110,9 +125,8 @@ describe('ReactFlightDOMNode', () => {
|
||||
return <ClientComponentOnTheClient />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<App />, webpackMap),
|
||||
);
|
||||
const readable = new Stream.PassThrough();
|
||||
let response;
|
||||
@@ -128,8 +142,8 @@ describe('ReactFlightDOMNode', () => {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot />,
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToPipeableStream(<ClientRoot />),
|
||||
);
|
||||
const result = await readResult(ssrStream);
|
||||
expect(result).toEqual(
|
||||
@@ -140,9 +154,11 @@ describe('ReactFlightDOMNode', () => {
|
||||
it('should encode long string in a compact format', async () => {
|
||||
const testString = '"\n\t'.repeat(500) + '🙃';
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream({
|
||||
text: testString,
|
||||
});
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream({
|
||||
text: testString,
|
||||
}),
|
||||
);
|
||||
|
||||
const readable = new Stream.PassThrough();
|
||||
|
||||
@@ -187,7 +203,9 @@ describe('ReactFlightDOMNode', () => {
|
||||
new BigUint64Array(buffer, 0),
|
||||
new DataView(buffer, 3),
|
||||
];
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(buffers);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(buffers),
|
||||
);
|
||||
const readable = new Stream.PassThrough();
|
||||
const promise = ReactServerDOMClient.createFromNodeStream(readable, {
|
||||
moduleMap: {},
|
||||
@@ -232,9 +250,8 @@ describe('ReactFlightDOMNode', () => {
|
||||
return <ClientComponentOnTheClient />;
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
<App />,
|
||||
webpackMap,
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(<App />, webpackMap),
|
||||
);
|
||||
const readable = new Stream.PassThrough();
|
||||
let response;
|
||||
@@ -253,8 +270,8 @@ describe('ReactFlightDOMNode', () => {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const ssrStream = await ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot />,
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToPipeableStream(<ClientRoot />),
|
||||
);
|
||||
const result = await readResult(ssrStream);
|
||||
expect(result).toEqual(
|
||||
@@ -275,14 +292,16 @@ describe('ReactFlightDOMNode', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const rscStream = ReactServerDOMServer.renderToPipeableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
onError(error) {
|
||||
return error.message;
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
onError(error) {
|
||||
return error.message;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const writable = new Stream.PassThrough();
|
||||
@@ -317,15 +336,17 @@ describe('ReactFlightDOMNode', () => {
|
||||
cancelReason = r;
|
||||
},
|
||||
});
|
||||
const rscStream = ReactServerDOMServer.renderToPipeableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x.message;
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
s,
|
||||
{},
|
||||
{
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return x.message;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const readable = new Stream.PassThrough();
|
||||
|
||||
+32
-12
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
@@ -20,10 +22,17 @@ let webpackServerMap;
|
||||
let React;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let ReactServerScheduler;
|
||||
let reactServerAct;
|
||||
|
||||
describe('ReactFlightDOMReply', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactServerScheduler = require('scheduler');
|
||||
patchMessageChannel(ReactServerScheduler);
|
||||
reactServerAct = require('internal-test-utils').act;
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-webpack/server', () =>
|
||||
@@ -39,6 +48,17 @@ describe('ReactFlightDOMReply', () => {
|
||||
ReactServerDOMClient = require('react-server-dom-webpack/client');
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
let maybePromise;
|
||||
await reactServerAct(() => {
|
||||
maybePromise = callback();
|
||||
if (maybePromise && typeof maybePromise.catch === 'function') {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
});
|
||||
return maybePromise;
|
||||
}
|
||||
|
||||
// This method should exist on File but is not implemented in JSDOM
|
||||
async function arrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -369,12 +389,10 @@ describe('ReactFlightDOMReply', () => {
|
||||
webpackServerMap,
|
||||
{temporaryReferences: temporaryReferencesServer},
|
||||
);
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
serverPayload,
|
||||
null,
|
||||
{
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(serverPayload, null, {
|
||||
temporaryReferences: temporaryReferencesServer,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const response = await ReactServerDOMClient.createFromReadableStream(
|
||||
stream,
|
||||
@@ -408,13 +426,15 @@ describe('ReactFlightDOMReply', () => {
|
||||
webpackServerMap,
|
||||
{temporaryReferences: temporaryReferencesServer},
|
||||
);
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
root: serverPayload,
|
||||
obj: serverPayload.obj,
|
||||
},
|
||||
null,
|
||||
{temporaryReferences: temporaryReferencesServer},
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
root: serverPayload,
|
||||
obj: serverPayload.obj,
|
||||
},
|
||||
null,
|
||||
{temporaryReferences: temporaryReferencesServer},
|
||||
),
|
||||
);
|
||||
const response = await ReactServerDOMClient.createFromReadableStream(
|
||||
stream,
|
||||
|
||||
+7
-2
@@ -3506,9 +3506,14 @@ function enqueueFlush(request: Request): void {
|
||||
// happen when we start flowing again
|
||||
request.destination !== null
|
||||
) {
|
||||
const destination = request.destination;
|
||||
request.flushScheduled = true;
|
||||
scheduleWork(() => flushCompletedChunks(request, destination));
|
||||
scheduleWork(() => {
|
||||
request.flushScheduled = false;
|
||||
const destination = request.destination;
|
||||
if (destination) {
|
||||
flushCompletedChunks(request, destination);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -13,8 +13,18 @@ export type PrecomputedChunk = Uint8Array;
|
||||
export opaque type Chunk = Uint8Array;
|
||||
export type BinaryChunk = Uint8Array;
|
||||
|
||||
const channel = new MessageChannel();
|
||||
const taskQueue = [];
|
||||
channel.port1.onmessage = () => {
|
||||
const task = taskQueue.shift();
|
||||
if (task) {
|
||||
task();
|
||||
}
|
||||
};
|
||||
|
||||
export function scheduleWork(callback: () => void) {
|
||||
callback();
|
||||
taskQueue.push(callback);
|
||||
channel.port2.postMessage(null);
|
||||
}
|
||||
|
||||
export function flushBuffered(destination: Destination) {
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ export opaque type Chunk = string;
|
||||
export type BinaryChunk = $ArrayBufferView;
|
||||
|
||||
export function scheduleWork(callback: () => void) {
|
||||
callback();
|
||||
setTimeout(callback, 0);
|
||||
}
|
||||
|
||||
export function flushBuffered(destination: Destination) {
|
||||
|
||||
@@ -42,8 +42,22 @@ export interface Destination {
|
||||
onError(error: mixed): void;
|
||||
}
|
||||
|
||||
function handleErrorInNextTick(error: any) {
|
||||
setTimeout(() => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
const LocalPromise = Promise;
|
||||
|
||||
/**
|
||||
* Since this environment doesn't have a way to schedule tasks from JS we schedule
|
||||
* using a microtask instead. This isn't necessarily ideal since we would like to give
|
||||
* other IO a chance to run before performing work typically but it's the best we can
|
||||
* do in this environment
|
||||
*/
|
||||
export function scheduleWork(callback: () => void) {
|
||||
callback();
|
||||
LocalPromise.resolve().then(callback).catch(handleErrorInNextTick);
|
||||
}
|
||||
|
||||
export function beginWriting(destination: Destination) {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
|
||||
|
||||
describe('ReactMismatchedVersions-test', () => {
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
@@ -20,6 +22,9 @@ describe('ReactMismatchedVersions-test', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
patchMessageChannel();
|
||||
|
||||
jest.mock('react', () => {
|
||||
const actualReact = jest.requireActual('react');
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
export function patchMessageChannel(Scheduler) {
|
||||
global.MessageChannel = class {
|
||||
constructor() {
|
||||
const port1 = {
|
||||
onmesssage: () => {},
|
||||
};
|
||||
|
||||
this.port1 = port1;
|
||||
|
||||
this.port2 = {
|
||||
postMessage(msg) {
|
||||
if (Scheduler) {
|
||||
Scheduler.unstable_scheduleCallback(
|
||||
Scheduler.unstable_NormalPriority,
|
||||
() => {
|
||||
port1.onmessage(msg);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
'MessageChannel patch was used without providing a Scheduler implementation. This is useful for tests that require this class to exist but are not actually utilizing the MessageChannel class. However it appears some test is trying to use this class so you should pass a Scheduler implemenation to the patch method'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
export function patchSetImmediate(Scheduler) {
|
||||
if (!Scheduler) {
|
||||
throw new Error(
|
||||
'setImmediate patch was used without providing a Scheduler implementation. If you are patching setImmediate you must provide a Scheduler.'
|
||||
);
|
||||
}
|
||||
|
||||
global.setImmediate = cb => {
|
||||
Scheduler.unstable_scheduleCallback(Scheduler.unstable_NormalPriority, cb);
|
||||
};
|
||||
}
|
||||
@@ -21,19 +21,6 @@ global.__EXPERIMENTAL__ =
|
||||
global.__VARIANT__ = !!process.env.VARIANT;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
global.requestIdleCallback = function (callback) {
|
||||
return setTimeout(() => {
|
||||
callback({
|
||||
timeRemaining() {
|
||||
return Infinity;
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
global.cancelIdleCallback = function (callbackID) {
|
||||
clearTimeout(callbackID);
|
||||
};
|
||||
} else {
|
||||
global.AbortController =
|
||||
require('abortcontroller-polyfill/dist/cjs-ponyfill').AbortController;
|
||||
|
||||
Reference in New Issue
Block a user