mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
The internal file ReactSharedSubset is what the `react` module resolves to when imported from a Server Component environment. We gave it this name because, originally, the idea was that Server Components can access a subset of the APIs available on the client. However, since then, we've also added APIs that can _only_ by accessed on the server and not the client. In other words, it's no longer a subset, it's a slightly different overlapping set. So this commit renames ReactSharedSubet to ReactServer and updates all the references. This does not affect the public API, only our internal implementation.
245 lines
7.9 KiB
JavaScript
245 lines
7.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// Polyfills for test environment
|
|
global.ReadableStream =
|
|
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
|
global.TextEncoder = require('util').TextEncoder;
|
|
global.TextDecoder = require('util').TextDecoder;
|
|
|
|
// let serverExports;
|
|
let webpackServerMap;
|
|
let ReactServerDOMServer;
|
|
let ReactServerDOMClient;
|
|
|
|
describe('ReactFlightDOMReply', () => {
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
// Simulate the condition resolution
|
|
jest.mock('react', () => require('react/react.react-server'));
|
|
jest.mock('react-server-dom-webpack/server', () =>
|
|
require('react-server-dom-webpack/server.browser'),
|
|
);
|
|
const WebpackMock = require('./utils/WebpackMock');
|
|
// serverExports = WebpackMock.serverExports;
|
|
webpackServerMap = WebpackMock.webpackServerMap;
|
|
ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
|
|
jest.resetModules();
|
|
ReactServerDOMClient = require('react-server-dom-webpack/client');
|
|
});
|
|
|
|
// This method should exist on File but is not implemented in JSDOM
|
|
async function arrayBuffer(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = function () {
|
|
return resolve(reader.result);
|
|
};
|
|
reader.onerror = function () {
|
|
return reject(reader.error);
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
}
|
|
|
|
it('can pass undefined as a reply', async () => {
|
|
const body = await ReactServerDOMClient.encodeReply(undefined);
|
|
const missing = await ReactServerDOMServer.decodeReply(
|
|
body,
|
|
webpackServerMap,
|
|
);
|
|
expect(missing).toBe(undefined);
|
|
|
|
const body2 = await ReactServerDOMClient.encodeReply({
|
|
array: [undefined, null, undefined],
|
|
prop: undefined,
|
|
});
|
|
const object = await ReactServerDOMServer.decodeReply(
|
|
body2,
|
|
webpackServerMap,
|
|
);
|
|
expect(object.array.length).toBe(3);
|
|
expect(object.array[0]).toBe(undefined);
|
|
expect(object.array[1]).toBe(null);
|
|
expect(object.array[3]).toBe(undefined);
|
|
expect(object.prop).toBe(undefined);
|
|
// These should really be true but our deserialization doesn't currently deal with it.
|
|
expect('3' in object.array).toBe(false);
|
|
expect('prop' in object).toBe(false);
|
|
});
|
|
|
|
it('can pass an iterable as a reply', async () => {
|
|
const body = await ReactServerDOMClient.encodeReply({
|
|
[Symbol.iterator]: function* () {
|
|
yield 'A';
|
|
yield 'B';
|
|
yield 'C';
|
|
},
|
|
});
|
|
const iterable = await ReactServerDOMServer.decodeReply(
|
|
body,
|
|
webpackServerMap,
|
|
);
|
|
const items = [];
|
|
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
|
for (const item of iterable) {
|
|
items.push(item);
|
|
}
|
|
expect(items).toEqual(['A', 'B', 'C']);
|
|
});
|
|
|
|
it('can pass weird numbers as a reply', async () => {
|
|
const nums = [0, -0, Infinity, -Infinity, NaN];
|
|
const body = await ReactServerDOMClient.encodeReply(nums);
|
|
const nums2 = await ReactServerDOMServer.decodeReply(
|
|
body,
|
|
webpackServerMap,
|
|
);
|
|
|
|
expect(nums).toEqual(nums2);
|
|
expect(nums.every((n, i) => Object.is(n, nums2[i]))).toBe(true);
|
|
});
|
|
|
|
it('can pass a BigInt as a reply', async () => {
|
|
const body = await ReactServerDOMClient.encodeReply(90071992547409910000n);
|
|
const n = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
|
|
|
|
expect(n).toEqual(90071992547409910000n);
|
|
});
|
|
|
|
it('can pass FormData as a reply', async () => {
|
|
const formData = new FormData();
|
|
formData.set('hello', 'world');
|
|
formData.append('list', '1');
|
|
formData.append('list', '2');
|
|
formData.append('list', '3');
|
|
const typedArray = new Uint8Array([0, 1, 2, 3]);
|
|
const blob = new Blob([typedArray]);
|
|
formData.append('blob', blob, 'filename.blob');
|
|
|
|
const body = await ReactServerDOMClient.encodeReply(formData);
|
|
const formData2 = await ReactServerDOMServer.decodeReply(
|
|
body,
|
|
webpackServerMap,
|
|
);
|
|
|
|
expect(formData2).not.toBe(formData);
|
|
expect(Array.from(formData2).length).toBe(5);
|
|
expect(formData2.get('hello')).toBe('world');
|
|
expect(formData2.getAll('list')).toEqual(['1', '2', '3']);
|
|
const blob2 = formData.get('blob');
|
|
expect(blob2.size).toBe(4);
|
|
expect(blob2.name).toBe('filename.blob');
|
|
expect(blob2.type).toBe('');
|
|
const typedArray2 = new Uint8Array(await arrayBuffer(blob2));
|
|
expect(typedArray2).toEqual(typedArray);
|
|
});
|
|
|
|
it('can pass multiple Files in FormData', async () => {
|
|
const typedArrayA = new Uint8Array([0, 1, 2, 3]);
|
|
const typedArrayB = new Uint8Array([4, 5]);
|
|
const blobA = new Blob([typedArrayA]);
|
|
const blobB = new Blob([typedArrayB]);
|
|
const formData = new FormData();
|
|
formData.append('filelist', 'string');
|
|
formData.append('filelist', blobA);
|
|
formData.append('filelist', blobB);
|
|
|
|
const body = await ReactServerDOMClient.encodeReply(formData);
|
|
const formData2 = await ReactServerDOMServer.decodeReply(
|
|
body,
|
|
webpackServerMap,
|
|
);
|
|
|
|
const filelist2 = formData2.getAll('filelist');
|
|
expect(filelist2.length).toBe(3);
|
|
expect(filelist2[0]).toBe('string');
|
|
const blobA2 = filelist2[1];
|
|
expect(blobA2.size).toBe(4);
|
|
expect(blobA2.name).toBe('blob');
|
|
expect(blobA2.type).toBe('');
|
|
const typedArrayA2 = new Uint8Array(await arrayBuffer(blobA2));
|
|
expect(typedArrayA2).toEqual(typedArrayA);
|
|
const blobB2 = filelist2[2];
|
|
expect(blobB2.size).toBe(2);
|
|
expect(blobB2.name).toBe('blob');
|
|
expect(blobB2.type).toBe('');
|
|
const typedArrayB2 = new Uint8Array(await arrayBuffer(blobB2));
|
|
expect(typedArrayB2).toEqual(typedArrayB);
|
|
});
|
|
|
|
it('can pass two independent FormData with same keys', async () => {
|
|
const formDataA = new FormData();
|
|
formDataA.set('greeting', 'hello');
|
|
const formDataB = new FormData();
|
|
formDataB.set('greeting', 'hi');
|
|
|
|
const body = await ReactServerDOMClient.encodeReply({
|
|
a: formDataA,
|
|
b: formDataB,
|
|
});
|
|
const {a: formDataA2, b: formDataB2} =
|
|
await ReactServerDOMServer.decodeReply(body, webpackServerMap);
|
|
|
|
expect(Array.from(formDataA2).length).toBe(1);
|
|
expect(Array.from(formDataB2).length).toBe(1);
|
|
expect(formDataA2.get('greeting')).toBe('hello');
|
|
expect(formDataB2.get('greeting')).toBe('hi');
|
|
});
|
|
|
|
it('can pass a Date as a reply', async () => {
|
|
const d = new Date(1234567890123);
|
|
const body = await ReactServerDOMClient.encodeReply(d);
|
|
const d2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
|
|
|
|
expect(d).toEqual(d2);
|
|
expect(d % 1000).toEqual(123); // double-check the milliseconds made it through
|
|
});
|
|
|
|
it('can pass a Map as a reply', async () => {
|
|
const objKey = {obj: 'key'};
|
|
const m = new Map([
|
|
['hi', {greet: 'world'}],
|
|
[objKey, 123],
|
|
]);
|
|
const body = await ReactServerDOMClient.encodeReply(m);
|
|
const m2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
|
|
|
|
expect(m2 instanceof Map).toBe(true);
|
|
expect(m2.size).toBe(2);
|
|
expect(m2.get('hi').greet).toBe('world');
|
|
expect(m2).toEqual(m);
|
|
});
|
|
|
|
it('can pass a Set as a reply', async () => {
|
|
const objKey = {obj: 'key'};
|
|
const s = new Set(['hi', objKey]);
|
|
|
|
const body = await ReactServerDOMClient.encodeReply(s);
|
|
const s2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
|
|
|
|
expect(s2 instanceof Set).toBe(true);
|
|
expect(s2.size).toBe(2);
|
|
expect(s2.has('hi')).toBe(true);
|
|
expect(s2).toEqual(s);
|
|
});
|
|
|
|
it('does not hang indefinitely when calling decodeReply with FormData', async () => {
|
|
let error;
|
|
try {
|
|
await ReactServerDOMServer.decodeReply(new FormData(), webpackServerMap);
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
expect(error.message).toBe('Connection closed.');
|
|
});
|
|
});
|