/** * 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'; import {insertNodesAndExecuteScripts} from 'react-dom/src/test-utils/FizzTestUtils'; // Polyfills for test environment global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextEncoder = require('util').TextEncoder; global.TextDecoder = require('util').TextDecoder; // Polyfill stream methods on JSDOM. global.Blob.prototype.stream = function () { const impl = Object.getOwnPropertySymbols(this)[0]; const buffer = this[impl]._buffer; return new ReadableStream({ start(c) { c.enqueue(new Uint8Array(buffer)); c.close(); }, }); }; global.Blob.prototype.text = async function () { const impl = Object.getOwnPropertySymbols(this)[0]; return this[impl]._buffer.toString('utf8'); }; // Don't wait before processing work on the server. // TODO: we can replace this with FlightServer.act(). global.setTimeout = cb => cb(); let container; let clientExports; let serverExports; let webpackMap; let webpackServerMap; let React; let ReactDOMServer; let ReactServerDOMServer; let ReactServerDOMClient; let ReactDOMClient; let useActionState; let act; let assertConsoleErrorDev; describe('ReactFlightDOMForm', () => { 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.edge'), ); ReactServerDOMServer = require('react-server-dom-webpack/server.edge'); const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; serverExports = WebpackMock.serverExports; webpackMap = WebpackMock.webpackMap; webpackServerMap = WebpackMock.webpackServerMap; __unmockReact(); jest.resetModules(); React = require('react'); ReactServerDOMClient = require('react-server-dom-webpack/client.edge'); ReactDOMServer = require('react-dom/server.edge'); ReactDOMClient = require('react-dom/client'); act = React.act; assertConsoleErrorDev = require('internal-test-utils').assertConsoleErrorDev; // TODO: Test the old api but it warns so needs warnings to be asserted. // if (__VARIANT__) { // Remove after API is deleted. // useActionState = require('react-dom').useFormState; // } useActionState = require('react').useActionState; container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { document.body.removeChild(container); }); async function POST(formData) { const boundAction = await ReactServerDOMServer.decodeAction( formData, webpackServerMap, ); const returnValue = boundAction(); const formState = await ReactServerDOMServer.decodeFormState( await returnValue, formData, webpackServerMap, ); return {returnValue, formState}; } function submit(submitter) { const form = submitter.form || submitter; if (!submitter.form) { submitter = undefined; } const submitEvent = new Event('submit', {bubbles: true, cancelable: true}); submitEvent.submitter = submitter; const returnValue = form.dispatchEvent(submitEvent); if (!returnValue) { return; } const action = (submitter && submitter.getAttribute('formaction')) || form.action; if (!/\s*javascript:/i.test(action)) { const method = (submitter && submitter.formMethod) || form.method; const encType = (submitter && submitter.formEnctype) || form.enctype; if (method === 'post' && encType === 'multipart/form-data') { const formData = new FormData(form, submitter); return POST(formData); } throw new Error('Navigate to: ' + action); } } async function readIntoContainer(stream) { const reader = stream.getReader(); let result = ''; while (true) { const {done, value} = await reader.read(); if (done) { break; } result += Buffer.from(value).toString('utf8'); } const temp = document.createElement('div'); temp.innerHTML = result; insertNodesAndExecuteScripts(temp, container, null); } it('can submit a passed server action without hydrating it', async () => { let foo = null; const serverAction = serverExports(function action(formData) { foo = formData.get('foo'); return 'hello'; }); function App() { return (
); } const rscStream = ReactServerDOMServer.renderToReadableStream(); const response = ReactServerDOMClient.createFromReadableStream(rscStream, { serverConsumerManifest: { moduleMap: null, moduleLoading: null, }, }); const ssrStream = await ReactDOMServer.renderToReadableStream(response); await readIntoContainer(ssrStream); const form = container.firstChild; expect(foo).toBe(null); const {returnValue} = await submit(form); expect(returnValue).toBe('hello'); expect(foo).toBe('bar'); }); it('can submit an imported server action without hydrating it', async () => { let foo = null; const ServerModule = serverExports(function action(formData) { foo = formData.get('foo'); return 'hi'; }); const serverAction = ReactServerDOMClient.createServerReference( ServerModule.$$id, ); function App() { return (
); } const ssrStream = await ReactDOMServer.renderToReadableStream(); await readIntoContainer(ssrStream); const form = container.firstChild; expect(foo).toBe(null); const {returnValue} = await submit(form); expect(returnValue).toBe('hi'); expect(foo).toBe('bar'); }); it('can submit a complex closure server action without hydrating it', async () => { let foo = null; const serverAction = serverExports(function action(bound, formData) { foo = formData.get('foo') + bound.complex; return 'hello'; }); function App() { return (
); } const rscStream = ReactServerDOMServer.renderToReadableStream(); const response = ReactServerDOMClient.createFromReadableStream(rscStream, { serverConsumerManifest: { moduleMap: null, moduleLoading: null, }, }); const ssrStream = await ReactDOMServer.renderToReadableStream(response); await readIntoContainer(ssrStream); const form = container.firstChild; expect(foo).toBe(null); const {returnValue} = await submit(form); expect(returnValue).toBe('hello'); expect(foo).toBe('barobject'); }); it('can submit a multiple complex closure server action without hydrating it', async () => { let foo = null; const serverAction = serverExports(function action(bound, formData) { foo = formData.get('foo') + bound.complex; return 'hello' + bound.complex; }); function App() { return (