69
}
70
}
71
72
+ function makeDelayedText(Model) {
73
+ let error, _resolve, _reject;
74
+ let promise = new Promise((resolve, reject) => {
75
+ _resolve = () => {
76
+ promise = null;
77
+ resolve();
78
+ };
79
+ _reject = e => {
80
+ error = e;
81
+ promise = null;
82
+ reject(e);
83
+ };
84
+ });
85
+ function DelayedText({children}, data) {
86
+ if (promise) {
87
+ throw promise;
88
+ }
89
+ if (error) {
90
+ throw error;
91
+ }
92
+ return <Model>{children}</Model>;
93
+ }
94
+ return [DelayedText, _resolve, _reject];
95
+ }
96
+
97
it('should resolve HTML using W3C streams', async () => {
98
function Text({children}) {
99
return <span>{children}</span>;
199
return children;
200
}
201
177
- function makeDelayedText() {
178
- let error, _resolve, _reject;
179
- let promise = new Promise((resolve, reject) => {
180
- _resolve = () => {
181
- promise = null;
182
- resolve();
183
- };
184
- _reject = e => {
185
- error = e;
186
- promise = null;
187
- reject(e);
188
- };
189
- });
190
- function DelayedText({children}, data) {
191
- if (promise) {
192
- throw promise;
193
- }
194
- if (error) {
195
- throw error;
196
- }
197
- return <Text>{children}</Text>;
198
- }
199
- return [DelayedText, _resolve, _reject];
200
- }
201
-
202
- const [Friends, resolveFriends] = makeDelayedText();
203
- const [Name, resolveName] = makeDelayedText();
204
- const [Posts, resolvePosts] = makeDelayedText();
205
- const [Photos, resolvePhotos] = makeDelayedText();
206
- const [Games, , rejectGames] = makeDelayedText();
202
+ const [Friends, resolveFriends] = makeDelayedText(Text);
203
+ const [Name, resolveName] = makeDelayedText(Text);
204
+ const [Posts, resolvePosts] = makeDelayedText(Text);
205
+ const [Photos, resolvePhotos] = makeDelayedText(Text);
206
+ const [Games, , rejectGames] = makeDelayedText(Text);
207
208
// View
209
function ProfileDetails({avatar}) {
340
341
expect(reportedErrors).toEqual([]);
342
});
343
+
344
+ it('should close the stream upon completion when rendering to W3C streams', async () => {
345
+ const {Suspense} = React;
346
+
347
+ // Model
348
+ function Text({children}) {
349
+ return children;
350
+ }
351
+
352
+ const [Friends, resolveFriends] = makeDelayedText(Text);
353
+ const [Name, resolveName] = makeDelayedText(Text);
354
+ const [Posts, resolvePosts] = makeDelayedText(Text);
355
+ const [Photos, resolvePhotos] = makeDelayedText(Text);
356
+
357
+ // View
358
+ function ProfileDetails({avatar}) {
359
+ return (
360
+ <div>
361
+ <Name>:name:</Name>
362
+ {avatar}
363
+ </div>
364
+ );
365
+ }
366
+ function ProfileSidebar({friends}) {
367
+ return (
368
+ <div>
369
+ <Photos>:photos:</Photos>
370
+ {friends}
371
+ </div>
372
+ );
373
+ }
374
+ function ProfilePosts({posts}) {
375
+ return <div>{posts}</div>;
376
+ }
377
+
378
+ function ProfileContent() {
379
+ return (
380
+ <Suspense fallback="(loading everything)">
381
+ <ProfileDetails avatar={<Text>:avatar:</Text>} />
382
+ <Suspense fallback={<p>(loading sidebar)</p>}>
383
+ <ProfileSidebar friends={<Friends>:friends:</Friends>} />
384
+ </Suspense>
385
+ <Suspense fallback={<p>(loading posts)</p>}>
386
+ <ProfilePosts posts={<Posts>:posts:</Posts>} />
387
+ </Suspense>
388
+ </Suspense>
389
+ );
390
+ }
391
+
392
+ const model = {
393
+ rootContent: <ProfileContent />,
394
+ };
395
+
396
+ const stream = ReactServerDOMWriter.renderToReadableStream(
397
+ model,
398
+ webpackMap,
399
+ );
400
+
401
+ const reader = stream.getReader();
402
+ const decoder = new TextDecoder();
403
+
404
+ let flightResponse = '';
405
+ let isDone = false;
406
+
407
+ reader.read().then(function progress({done, value}) {
408
+ if (done) {
409
+ isDone = true;
410
+ return;
411
+ }
412
+
413
+ flightResponse += decoder.decode(value);
414
+
415
+ return reader.read().then(progress);
416
+ });
417
+
418
+ // Advance time enough to trigger a nested fallback.
419
+ jest.advanceTimersByTime(500);
420
+
421
+ await act(async () => {});
422
+
423
+ expect(flightResponse).toContain('(loading everything)');
424
+ expect(flightResponse).toContain('(loading sidebar)');
425
+ expect(flightResponse).toContain('(loading posts)');
426
+ expect(flightResponse).not.toContain(':friends:');
427
+ expect(flightResponse).not.toContain(':name:');
428
+
429
+ await act(async () => {
430
+ resolveFriends();
431
+ });
432
+
433
+ expect(flightResponse).toContain(':friends:');
434
+
435
+ await act(async () => {
436
+ resolveName();
437
+ });
438
+
439
+ expect(flightResponse).toContain(':name:');
440
+
441
+ await act(async () => {
442
+ resolvePhotos();
443
+ });
444
+
445
+ expect(flightResponse).toContain(':photos:');
446
+
447
+ await act(async () => {
448
+ resolvePosts();
449
+ });
450
+
451
+ expect(flightResponse).toContain(':posts:');
452
+
453
+ // Final pending chunk is written; stream should be closed.
454
+ expect(isDone).toBeTruthy();
455
+ });
456
});