Move renderToString tests out of the streamable API tests (#24724)
ReactDOMFizzServer(Browser/Node)-test file is really meant to just be a very shallow tests of the Browser/Node specific streaming APIs. Most of the general streaming tests are in ReactDOMFizzServer-test instead of testing all streaming related things in each environment. The legacy renderToString APIs are mostly covered by ReactServerRendering-test et al for historical reasons.
Sebastian Markbåge committed
Jun 14, 2022 at 16:48 UTC
567500d71083f84cdd4259a76f8a1289d159747f
2 files changed
+387
-352
packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
+291
-352
@@ -14,20 +14,13 @@ global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStr
14
global.TextEncoder = require('util').TextEncoder;
15
16
let React;
17
-let ReactDOMClient;
17
let ReactDOMFizzServer;
18
let Suspense;
20
-let Scheduler;
21
-let JSDOM;
22
-let document;
23
-let container;
19
20
describe('ReactDOMFizzServer', () => {
21
beforeEach(() => {
22
jest.resetModules();
23
React = require('react');
29
- ReactDOMClient = require('react-dom/client');
30
- Scheduler = require('scheduler');
24
if (__EXPERIMENTAL__) {
25
ReactDOMFizzServer = require('react-dom/server.browser');
26
}
@@ -55,137 +48,90 @@ describe('ReactDOMFizzServer', () => {
48
}
49
}
50
58
- describe('renderToReadableStream', () => {
59
- // @gate experimental
60
- it('should call renderToReadableStream', async () => {
61
- const stream = await ReactDOMFizzServer.renderToReadableStream(
62
- <div>hello world</div>,
63
- );
64
- const result = await readResult(stream);
65
- expect(result).toMatchInlineSnapshot(`"<div>hello world</div>"`);
66
- });
67
-
68
- // @gate experimental
69
- it('should emit DOCTYPE at the root of the document', async () => {
70
- const stream = await ReactDOMFizzServer.renderToReadableStream(
71
- <html>
72
- <body>hello world</body>
73
- </html>,
74
- );
75
- const result = await readResult(stream);
76
- expect(result).toMatchInlineSnapshot(
77
- `"<!DOCTYPE html><html><body>hello world</body></html>"`,
78
- );
79
- });
80
-
81
- // @gate experimental
82
- it('should emit bootstrap script src at the end', async () => {
83
- const stream = await ReactDOMFizzServer.renderToReadableStream(
84
- <div>hello world</div>,
85
- {
86
- bootstrapScriptContent: 'INIT();',
87
- bootstrapScripts: ['init.js'],
88
- bootstrapModules: ['init.mjs'],
89
- },
90
- );
91
- const result = await readResult(stream);
92
- expect(result).toMatchInlineSnapshot(
93
- `"<div>hello world</div><script>INIT();</script><script src=\\"init.js\\" async=\\"\\"></script><script type=\\"module\\" src=\\"init.mjs\\" async=\\"\\"></script>"`,
94
- );
95
- });
96
-
97
- // @gate experimental
98
- it('emits all HTML as one unit if we wait until the end to start', async () => {
99
- let hasLoaded = false;
100
- let resolve;
101
- const promise = new Promise(r => (resolve = r));
102
- function Wait() {
103
- if (!hasLoaded) {
104
- throw promise;
105
- }
106
- return 'Done';
107
- }
108
- let isComplete = false;
109
- const stream = await ReactDOMFizzServer.renderToReadableStream(
110
- <div>
111
- <Suspense fallback="Loading">
112
- <Wait />
113
- </Suspense>
114
- </div>,
115
- );
116
-
117
- stream.allReady.then(() => (isComplete = true));
118
-
119
- await jest.runAllTimers();
120
- expect(isComplete).toBe(false);
121
- // Resolve the loading.
122
- hasLoaded = true;
123
- await resolve();
124
-
125
- await jest.runAllTimers();
126
-
127
- expect(isComplete).toBe(true);
51
+ // @gate experimental
52
+ it('should call renderToReadableStream', async () => {
53
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
54
+ <div>hello world</div>,
55
+ );
56
+ const result = await readResult(stream);
57
+ expect(result).toMatchInlineSnapshot(`"<div>hello world</div>"`);
58
+ });
59
129
- const result = await readResult(stream);
130
- expect(result).toMatchInlineSnapshot(
131
- `"<div><!--$-->Done<!-- --><!--/$--></div>"`,
132
- );
133
- });
60
+ // @gate experimental
61
+ it('should emit DOCTYPE at the root of the document', async () => {
62
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
63
+ <html>
64
+ <body>hello world</body>
65
+ </html>,
66
+ );
67
+ const result = await readResult(stream);
68
+ expect(result).toMatchInlineSnapshot(
69
+ `"<!DOCTYPE html><html><body>hello world</body></html>"`,
70
+ );
71
+ });
72
135
- // @gate experimental
136
- it('should reject the promise when an error is thrown at the root', async () => {
137
- const reportedErrors = [];
138
- let caughtError = null;
139
- try {
140
- await ReactDOMFizzServer.renderToReadableStream(
141
- <div>
142
- <Throw />
143
- </div>,
144
- {
145
- onError(x) {
146
- reportedErrors.push(x);
147
- },
148
- },
149
- );
150
- } catch (error) {
151
- caughtError = error;
152
- }
153
- expect(caughtError).toBe(theError);
154
- expect(reportedErrors).toEqual([theError]);
155
- });
73
+ // @gate experimental
74
+ it('should emit bootstrap script src at the end', async () => {
75
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
76
+ <div>hello world</div>,
77
+ {
78
+ bootstrapScriptContent: 'INIT();',
79
+ bootstrapScripts: ['init.js'],
80
+ bootstrapModules: ['init.mjs'],
81
+ },
82
+ );
83
+ const result = await readResult(stream);
84
+ expect(result).toMatchInlineSnapshot(
85
+ `"<div>hello world</div><script>INIT();</script><script src=\\"init.js\\" async=\\"\\"></script><script type=\\"module\\" src=\\"init.mjs\\" async=\\"\\"></script>"`,
86
+ );
87
+ });
88
157
- // @gate experimental
158
- it('should reject the promise when an error is thrown inside a fallback', async () => {
159
- const reportedErrors = [];
160
- let caughtError = null;
161
- try {
162
- await ReactDOMFizzServer.renderToReadableStream(
163
- <div>
164
- <Suspense fallback={<Throw />}>
165
- <InfiniteSuspend />
166
- </Suspense>
167
- </div>,
168
- {
169
- onError(x) {
170
- reportedErrors.push(x);
171
- },
172
- },
173
- );
174
- } catch (error) {
175
- caughtError = error;
89
+ // @gate experimental
90
+ it('emits all HTML as one unit if we wait until the end to start', async () => {
91
+ let hasLoaded = false;
92
+ let resolve;
93
+ const promise = new Promise(r => (resolve = r));
94
+ function Wait() {
95
+ if (!hasLoaded) {
96
+ throw promise;
97
}
177
- expect(caughtError).toBe(theError);
178
- expect(reportedErrors).toEqual([theError]);
179
- });
98
+ return 'Done';
99
+ }
100
+ let isComplete = false;
101
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
102
+ <div>
103
+ <Suspense fallback="Loading">
104
+ <Wait />
105
+ </Suspense>
106
+ </div>,
107
+ );
108
+
109
+ stream.allReady.then(() => (isComplete = true));
110
+
111
+ await jest.runAllTimers();
112
+ expect(isComplete).toBe(false);
113
+ // Resolve the loading.
114
+ hasLoaded = true;
115
+ await resolve();
116
+
117
+ await jest.runAllTimers();
118
+
119
+ expect(isComplete).toBe(true);
120
+
121
+ const result = await readResult(stream);
122
+ expect(result).toMatchInlineSnapshot(
123
+ `"<div><!--$-->Done<!-- --><!--/$--></div>"`,
124
+ );
125
+ });
126
181
- // @gate experimental
182
- it('should not error the stream when an error is thrown inside suspense boundary', async () => {
183
- const reportedErrors = [];
184
- const stream = await ReactDOMFizzServer.renderToReadableStream(
127
+ // @gate experimental
128
+ it('should reject the promise when an error is thrown at the root', async () => {
129
+ const reportedErrors = [];
130
+ let caughtError = null;
131
+ try {
132
+ await ReactDOMFizzServer.renderToReadableStream(
133
<div>
186
- <Suspense fallback={<div>Loading</div>}>
187
- <Throw />
188
- </Suspense>
134
+ <Throw />
135
</div>,
136
{
137
onError(x) {
@@ -193,263 +139,256 @@ describe('ReactDOMFizzServer', () => {
139
},
140
},
141
);
142
+ } catch (error) {
143
+ caughtError = error;
144
+ }
145
+ expect(caughtError).toBe(theError);
146
+ expect(reportedErrors).toEqual([theError]);
147
+ });
148
197
- const result = await readResult(stream);
198
- expect(result).toContain('Loading');
199
- expect(reportedErrors).toEqual([theError]);
200
- });
201
-
202
- // @gate experimental
203
- it('should be able to complete by aborting even if the promise never resolves', async () => {
204
- const errors = [];
205
- const controller = new AbortController();
206
- const stream = await ReactDOMFizzServer.renderToReadableStream(
149
+ // @gate experimental
150
+ it('should reject the promise when an error is thrown inside a fallback', async () => {
151
+ const reportedErrors = [];
152
+ let caughtError = null;
153
+ try {
154
+ await ReactDOMFizzServer.renderToReadableStream(
155
<div>
208
- <Suspense fallback={<div>Loading</div>}>
156
+ <Suspense fallback={<Throw />}>
157
<InfiniteSuspend />
158
</Suspense>
159
</div>,
160
{
213
- signal: controller.signal,
161
onError(x) {
215
- errors.push(x.message);
162
+ reportedErrors.push(x);
163
},
164
},
165
);
166
+ } catch (error) {
167
+ caughtError = error;
168
+ }
169
+ expect(caughtError).toBe(theError);
170
+ expect(reportedErrors).toEqual([theError]);
171
+ });
172
220
- controller.abort();
221
-
222
- const result = await readResult(stream);
223
- expect(result).toContain('Loading');
224
-
225
- expect(errors).toEqual([
226
- 'The render was aborted by the server without a reason.',
227
- ]);
228
- });
229
-
230
- // @gate experimental
231
- it('should not continue rendering after the reader cancels', async () => {
232
- let hasLoaded = false;
233
- let resolve;
234
- let isComplete = false;
235
- let rendered = false;
236
- const promise = new Promise(r => (resolve = r));
237
- function Wait() {
238
- if (!hasLoaded) {
239
- throw promise;
240
- }
241
- rendered = true;
242
- return 'Done';
243
- }
244
- const errors = [];
245
- const stream = await ReactDOMFizzServer.renderToReadableStream(
246
- <div>
247
- <Suspense fallback={<div>Loading</div>}>
248
- <Wait /> />
249
- </Suspense>
250
- </div>,
251
- {
252
- onError(x) {
253
- errors.push(x.message);
254
- },
173
+ // @gate experimental
174
+ it('should not error the stream when an error is thrown inside suspense boundary', async () => {
175
+ const reportedErrors = [];
176
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
177
+ <div>
178
+ <Suspense fallback={<div>Loading</div>}>
179
+ <Throw />
180
+ </Suspense>
181
+ </div>,
182
+ {
183
+ onError(x) {
184
+ reportedErrors.push(x);
185
},
256
- );
257
-
258
- stream.allReady.then(() => (isComplete = true));
259
-
260
- expect(rendered).toBe(false);
261
- expect(isComplete).toBe(false);
262
-
263
- const reader = stream.getReader();
264
- reader.cancel();
265
-
266
- expect(errors).toEqual([
267
- 'The render was aborted by the server without a reason.',
268
- ]);
269
-
270
- hasLoaded = true;
271
- resolve();
272
-
273
- await jest.runAllTimers();
186
+ },
187
+ );
188
275
- expect(rendered).toBe(false);
276
- expect(isComplete).toBe(true);
277
- });
189
+ const result = await readResult(stream);
190
+ expect(result).toContain('Loading');
191
+ expect(reportedErrors).toEqual([theError]);
192
+ });
193
279
- // @gate experimental
280
- it('should stream large contents that might overlow individual buffers', async () => {
281
- const str492 = `(492) This string is intentionally 492 bytes long because we want to make sure we process chunks that will overflow buffer boundaries. It will repeat to fill out the bytes required (inclusive of this prompt):: foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux q :: total count (492)`;
282
- const str2049 = `(2049) This string is intentionally 2049 bytes long because we want to make sure we process chunks that will overflow buffer boundaries. It will repeat to fill out the bytes required (inclusive of this prompt):: foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy :: total count (2049)`;
283
-
284
- // this specific layout is somewhat contrived to exercise the landing on
285
- // an exact view boundary. it's not critical to test this edge case but
286
- // since we are setting up a test in general for larger chunks I contrived it
287
- // as such for now. I don't think it needs to be maintained if in the future
288
- // the view sizes change or become dynamic becasue of the use of byobRequest
289
- let stream;
290
- stream = await ReactDOMFizzServer.renderToReadableStream(
291
- <>
292
- <div>
293
- <span>{''}</span>
294
- </div>
295
- <div>{str492}</div>
296
- <div>{str492}</div>
297
- </>,
298
- );
194
+ // @gate experimental
195
+ it('should be able to complete by aborting even if the promise never resolves', async () => {
196
+ const errors = [];
197
+ const controller = new AbortController();
198
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
199
+ <div>
200
+ <Suspense fallback={<div>Loading</div>}>
201
+ <InfiniteSuspend />
202
+ </Suspense>
203
+ </div>,
204
+ {
205
+ signal: controller.signal,
206
+ onError(x) {
207
+ errors.push(x.message);
208
+ },
209
+ },
210
+ );
211
300
- let result;
301
- result = await readResult(stream);
302
- expect(result).toMatchInlineSnapshot(
303
- `"<div><span></span></div><div>${str492}</div><div>${str492}</div>"`,
304
- );
212
+ controller.abort();
213
306
- // this size 2049 was chosen to be a couple base 2 orders larger than the current view
307
- // size. if the size changes in the future hopefully this will still exercise
308
- // a chunk that is too large for the view size.
309
- stream = await ReactDOMFizzServer.renderToReadableStream(
310
- <>
311
- <div>{str2049}</div>
312
- </>,
313
- );
214
+ const result = await readResult(stream);
215
+ expect(result).toContain('Loading');
216
315
- result = await readResult(stream);
316
- expect(result).toMatchInlineSnapshot(`"<div>${str2049}</div>"`);
317
- });
217
+ expect(errors).toEqual([
218
+ 'The render was aborted by the server without a reason.',
219
+ ]);
220
+ });
221
319
- // @gate experimental
320
- it('Supports custom abort reasons with a string', async () => {
321
- const promise = new Promise(r => {});
322
- function Wait() {
222
+ // @gate experimental
223
+ it('should not continue rendering after the reader cancels', async () => {
224
+ let hasLoaded = false;
225
+ let resolve;
226
+ let isComplete = false;
227
+ let rendered = false;
228
+ const promise = new Promise(r => (resolve = r));
229
+ function Wait() {
230
+ if (!hasLoaded) {
231
throw promise;
232
}
325
- function App() {
326
- return (
327
- <div>
328
- <p>
329
- <Suspense fallback={'p'}>
330
- <Wait />
331
- </Suspense>
332
- </p>
333
- <span>
334
- <Suspense fallback={'span'}>
335
- <Wait />
336
- </Suspense>
337
- </span>
338
- </div>
339
- );
340
- }
341
-
342
- const errors = [];
343
- const controller = new AbortController();
344
- await ReactDOMFizzServer.renderToReadableStream(<App />, {
345
- signal: controller.signal,
233
+ rendered = true;
234
+ return 'Done';
235
+ }
236
+ const errors = [];
237
+ const stream = await ReactDOMFizzServer.renderToReadableStream(
238
+ <div>
239
+ <Suspense fallback={<div>Loading</div>}>
240
+ <Wait /> />
241
+ </Suspense>
242
+ </div>,
243
+ {
244
onError(x) {
347
- errors.push(x);
348
- return 'a digest';
245
+ errors.push(x.message);
246
},
350
- });
247
+ },
248
+ );
249
352
- // @TODO this is a hack to work around lack of support for abortSignal.reason in node
353
- // The abort call itself should set this property but since we are testing in node we
354
- // set it here manually
355
- controller.signal.reason = 'foobar';
356
- controller.abort('foobar');
250
+ stream.allReady.then(() => (isComplete = true));
251
358
- expect(errors).toEqual(['foobar', 'foobar']);
359
- });
252
+ expect(rendered).toBe(false);
253
+ expect(isComplete).toBe(false);
254
361
- // @gate experimental
362
- it('Supports custom abort reasons with an Error', async () => {
363
- const promise = new Promise(r => {});
364
- function Wait() {
365
- throw promise;
366
- }
367
- function App() {
368
- return (
369
- <div>
370
- <p>
371
- <Suspense fallback={'p'}>
372
- <Wait />
373
- </Suspense>
374
- </p>
375
- <span>
376
- <Suspense fallback={'span'}>
377
- <Wait />
378
- </Suspense>
379
- </span>
380
- </div>
381
- );
382
- }
255
+ const reader = stream.getReader();
256
+ reader.cancel();
257
384
- const errors = [];
385
- const controller = new AbortController();
386
- await ReactDOMFizzServer.renderToReadableStream(<App />, {
387
- signal: controller.signal,
388
- onError(x) {
389
- errors.push(x.message);
390
- return 'a digest';
391
- },
392
- });
258
+ expect(errors).toEqual([
259
+ 'The render was aborted by the server without a reason.',
260
+ ]);
261
394
- // @TODO this is a hack to work around lack of support for abortSignal.reason in node
395
- // The abort call itself should set this property but since we are testing in node we
396
- // set it here manually
397
- controller.signal.reason = new Error('uh oh');
398
- controller.abort(new Error('uh oh'));
262
+ hasLoaded = true;
263
+ resolve();
264
400
- expect(errors).toEqual(['uh oh', 'uh oh']);
401
- });
265
+ await jest.runAllTimers();
266
+
267
+ expect(rendered).toBe(false);
268
+ expect(isComplete).toBe(true);
269
});
270
404
- describe('renderToString', () => {
405
- beforeEach(() => {
406
- JSDOM = require('jsdom').JSDOM;
271
+ // @gate experimental
272
+ it('should stream large contents that might overlow individual buffers', async () => {
273
+ const str492 = `(492) This string is intentionally 492 bytes long because we want to make sure we process chunks that will overflow buffer boundaries. It will repeat to fill out the bytes required (inclusive of this prompt):: foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux q :: total count (492)`;
274
+ const str2049 = `(2049) This string is intentionally 2049 bytes long because we want to make sure we process chunks that will overflow buffer boundaries. It will repeat to fill out the bytes required (inclusive of this prompt):: foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy thud foo bar qux quux corge grault garply waldo fred plugh xyzzy :: total count (2049)`;
275
+
276
+ // this specific layout is somewhat contrived to exercise the landing on
277
+ // an exact view boundary. it's not critical to test this edge case but
278
+ // since we are setting up a test in general for larger chunks I contrived it
279
+ // as such for now. I don't think it needs to be maintained if in the future
280
+ // the view sizes change or become dynamic becasue of the use of byobRequest
281
+ let stream;
282
+ stream = await ReactDOMFizzServer.renderToReadableStream(
283
+ <>
284
+ <div>
285
+ <span>{''}</span>
286
+ </div>
287
+ <div>{str492}</div>
288
+ <div>{str492}</div>
289
+ </>,
290
+ );
291
+
292
+ let result;
293
+ result = await readResult(stream);
294
+ expect(result).toMatchInlineSnapshot(
295
+ `"<div><span></span></div><div>${str492}</div><div>${str492}</div>"`,
296
+ );
297
+
298
+ // this size 2049 was chosen to be a couple base 2 orders larger than the current view
299
+ // size. if the size changes in the future hopefully this will still exercise
300
+ // a chunk that is too large for the view size.
301
+ stream = await ReactDOMFizzServer.renderToReadableStream(
302
+ <>
303
+ <div>{str2049}</div>
304
+ </>,
305
+ );
306
+
307
+ result = await readResult(stream);
308
+ expect(result).toMatchInlineSnapshot(`"<div>${str2049}</div>"`);
309
+ });
310
408
- // Test Environment
409
- const jsdom = new JSDOM(
410
- '<!DOCTYPE html><html><head></head><body><div id="container">',
411
- {
412
- runScripts: 'dangerously',
413
- },
311
+ // @gate experimental
312
+ it('Supports custom abort reasons with a string', async () => {
313
+ const promise = new Promise(r => {});
314
+ function Wait() {
315
+ throw promise;
316
+ }
317
+ function App() {
318
+ return (
319
+ <div>
320
+ <p>
321
+ <Suspense fallback={'p'}>
322
+ <Wait />
323
+ </Suspense>
324
+ </p>
325
+ <span>
326
+ <Suspense fallback={'span'}>
327
+ <Wait />
328
+ </Suspense>
329
+ </span>
330
+ </div>
331
);
415
- document = jsdom.window.document;
416
- container = document.getElementById('container');
332
+ }
333
+
334
+ const errors = [];
335
+ const controller = new AbortController();
336
+ await ReactDOMFizzServer.renderToReadableStream(<App />, {
337
+ signal: controller.signal,
338
+ onError(x) {
339
+ errors.push(x);
340
+ return 'a digest';
341
+ },
342
});
343
419
- // @gate experimental
420
- it('refers users to apis that support Suspense when somethign suspends', () => {
421
- function App({isClient}) {
422
- return (
423
- <div>
424
- <Suspense fallback={'fallback'}>
425
- {isClient ? 'resolved' : <InfiniteSuspend />}
344
+ // @TODO this is a hack to work around lack of support for abortSignal.reason in node
345
+ // The abort call itself should set this property but since we are testing in node we
346
+ // set it here manually
347
+ controller.signal.reason = 'foobar';
348
+ controller.abort('foobar');
349
+
350
+ expect(errors).toEqual(['foobar', 'foobar']);
351
+ });
352
+
353
+ // @gate experimental
354
+ it('Supports custom abort reasons with an Error', async () => {
355
+ const promise = new Promise(r => {});
356
+ function Wait() {
357
+ throw promise;
358
+ }
359
+ function App() {
360
+ return (
361
+ <div>
362
+ <p>
363
+ <Suspense fallback={'p'}>
364
+ <Wait />
365
</Suspense>
427
- </div>
428
- );
429
- }
430
- container.innerHTML = ReactDOMFizzServer.renderToString(
431
- <App isClient={false} />,
366
+ </p>
367
+ <span>
368
+ <Suspense fallback={'span'}>
369
+ <Wait />
370
+ </Suspense>
371
+ </span>
372
+ </div>
373
);
374
+ }
375
434
- const errors = [];
435
- ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
436
- onRecoverableError(error, errorInfo) {
437
- errors.push(error.message);
438
- },
439
- });
440
-
441
- expect(Scheduler).toFlushAndYield([]);
442
- expect(errors.length).toBe(1);
443
- if (__DEV__) {
444
- expect(errors[0]).toBe(
445
- 'The server did not finish this Suspense boundary: The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server',
446
- );
447
- } else {
448
- expect(errors[0]).toBe(
449
- 'The server could not finish this Suspense boundary, likely due to ' +
450
- 'an error during server rendering. Switched to client rendering.',
451
- );
452
- }
376
+ const errors = [];
377
+ const controller = new AbortController();
378
+ await ReactDOMFizzServer.renderToReadableStream(<App />, {
379
+ signal: controller.signal,
380
+ onError(x) {
381
+ errors.push(x.message);
382
+ return 'a digest';
383
+ },
384
});
385
+
386
+ // @TODO this is a hack to work around lack of support for abortSignal.reason in node
387
+ // The abort call itself should set this property but since we are testing in node we
388
+ // set it here manually
389
+ controller.signal.reason = new Error('uh oh');
390
+ controller.abort(new Error('uh oh'));
391
+
392
+ expect(errors).toEqual(['uh oh', 'uh oh']);
393
});
394
});
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+96
@@ -9,10 +9,13 @@
9
10
'use strict';
11
12
+global.TextEncoder = require('util').TextEncoder;
13
+
14
let React;
15
let ReactDOM;
16
let ReactDOMClient;
17
let ReactDOMServer;
18
+let ReactDOMServerBrowser;
19
let Scheduler;
20
21
// These tests rely both on ReactDOMServer and ReactDOM.
@@ -24,6 +27,7 @@ describe('ReactDOMServerHydration', () => {
27
ReactDOM = require('react-dom');
28
ReactDOMClient = require('react-dom/client');
29
ReactDOMServer = require('react-dom/server');
30
+ ReactDOMServerBrowser = require('react-dom/server.browser');
31
Scheduler = require('scheduler');
32
});
33
@@ -598,4 +602,96 @@ describe('ReactDOMServerHydration', () => {
602
expect(customElement.str).toBe(undefined);
603
expect(customElement.obj).toBe(undefined);
604
});
605
+
606
+ // @gate experimental || !www || !__DEV__
607
+ it('refers users to apis that support Suspense when something suspends', () => {
608
+ const theInfinitePromise = new Promise(() => {});
609
+ function InfiniteSuspend() {
610
+ throw theInfinitePromise;
611
+ }
612
+
613
+ function App({isClient}) {
614
+ return (
615
+ <div>
616
+ <React.Suspense fallback={'fallback'}>
617
+ {isClient ? 'resolved' : <InfiniteSuspend />}
618
+ </React.Suspense>
619
+ </div>
620
+ );
621
+ }
622
+ const container = document.createElement('div');
623
+ container.innerHTML = ReactDOMServer.renderToString(
624
+ <App isClient={false} />,
625
+ );
626
+
627
+ const errors = [];
628
+ ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
629
+ onRecoverableError(error, errorInfo) {
630
+ errors.push(error.message);
631
+ },
632
+ });
633
+
634
+ expect(Scheduler).toFlushAndYield([]);
635
+ expect(errors.length).toBe(1);
636
+ if (__DEV__) {
637
+ expect(errors[0]).toBe(
638
+ 'The server did not finish this Suspense boundary: The server used "renderToString" ' +
639
+ 'which does not support Suspense. If you intended for this Suspense boundary to render ' +
640
+ 'the fallback content on the server consider throwing an Error somewhere within the ' +
641
+ 'Suspense boundary. If you intended to have the server wait for the suspended component ' +
642
+ 'please switch to "renderToPipeableStream" which supports Suspense on the server',
643
+ );
644
+ } else {
645
+ expect(errors[0]).toBe(
646
+ 'The server could not finish this Suspense boundary, likely due to ' +
647
+ 'an error during server rendering. Switched to client rendering.',
648
+ );
649
+ }
650
+ });
651
+
652
+ // @gate experimental || !www || !__DEV__
653
+ it('refers users to apis that support Suspense when something suspends (browser)', () => {
654
+ const theInfinitePromise = new Promise(() => {});
655
+ function InfiniteSuspend() {
656
+ throw theInfinitePromise;
657
+ }
658
+
659
+ function App({isClient}) {
660
+ return (
661
+ <div>
662
+ <React.Suspense fallback={'fallback'}>
663
+ {isClient ? 'resolved' : <InfiniteSuspend />}
664
+ </React.Suspense>
665
+ </div>
666
+ );
667
+ }
668
+ const container = document.createElement('div');
669
+ container.innerHTML = ReactDOMServerBrowser.renderToString(
670
+ <App isClient={false} />,
671
+ );
672
+
673
+ const errors = [];
674
+ ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
675
+ onRecoverableError(error, errorInfo) {
676
+ errors.push(error.message);
677
+ },
678
+ });
679
+
680
+ expect(Scheduler).toFlushAndYield([]);
681
+ expect(errors.length).toBe(1);
682
+ if (__DEV__) {
683
+ expect(errors[0]).toBe(
684
+ 'The server did not finish this Suspense boundary: The server used "renderToString" ' +
685
+ 'which does not support Suspense. If you intended for this Suspense boundary to render ' +
686
+ 'the fallback content on the server consider throwing an Error somewhere within the ' +
687
+ 'Suspense boundary. If you intended to have the server wait for the suspended component ' +
688
+ 'please switch to "renderToReadableStream" which supports Suspense on the server',
689
+ );
690
+ } else {
691
+ expect(errors[0]).toBe(
692
+ 'The server could not finish this Suspense boundary, likely due to ' +
693
+ 'an error during server rendering. Switched to client rendering.',
694
+ );
695
+ }
696
+ });
697
});