23
// Patch for Edge environments for global scope
24
global.AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
25
26
-// Don't wait before processing work on the server.
27
-// TODO: we can replace this with FlightServer.act().
28
-global.setTimeout = cb => cb();
26
+const {
27
+ patchMessageChannel,
28
+} = require('../../../../scripts/jest/patchMessageChannel');
29
30
let serverExports;
31
let clientExports;
36
let ReactServer;
37
let ReactDOMServer;
38
let ReactServerDOMServer;
39
+let ReactServerDOMStaticServer;
40
let ReactServerDOMClient;
41
let use;
42
+let ReactServerScheduler;
43
+let reactServerAct;
44
45
function normalizeCodeLocInfo(str) {
46
return (
55
beforeEach(() => {
56
jest.resetModules();
57
58
+ ReactServerScheduler = require('scheduler');
59
+ patchMessageChannel(ReactServerScheduler);
60
+ reactServerAct = require('internal-test-utils').act;
61
+
62
// Simulate the condition resolution
63
jest.mock('react', () => require('react/react.react-server'));
64
jest.mock('react-server-dom-webpack/server', () =>
75
76
ReactServer = require('react');
77
ReactServerDOMServer = require('react-server-dom-webpack/server');
78
+ if (__EXPERIMENTAL__) {
79
+ jest.mock('react-server-dom-webpack/static', () =>
80
+ require('react-server-dom-webpack/static.edge'),
81
+ );
82
+ ReactServerDOMStaticServer = require('react-server-dom-webpack/static');
83
+ }
84
85
jest.resetModules();
86
__unmockReact();
94
use = React.use;
95
});
96
97
+ async function serverAct(callback) {
98
+ let maybePromise;
99
+ await reactServerAct(() => {
100
+ maybePromise = callback();
101
+ if (maybePromise && typeof maybePromise.catch === 'function') {
102
+ maybePromise.catch(() => {});
103
+ }
104
+ });
105
+ return maybePromise;
106
+ }
107
+
108
function passThrough(stream) {
109
// Simulate more realistic network by splitting up and rejoining some chunks.
110
// This lets us test that we don't accidentally rely on particular bounds of the chunks.
198
return <ClientComponentOnTheClient />;
199
}
200
177
- const stream = ReactServerDOMServer.renderToReadableStream(
178
- <App />,
179
- webpackMap,
201
+ const stream = await serverAct(() =>
202
+ ReactServerDOMServer.renderToReadableStream(<App />, webpackMap),
203
);
204
const response = ReactServerDOMClient.createFromReadableStream(stream, {
205
ssrManifest: {
212
return use(response);
213
}
214
192
- const ssrStream = await ReactDOMServer.renderToReadableStream(
193
- <ClientRoot />,
215
+ const ssrStream = await serverAct(() =>
216
+ ReactDOMServer.renderToReadableStream(<ClientRoot />),
217
);
218
const result = await readResult(ssrStream);
219
expect(result).toEqual('<span>Client Component</span>');
223
const testString = '"\n\t'.repeat(500) + '🙃';
224
const testString2 = 'hello'.repeat(400);
225
203
- const stream = ReactServerDOMServer.renderToReadableStream({
204
- text: testString,
205
- text2: testString2,
206
- });
226
+ const stream = await serverAct(() =>
227
+ ReactServerDOMServer.renderToReadableStream({
228
+ text: testString,
229
+ text2: testString2,
230
+ }),
231
+ );
232
const [stream1, stream2] = passThrough(stream).tee();
233
234
const serializedContent = await readResult(stream1);
259
with: {many: 'properties in it'},
260
};
261
const props = {root: <div>{new Array(30).fill(obj)}</div>};
237
- const stream = ReactServerDOMServer.renderToReadableStream(props);
262
+ const stream = await serverAct(() =>
263
+ ReactServerDOMServer.renderToReadableStream(props),
264
+ );
265
const [stream1, stream2] = passThrough(stream).tee();
266
267
const serializedContent = await readResult(stream1);
329
</>
330
);
331
const resolvedChildren = new Array(30).fill(str);
305
- const stream = ReactServerDOMServer.renderToReadableStream(children);
332
+ const stream = await serverAct(() =>
333
+ ReactServerDOMServer.renderToReadableStream(children),
334
+ );
335
const [stream1, stream2] = passThrough(stream).tee();
336
337
const serializedContent = await readResult(stream1);
347
});
348
349
// Use the SSR render to resolve any lazy elements
321
- const ssrStream = await ReactDOMServer.renderToReadableStream(model);
350
+ const ssrStream = await serverAct(() =>
351
+ ReactDOMServer.renderToReadableStream(model),
352
+ );
353
// Should still match the result when parsed
354
const result = await readResult(ssrStream);
355
expect(result).toEqual(resolvedChildren.join('<!-- -->'));
401
const resolvedChildren = new Array(30).fill(
402
'<div>this is a long return value</div>',
403
);
373
- const stream = ReactServerDOMServer.renderToReadableStream(children);
404
+ const stream = await serverAct(() =>
405
+ ReactServerDOMServer.renderToReadableStream(children),
406
+ );
407
const [stream1, stream2] = passThrough(stream).tee();
408
409
const serializedContent = await readResult(stream1);
410
expect(serializedContent.length).toBeLessThan(__DEV__ ? 605 : 400);
411
expect(timesRendered).toBeLessThan(5);
412
380
- const model = await ReactServerDOMClient.createFromReadableStream(stream2, {
381
- ssrManifest: {
382
- moduleMap: null,
383
- moduleLoading: null,
384
- },
385
- });
413
+ const model = await serverAct(() =>
414
+ ReactServerDOMClient.createFromReadableStream(stream2, {
415
+ ssrManifest: {
416
+ moduleMap: null,
417
+ moduleLoading: null,
418
+ },
419
+ }),
420
+ );
421
422
// Use the SSR render to resolve any lazy elements
388
- const ssrStream = await ReactDOMServer.renderToReadableStream(model);
423
+ const ssrStream = await serverAct(() =>
424
+ ReactDOMServer.renderToReadableStream(model),
425
+ );
426
// Should still match the result when parsed
427
const result = await readResult(ssrStream);
428
expect(result).toEqual(resolvedChildren.join(''));
435
}
436
return <div>Fin</div>;
437
}
401
- const stream = ReactServerDOMServer.renderToReadableStream(
402
- <ServerComponent recurse={20} />,
438
+ const stream = await serverAct(() =>
439
+ ReactServerDOMServer.renderToReadableStream(
440
+ <ServerComponent recurse={20} />,
441
+ ),
442
);
443
const serializedContent = await readResult(stream);
444
const expectedDebugInfoSize = __DEV__ ? 300 * 20 : 0;
465
new BigUint64Array(buffer, 0),
466
new DataView(buffer, 3),
467
];
429
- const stream = passThrough(
430
- ReactServerDOMServer.renderToReadableStream(buffers),
468
+ const stream = await serverAct(() =>
469
+ passThrough(ReactServerDOMServer.renderToReadableStream(buffers)),
470
);
471
const result = await ReactServerDOMClient.createFromReadableStream(stream, {
472
ssrManifest: {
485
const blob = new Blob([bytes, bytes], {
486
type: 'application/x-test',
487
});
449
- const stream = passThrough(
450
- ReactServerDOMServer.renderToReadableStream(blob),
488
+ const stream = await serverAct(() =>
489
+ passThrough(ReactServerDOMServer.renderToReadableStream(blob)),
490
);
491
const result = await ReactServerDOMClient.createFromReadableStream(stream, {
492
ssrManifest: {
515
expect(formData.get('file') instanceof File).toBe(true);
516
expect(formData.get('file').name).toBe('filename.test');
517
479
- const stream = passThrough(
480
- ReactServerDOMServer.renderToReadableStream(formData),
518
+ const stream = await serverAct(() =>
519
+ passThrough(ReactServerDOMServer.renderToReadableStream(formData)),
520
);
521
const result = await ReactServerDOMClient.createFromReadableStream(stream, {
522
ssrManifest: {
546
const map = new Map();
547
map.set('value', awaitedValue);
548
510
- const stream = passThrough(
511
- ReactServerDOMServer.renderToReadableStream(map, webpackMap),
549
+ const stream = await serverAct(() =>
550
+ passThrough(ReactServerDOMServer.renderToReadableStream(map, webpackMap)),
551
);
552
553
// Parsing the root blocks because the module hasn't loaded yet
588
},
589
});
590
552
- const stream = passThrough(
553
- ReactServerDOMServer.renderToReadableStream(s, webpackMap),
591
+ const stream = await serverAct(() =>
592
+ passThrough(ReactServerDOMServer.renderToReadableStream(s, webpackMap)),
593
);
594
556
- const result = await ReactServerDOMClient.createFromReadableStream(stream, {
557
- ssrManifest: {
558
- moduleMap: null,
559
- moduleLoading: null,
560
- },
561
- });
595
+ const result = await serverAct(() =>
596
+ ReactServerDOMClient.createFromReadableStream(stream, {
597
+ ssrManifest: {
598
+ moduleMap: null,
599
+ moduleLoading: null,
600
+ },
601
+ }),
602
+ );
603
604
const reader = result.getReader();
605
630
},
631
};
632
592
- const stream = passThrough(
593
- ReactServerDOMServer.renderToReadableStream(
594
- multiShotIterable,
595
- webpackMap,
633
+ const stream = await serverAct(() =>
634
+ passThrough(
635
+ ReactServerDOMServer.renderToReadableStream(
636
+ multiShotIterable,
637
+ webpackMap,
638
+ ),
639
),
640
);
641
642
// Parsing the root blocks because the module hasn't loaded yet
600
- const result = await ReactServerDOMClient.createFromReadableStream(stream, {
601
- ssrManifest: {
602
- moduleMap: null,
603
- moduleLoading: null,
604
- },
605
- });
643
+ const result = await serverAct(() =>
644
+ ReactServerDOMClient.createFromReadableStream(stream, {
645
+ ssrManifest: {
646
+ moduleMap: null,
647
+ moduleLoading: null,
648
+ },
649
+ }),
650
+ );
651
652
const iterator = result[Symbol.asyncIterator]();
653
680
},
681
};
682
638
- const stream = ReactServerDOMServer.renderToReadableStream({
639
- iterable,
640
- });
683
+ const stream = await serverAct(() =>
684
+ ReactServerDOMServer.renderToReadableStream({
685
+ iterable,
686
+ }),
687
+ );
688
const [stream1, stream2] = passThrough(stream).tee();
689
690
const serializedContent = await readResult(stream1);
775
},
776
});
777
731
- const stream = ReactServerDOMServer.renderToReadableStream(s, {});
778
+ const stream = await serverAct(() =>
779
+ ReactServerDOMServer.renderToReadableStream(s, {}),
780
+ );
781
782
const [stream1, stream2] = passThrough(stream).tee();
783
834
},
835
});
836
788
- const stream = ReactServerDOMServer.renderToReadableStream(s, {});
837
+ const stream = await serverAct(() =>
838
+ ReactServerDOMServer.renderToReadableStream(s, {}),
839
+ );
840
841
const [stream1, stream2] = passThrough(stream).tee();
842
892
greeting: ReactServer.createElement(Greeting, {firstName: 'Seb'}),
893
};
894
844
- const stream = ReactServerDOMServer.renderToReadableStream(
845
- model,
846
- webpackMap,
895
+ const stream = await serverAct(() =>
896
+ ReactServerDOMServer.renderToReadableStream(model, webpackMap),
897
);
898
849
- const rootModel = await ReactServerDOMClient.createFromReadableStream(
850
- stream,
851
- {
899
+ const rootModel = await serverAct(() =>
900
+ ReactServerDOMClient.createFromReadableStream(stream, {
901
ssrManifest: {
902
moduleMap: null,
903
moduleLoading: null,
904
},
856
- },
905
+ }),
906
);
907
859
- const ssrStream = await ReactDOMServer.renderToReadableStream(
860
- rootModel.greeting,
908
+ const ssrStream = await serverAct(() =>
909
+ ReactDOMServer.renderToReadableStream(rootModel.greeting),
910
);
911
const result = await readResult(ssrStream);
912
expect(result).toEqual('<span>Hello, Seb</span>');
965
return ReactServer.createElement('span', null, 'hi');
966
}
967
919
- const stream = ReactServerDOMServer.renderToReadableStream(
920
- ReactServer.createElement(
921
- 'div',
922
- null,
923
- ReactServer.createElement(Foo, null),
968
+ const stream = await serverAct(() =>
969
+ ReactServerDOMServer.renderToReadableStream(
970
+ ReactServer.createElement(
971
+ 'div',
972
+ null,
973
+ ReactServer.createElement(Foo, null),
974
+ ),
975
+ webpackMap,
976
),
925
- webpackMap,
977
);
978
await readResult(stream);
979
994
root: ReactServer.createElement(Erroring),
995
};
996
946
- const stream = ReactServerDOMServer.renderToReadableStream(
947
- model,
948
- webpackMap,
949
- {
997
+ const stream = await serverAct(() =>
998
+ ReactServerDOMServer.renderToReadableStream(model, webpackMap, {
999
onError() {},
951
- },
1000
+ }),
1001
);
1002
954
- const rootModel = await ReactServerDOMClient.createFromReadableStream(
955
- stream,
956
- {
1003
+ const rootModel = await serverAct(() =>
1004
+ ReactServerDOMClient.createFromReadableStream(stream, {
1005
ssrManifest: {
1006
moduleMap: null,
1007
moduleLoading: null,
1008
},
961
- },
1009
+ }),
1010
);
1011
1012
const errors = [];
965
- const result = ReactDOMServer.renderToReadableStream(
966
- <div>{rootModel.root}</div>,
967
- {
1013
+ const result = serverAct(() =>
1014
+ ReactDOMServer.renderToReadableStream(<div>{rootModel.root}</div>, {
1015
onError(error, {componentStack}) {
1016
errors.push({
1017
error,
1018
componentStack: normalizeCodeLocInfo(componentStack),
1019
});
1020
},
974
- },
1021
+ }),
1022
);
1023
1024
const theError = new Error('my error');
1047
},
1048
]);
1049
});
1050
+
1051
+ // @gate experimental
1052
+ it('can prerender', async () => {
1053
+ let resolveGreeting;
1054
+ const greetingPromise = new Promise(resolve => {
1055
+ resolveGreeting = resolve;
1056
+ });
1057
+
1058
+ function App() {
1059
+ return (
1060
+ <div>
1061
+ <Greeting />
1062
+ </div>
1063
+ );
1064
+ }
1065
+
1066
+ async function Greeting() {
1067
+ await greetingPromise;
1068
+ return 'hello world';
1069
+ }
1070
+
1071
+ const {pendingResult} = await serverAct(async () => {
1072
+ // destructure trick to avoid the act scope from awaiting the returned value
1073
+ return {
1074
+ pendingResult: ReactServerDOMStaticServer.prerender(
1075
+ <App />,
1076
+ webpackMap,
1077
+ ),
1078
+ };
1079
+ });
1080
+
1081
+ resolveGreeting();
1082
+ const {prelude} = await pendingResult;
1083
+
1084
+ function ClientRoot({response}) {
1085
+ return use(response);
1086
+ }
1087
+
1088
+ const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1089
+ ssrManifest: {
1090
+ moduleMap: null,
1091
+ moduleLoading: null,
1092
+ },
1093
+ });
1094
+ // Use the SSR render to resolve any lazy elements
1095
+ const ssrStream = await serverAct(() =>
1096
+ ReactDOMServer.renderToReadableStream(
1097
+ React.createElement(ClientRoot, {response}),
1098
+ ),
1099
+ );
1100
+ // Should still match the result when parsed
1101
+ const result = await readResult(ssrStream);
1102
+ expect(result).toBe('<div>hello world</div>');
1103
+ });
1104
+
1105
+ // @gate enableHalt
1106
+ it('serializes unfinished tasks with infinite promises when aborting a prerender', async () => {
1107
+ let resolveGreeting;
1108
+ const greetingPromise = new Promise(resolve => {
1109
+ resolveGreeting = resolve;
1110
+ });
1111
+
1112
+ function App() {
1113
+ return (
1114
+ <div>
1115
+ <ReactServer.Suspense fallback="loading...">
1116
+ <Greeting />
1117
+ </ReactServer.Suspense>
1118
+ </div>
1119
+ );
1120
+ }
1121
+
1122
+ async function Greeting() {
1123
+ await greetingPromise;
1124
+ return 'hello world';
1125
+ }
1126
+
1127
+ const controller = new AbortController();
1128
+ const {pendingResult} = await serverAct(async () => {
1129
+ // destructure trick to avoid the act scope from awaiting the returned value
1130
+ return {
1131
+ pendingResult: ReactServerDOMStaticServer.prerender(
1132
+ <App />,
1133
+ webpackMap,
1134
+ {
1135
+ signal: controller.signal,
1136
+ },
1137
+ ),
1138
+ };
1139
+ });
1140
+
1141
+ controller.abort();
1142
+ resolveGreeting();
1143
+ const {prelude} = await pendingResult;
1144
+
1145
+ function ClientRoot({response}) {
1146
+ return use(response);
1147
+ }
1148
+
1149
+ const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1150
+ ssrManifest: {
1151
+ moduleMap: null,
1152
+ moduleLoading: null,
1153
+ },
1154
+ });
1155
+ const fizzController = new AbortController();
1156
+ const errors = [];
1157
+ const ssrStream = await serverAct(() =>
1158
+ ReactDOMServer.renderToReadableStream(
1159
+ React.createElement(ClientRoot, {response}),
1160
+ {
1161
+ signal: fizzController.signal,
1162
+ onError(error) {
1163
+ errors.push(error);
1164
+ },
1165
+ },
1166
+ ),
1167
+ );
1168
+ fizzController.abort('boom');
1169
+ expect(errors).toEqual(['boom']);
1170
+ // Should still match the result when parsed
1171
+ const result = await readResult(ssrStream);
1172
+ const div = document.createElement('div');
1173
+ div.innerHTML = result;
1174
+ expect(div.textContent).toBe('loading...');
1175
+ });
1176
});