@@ -69,6 +69,31 @@ describe('ReactFlightDOMBrowser', () => {
69
69
}
70
70
}
71
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
+
72
97
it ( 'should resolve HTML using W3C streams' , async ( ) => {
73
98
function Text ( { children} ) {
74
99
return < span > { children } </ span > ;
@@ -174,36 +199,11 @@ describe('ReactFlightDOMBrowser', () => {
174
199
return children ;
175
200
}
176
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
207
208
208
// View
209
209
function ProfileDetails ( { avatar} ) {
@@ -340,4 +340,117 @@ describe('ReactFlightDOMBrowser', () => {
340
340
341
341
expect ( reportedErrors ) . toEqual ( [ ] ) ;
342
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
+ } ) ;
343
456
} ) ;
0 commit comments