1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. // Polyfills for test environment
    
  13. global.ReadableStream =
    
  14.   require('web-streams-polyfill/ponyfill/es6').ReadableStream;
    
  15. global.TextEncoder = require('util').TextEncoder;
    
  16. global.TextDecoder = require('util').TextDecoder;
    
  17. 
    
  18. // let serverExports;
    
  19. let webpackServerMap;
    
  20. let ReactServerDOMServer;
    
  21. let ReactServerDOMClient;
    
  22. 
    
  23. describe('ReactFlightDOMReply', () => {
    
  24.   beforeEach(() => {
    
  25.     jest.resetModules();
    
  26.     // Simulate the condition resolution
    
  27.     jest.mock('react', () => require('react/react.shared-subset'));
    
  28.     jest.mock('react-server-dom-webpack/server', () =>
    
  29.       require('react-server-dom-webpack/server.browser'),
    
  30.     );
    
  31.     const WebpackMock = require('./utils/WebpackMock');
    
  32.     // serverExports = WebpackMock.serverExports;
    
  33.     webpackServerMap = WebpackMock.webpackServerMap;
    
  34.     ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
    
  35.     jest.resetModules();
    
  36.     ReactServerDOMClient = require('react-server-dom-webpack/client');
    
  37.   });
    
  38. 
    
  39.   // This method should exist on File but is not implemented in JSDOM
    
  40.   async function arrayBuffer(file) {
    
  41.     return new Promise((resolve, reject) => {
    
  42.       const reader = new FileReader();
    
  43.       reader.onload = function () {
    
  44.         return resolve(reader.result);
    
  45.       };
    
  46.       reader.onerror = function () {
    
  47.         return reject(reader.error);
    
  48.       };
    
  49.       reader.readAsArrayBuffer(file);
    
  50.     });
    
  51.   }
    
  52. 
    
  53.   it('can pass undefined as a reply', async () => {
    
  54.     const body = await ReactServerDOMClient.encodeReply(undefined);
    
  55.     const missing = await ReactServerDOMServer.decodeReply(
    
  56.       body,
    
  57.       webpackServerMap,
    
  58.     );
    
  59.     expect(missing).toBe(undefined);
    
  60. 
    
  61.     const body2 = await ReactServerDOMClient.encodeReply({
    
  62.       array: [undefined, null, undefined],
    
  63.       prop: undefined,
    
  64.     });
    
  65.     const object = await ReactServerDOMServer.decodeReply(
    
  66.       body2,
    
  67.       webpackServerMap,
    
  68.     );
    
  69.     expect(object.array.length).toBe(3);
    
  70.     expect(object.array[0]).toBe(undefined);
    
  71.     expect(object.array[1]).toBe(null);
    
  72.     expect(object.array[3]).toBe(undefined);
    
  73.     expect(object.prop).toBe(undefined);
    
  74.     // These should really be true but our deserialization doesn't currently deal with it.
    
  75.     expect('3' in object.array).toBe(false);
    
  76.     expect('prop' in object).toBe(false);
    
  77.   });
    
  78. 
    
  79.   it('can pass an iterable as a reply', async () => {
    
  80.     const body = await ReactServerDOMClient.encodeReply({
    
  81.       [Symbol.iterator]: function* () {
    
  82.         yield 'A';
    
  83.         yield 'B';
    
  84.         yield 'C';
    
  85.       },
    
  86.     });
    
  87.     const iterable = await ReactServerDOMServer.decodeReply(
    
  88.       body,
    
  89.       webpackServerMap,
    
  90.     );
    
  91.     const items = [];
    
  92.     // eslint-disable-next-line no-for-of-loops/no-for-of-loops
    
  93.     for (const item of iterable) {
    
  94.       items.push(item);
    
  95.     }
    
  96.     expect(items).toEqual(['A', 'B', 'C']);
    
  97.   });
    
  98. 
    
  99.   it('can pass weird numbers as a reply', async () => {
    
  100.     const nums = [0, -0, Infinity, -Infinity, NaN];
    
  101.     const body = await ReactServerDOMClient.encodeReply(nums);
    
  102.     const nums2 = await ReactServerDOMServer.decodeReply(
    
  103.       body,
    
  104.       webpackServerMap,
    
  105.     );
    
  106. 
    
  107.     expect(nums).toEqual(nums2);
    
  108.     expect(nums.every((n, i) => Object.is(n, nums2[i]))).toBe(true);
    
  109.   });
    
  110. 
    
  111.   it('can pass a BigInt as a reply', async () => {
    
  112.     const body = await ReactServerDOMClient.encodeReply(90071992547409910000n);
    
  113.     const n = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
    
  114. 
    
  115.     expect(n).toEqual(90071992547409910000n);
    
  116.   });
    
  117. 
    
  118.   it('can pass FormData as a reply', async () => {
    
  119.     const formData = new FormData();
    
  120.     formData.set('hello', 'world');
    
  121.     formData.append('list', '1');
    
  122.     formData.append('list', '2');
    
  123.     formData.append('list', '3');
    
  124.     const typedArray = new Uint8Array([0, 1, 2, 3]);
    
  125.     const blob = new Blob([typedArray]);
    
  126.     formData.append('blob', blob, 'filename.blob');
    
  127. 
    
  128.     const body = await ReactServerDOMClient.encodeReply(formData);
    
  129.     const formData2 = await ReactServerDOMServer.decodeReply(
    
  130.       body,
    
  131.       webpackServerMap,
    
  132.     );
    
  133. 
    
  134.     expect(formData2).not.toBe(formData);
    
  135.     expect(Array.from(formData2).length).toBe(5);
    
  136.     expect(formData2.get('hello')).toBe('world');
    
  137.     expect(formData2.getAll('list')).toEqual(['1', '2', '3']);
    
  138.     const blob2 = formData.get('blob');
    
  139.     expect(blob2.size).toBe(4);
    
  140.     expect(blob2.name).toBe('filename.blob');
    
  141.     expect(blob2.type).toBe('');
    
  142.     const typedArray2 = new Uint8Array(await arrayBuffer(blob2));
    
  143.     expect(typedArray2).toEqual(typedArray);
    
  144.   });
    
  145. 
    
  146.   it('can pass multiple Files in FormData', async () => {
    
  147.     const typedArrayA = new Uint8Array([0, 1, 2, 3]);
    
  148.     const typedArrayB = new Uint8Array([4, 5]);
    
  149.     const blobA = new Blob([typedArrayA]);
    
  150.     const blobB = new Blob([typedArrayB]);
    
  151.     const formData = new FormData();
    
  152.     formData.append('filelist', 'string');
    
  153.     formData.append('filelist', blobA);
    
  154.     formData.append('filelist', blobB);
    
  155. 
    
  156.     const body = await ReactServerDOMClient.encodeReply(formData);
    
  157.     const formData2 = await ReactServerDOMServer.decodeReply(
    
  158.       body,
    
  159.       webpackServerMap,
    
  160.     );
    
  161. 
    
  162.     const filelist2 = formData2.getAll('filelist');
    
  163.     expect(filelist2.length).toBe(3);
    
  164.     expect(filelist2[0]).toBe('string');
    
  165.     const blobA2 = filelist2[1];
    
  166.     expect(blobA2.size).toBe(4);
    
  167.     expect(blobA2.name).toBe('blob');
    
  168.     expect(blobA2.type).toBe('');
    
  169.     const typedArrayA2 = new Uint8Array(await arrayBuffer(blobA2));
    
  170.     expect(typedArrayA2).toEqual(typedArrayA);
    
  171.     const blobB2 = filelist2[2];
    
  172.     expect(blobB2.size).toBe(2);
    
  173.     expect(blobB2.name).toBe('blob');
    
  174.     expect(blobB2.type).toBe('');
    
  175.     const typedArrayB2 = new Uint8Array(await arrayBuffer(blobB2));
    
  176.     expect(typedArrayB2).toEqual(typedArrayB);
    
  177.   });
    
  178. 
    
  179.   it('can pass two independent FormData with same keys', async () => {
    
  180.     const formDataA = new FormData();
    
  181.     formDataA.set('greeting', 'hello');
    
  182.     const formDataB = new FormData();
    
  183.     formDataB.set('greeting', 'hi');
    
  184. 
    
  185.     const body = await ReactServerDOMClient.encodeReply({
    
  186.       a: formDataA,
    
  187.       b: formDataB,
    
  188.     });
    
  189.     const {a: formDataA2, b: formDataB2} =
    
  190.       await ReactServerDOMServer.decodeReply(body, webpackServerMap);
    
  191. 
    
  192.     expect(Array.from(formDataA2).length).toBe(1);
    
  193.     expect(Array.from(formDataB2).length).toBe(1);
    
  194.     expect(formDataA2.get('greeting')).toBe('hello');
    
  195.     expect(formDataB2.get('greeting')).toBe('hi');
    
  196.   });
    
  197. 
    
  198.   it('can pass a Date as a reply', async () => {
    
  199.     const d = new Date(1234567890123);
    
  200.     const body = await ReactServerDOMClient.encodeReply(d);
    
  201.     const d2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
    
  202. 
    
  203.     expect(d).toEqual(d2);
    
  204.     expect(d % 1000).toEqual(123); // double-check the milliseconds made it through
    
  205.   });
    
  206. 
    
  207.   it('can pass a Map as a reply', async () => {
    
  208.     const objKey = {obj: 'key'};
    
  209.     const m = new Map([
    
  210.       ['hi', {greet: 'world'}],
    
  211.       [objKey, 123],
    
  212.     ]);
    
  213.     const body = await ReactServerDOMClient.encodeReply(m);
    
  214.     const m2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
    
  215. 
    
  216.     expect(m2 instanceof Map).toBe(true);
    
  217.     expect(m2.size).toBe(2);
    
  218.     expect(m2.get('hi').greet).toBe('world');
    
  219.     expect(m2).toEqual(m);
    
  220.   });
    
  221. 
    
  222.   it('can pass a Set as a reply', async () => {
    
  223.     const objKey = {obj: 'key'};
    
  224.     const s = new Set(['hi', objKey]);
    
  225. 
    
  226.     const body = await ReactServerDOMClient.encodeReply(s);
    
  227.     const s2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
    
  228. 
    
  229.     expect(s2 instanceof Set).toBe(true);
    
  230.     expect(s2.size).toBe(2);
    
  231.     expect(s2.has('hi')).toBe(true);
    
  232.     expect(s2).toEqual(s);
    
  233.   });
    
  234. 
    
  235.   it('does not hang indefinitely when calling decodeReply with FormData', async () => {
    
  236.     let error;
    
  237.     try {
    
  238.       await ReactServerDOMServer.decodeReply(new FormData(), webpackServerMap);
    
  239.     } catch (e) {
    
  240.       error = e;
    
  241.     }
    
  242.     expect(error.message).toBe('Connection closed.');
    
  243.   });
    
  244. });