30
let ReactDOMServer;
31
let ReactServerDOMServer;
32
let ReactServerDOMClient;
33
+let ReactDOMClient;
34
let useFormState;
35
+let act;
36
37
describe('ReactFlightDOMForm', () => {
38
beforeEach(() => {
50
ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
51
ReactServerDOMClient = require('react-server-dom-webpack/client.edge');
52
ReactDOMServer = require('react-dom/server.edge');
53
+ ReactDOMClient = require('react-dom/client');
54
+ act = require('react-dom/test-utils').act;
55
useFormState = require('react-dom').experimental_useFormState;
56
container = document.createElement('div');
57
document.body.appendChild(container);
66
formData,
67
webpackServerMap,
68
);
65
- return boundAction();
69
+ const returnValue = boundAction();
70
+ const formState = ReactServerDOMServer.decodeFormState(
71
+ await returnValue,
72
+ formData,
73
+ webpackServerMap,
74
+ );
75
+ return {returnValue, formState};
76
}
77
78
function submit(submitter) {
148
149
expect(foo).toBe(null);
150
141
- const result = await submit(form);
151
+ const {returnValue} = await submit(form);
152
143
- expect(result).toBe('hello');
153
+ expect(returnValue).toBe('hello');
154
expect(foo).toBe('bar');
155
});
156
180
181
expect(foo).toBe(null);
182
173
- const result = await submit(form);
183
+ const {returnValue} = await submit(form);
184
175
- expect(result).toBe('hi');
185
+ expect(returnValue).toBe('hi');
186
187
expect(foo).toBe('bar');
188
});
211
212
expect(foo).toBe(null);
213
204
- const result = await submit(form);
214
+ const {returnValue} = await submit(form);
215
206
- expect(result).toBe('hello');
216
+ expect(returnValue).toBe('hello');
217
expect(foo).toBe('barobject');
218
});
219
247
248
expect(foo).toBe(null);
249
240
- const result = await submit(form.getElementsByTagName('button')[1]);
250
+ const {returnValue} = await submit(form.getElementsByTagName('button')[1]);
251
242
- expect(result).toBe('helloc');
252
+ expect(returnValue).toBe('helloc');
253
expect(foo).toBe('barc');
254
});
255
279
280
expect(foo).toBe(null);
281
272
- const result = await submit(form);
282
+ const {returnValue} = await submit(form);
283
274
- expect(result).toBe('hello');
284
+ expect(returnValue).toBe('hello');
285
expect(foo).toBe('barobject');
286
});
287
315
316
expect(foo).toBe(null);
317
308
- const result = await submit(form);
318
+ const {returnValue} = await submit(form);
319
310
- expect(result).toBe('hello');
320
+ expect(returnValue).toBe('hello');
321
expect(foo).toBe('barobject');
322
});
323
324
// @gate enableFormActions
325
// @gate enableAsyncActions
326
it("useFormState's dispatch binds the initial state to the provided action", async () => {
317
- let serverActionResult = null;
318
-
319
- const serverAction = serverExports(function action(prevState, formData) {
320
- const newState = {
327
+ const serverAction = serverExports(async function action(
328
+ prevState,
329
+ formData,
330
+ ) {
331
+ return {
332
count: prevState.count + parseInt(formData.get('incrementAmount'), 10),
333
};
323
- serverActionResult = newState;
324
- return newState;
334
});
335
336
const initialState = {count: 1};
357
const span = container.getElementsByTagName('span')[0];
358
expect(span.textContent).toBe('Count: 1');
359
351
- await submit(form);
352
- expect(serverActionResult.count).toBe(6);
360
+ const {returnValue} = await submit(form);
361
+ expect(await returnValue).toEqual({count: 6});
362
+ });
363
+
364
+ // @gate enableFormActions
365
+ // @gate enableAsyncActions
366
+ it('useFormState can reuse state during MPA form submission', async () => {
367
+ const serverAction = serverExports(async function action(
368
+ prevState,
369
+ formData,
370
+ ) {
371
+ return prevState + 1;
372
+ });
373
+
374
+ function Form({action}) {
375
+ const [count, dispatch] = useFormState(action, 1);
376
+ return <form action={dispatch}>{count}</form>;
377
+ }
378
+
379
+ function Client({action}) {
380
+ return (
381
+ <div>
382
+ <Form action={action} />
383
+ <Form action={action} />
384
+ <Form action={action} />
385
+ </div>
386
+ );
387
+ }
388
+
389
+ const ClientRef = await clientExports(Client);
390
+
391
+ const rscStream = ReactServerDOMServer.renderToReadableStream(
392
+ <ClientRef action={serverAction} />,
393
+ webpackMap,
394
+ );
395
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream);
396
+ const ssrStream = await ReactDOMServer.renderToReadableStream(response);
397
+ await readIntoContainer(ssrStream);
398
+
399
+ expect(container.textContent).toBe('111');
400
+
401
+ // There are three identical forms. We're going to submit the second one.
402
+ const form = container.getElementsByTagName('form')[1];
403
+ const {formState} = await submit(form);
404
+
405
+ // Simulate an MPA form submission by resetting the container and
406
+ // rendering again.
407
+ container.innerHTML = '';
408
+
409
+ const postbackRscStream = ReactServerDOMServer.renderToReadableStream(
410
+ <ClientRef action={serverAction} />,
411
+ webpackMap,
412
+ );
413
+ const postbackResponse =
414
+ ReactServerDOMClient.createFromReadableStream(postbackRscStream);
415
+ const postbackSsrStream = await ReactDOMServer.renderToReadableStream(
416
+ postbackResponse,
417
+ {experimental_formState: formState},
418
+ );
419
+ await readIntoContainer(postbackSsrStream);
420
+
421
+ // Only the second form's state should have been updated.
422
+ expect(container.textContent).toBe('121');
423
+
424
+ // Test that it hydrates correctly
425
+ if (__DEV__) {
426
+ // TODO: Can't use our internal act() util that works in production
427
+ // because it works by overriding the timer APIs, which this test module
428
+ // also does. Remove dev condition once FlightServer.act() is available.
429
+ await act(() => {
430
+ ReactDOMClient.hydrateRoot(container, postbackResponse, {
431
+ experimental_formState: formState,
432
+ });
433
+ });
434
+ expect(container.textContent).toBe('121');
435
+ }
436
});
437
438
// @gate enableFormActions