@@ -207,6 +207,230 @@ describe('ReactDOMFizzServer', () => {
207
207
return readText ( text ) ;
208
208
}
209
209
210
+ // @gate experimental
211
+ it ( 'should asynchronously load a lazy component' , async ( ) => {
212
+ let resolveA ;
213
+ const LazyA = React . lazy ( ( ) => {
214
+ return new Promise ( r => {
215
+ resolveA = r ;
216
+ } ) ;
217
+ } ) ;
218
+
219
+ let resolveB ;
220
+ const LazyB = React . lazy ( ( ) => {
221
+ return new Promise ( r => {
222
+ resolveB = r ;
223
+ } ) ;
224
+ } ) ;
225
+
226
+ function TextWithPunctuation ( { text, punctuation} ) {
227
+ return < Text text = { text + punctuation } /> ;
228
+ }
229
+ // This tests that default props of the inner element is resolved.
230
+ TextWithPunctuation . defaultProps = {
231
+ punctuation : '!' ,
232
+ } ;
233
+
234
+ await act ( async ( ) => {
235
+ const { startWriting} = ReactDOMFizzServer . pipeToNodeWritable (
236
+ < div >
237
+ < div >
238
+ < Suspense fallback = { < Text text = "Loading..." /> } >
239
+ < LazyA text = "Hello" />
240
+ </ Suspense >
241
+ </ div >
242
+ < div >
243
+ < Suspense fallback = { < Text text = "Loading..." /> } >
244
+ < LazyB text = "world" />
245
+ </ Suspense >
246
+ </ div >
247
+ </ div > ,
248
+ writable ,
249
+ ) ;
250
+ startWriting ( ) ;
251
+ } ) ;
252
+ expect ( getVisibleChildren ( container ) ) . toEqual (
253
+ < div >
254
+ < div > Loading...</ div >
255
+ < div > Loading...</ div >
256
+ </ div > ,
257
+ ) ;
258
+ await act ( async ( ) => {
259
+ resolveA ( { default : Text } ) ;
260
+ } ) ;
261
+ expect ( getVisibleChildren ( container ) ) . toEqual (
262
+ < div >
263
+ < div > Hello</ div >
264
+ < div > Loading...</ div >
265
+ </ div > ,
266
+ ) ;
267
+ await act ( async ( ) => {
268
+ resolveB ( { default : TextWithPunctuation } ) ;
269
+ } ) ;
270
+ expect ( getVisibleChildren ( container ) ) . toEqual (
271
+ < div >
272
+ < div > Hello</ div >
273
+ < div > world!</ div >
274
+ </ div > ,
275
+ ) ;
276
+ } ) ;
277
+
278
+ // @gate experimental
279
+ it ( 'should client render a boundary if a lazy component rejects' , async ( ) => {
280
+ let rejectComponent ;
281
+ const LazyComponent = React . lazy ( ( ) => {
282
+ return new Promise ( ( resolve , reject ) => {
283
+ rejectComponent = reject ;
284
+ } ) ;
285
+ } ) ;
286
+
287
+ const loggedErrors = [ ] ;
288
+
289
+ function App ( { isClient} ) {
290
+ return (
291
+ < div >
292
+ < Suspense fallback = { < Text text = "Loading..." /> } >
293
+ { isClient ? < Text text = "Hello" /> : < LazyComponent text = "Hello" /> }
294
+ </ Suspense >
295
+ </ div >
296
+ ) ;
297
+ }
298
+
299
+ await act ( async ( ) => {
300
+ const { startWriting} = ReactDOMFizzServer . pipeToNodeWritable (
301
+ < App isClient = { false } /> ,
302
+ writable ,
303
+ {
304
+ onError ( x ) {
305
+ loggedErrors . push ( x ) ;
306
+ } ,
307
+ } ,
308
+ ) ;
309
+ startWriting ( ) ;
310
+ } ) ;
311
+ expect ( loggedErrors ) . toEqual ( [ ] ) ;
312
+
313
+ // Attempt to hydrate the content.
314
+ const root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
315
+ root . render ( < App isClient = { true } /> ) ;
316
+ Scheduler . unstable_flushAll ( ) ;
317
+
318
+ // We're still loading because we're waiting for the server to stream more content.
319
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Loading...</ div > ) ;
320
+
321
+ expect ( loggedErrors ) . toEqual ( [ ] ) ;
322
+
323
+ const theError = new Error ( 'Test' ) ;
324
+ await act ( async ( ) => {
325
+ rejectComponent ( theError ) ;
326
+ } ) ;
327
+
328
+ expect ( loggedErrors ) . toEqual ( [ theError ] ) ;
329
+
330
+ // We haven't ran the client hydration yet.
331
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Loading...</ div > ) ;
332
+
333
+ // Now we can client render it instead.
334
+ Scheduler . unstable_flushAll ( ) ;
335
+
336
+ // The client rendered HTML is now in place.
337
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Hello</ div > ) ;
338
+
339
+ expect ( loggedErrors ) . toEqual ( [ theError ] ) ;
340
+ } ) ;
341
+
342
+ // @gate experimental
343
+ it ( 'should asynchronously load a lazy element' , async ( ) => {
344
+ let resolveElement ;
345
+ const lazyElement = React . lazy ( ( ) => {
346
+ return new Promise ( r => {
347
+ resolveElement = r ;
348
+ } ) ;
349
+ } ) ;
350
+
351
+ await act ( async ( ) => {
352
+ const { startWriting} = ReactDOMFizzServer . pipeToNodeWritable (
353
+ < div >
354
+ < Suspense fallback = { < Text text = "Loading..." /> } >
355
+ { lazyElement }
356
+ </ Suspense >
357
+ </ div > ,
358
+ writable ,
359
+ ) ;
360
+ startWriting ( ) ;
361
+ } ) ;
362
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Loading...</ div > ) ;
363
+ await act ( async ( ) => {
364
+ resolveElement ( { default : < Text text = "Hello" /> } ) ;
365
+ } ) ;
366
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Hello</ div > ) ;
367
+ } ) ;
368
+
369
+ // @gate experimental
370
+ it ( 'should client render a boundary if a lazy element rejects' , async ( ) => {
371
+ let rejectElement ;
372
+ const element = < Text text = "Hello" /> ;
373
+ const lazyElement = React . lazy ( ( ) => {
374
+ return new Promise ( ( resolve , reject ) => {
375
+ rejectElement = reject ;
376
+ } ) ;
377
+ } ) ;
378
+
379
+ const loggedErrors = [ ] ;
380
+
381
+ function App ( { isClient} ) {
382
+ return (
383
+ < div >
384
+ < Suspense fallback = { < Text text = "Loading..." /> } >
385
+ { isClient ? element : lazyElement }
386
+ </ Suspense >
387
+ </ div >
388
+ ) ;
389
+ }
390
+
391
+ await act ( async ( ) => {
392
+ const { startWriting} = ReactDOMFizzServer . pipeToNodeWritable (
393
+ < App isClient = { false } /> ,
394
+ writable ,
395
+ {
396
+ onError ( x ) {
397
+ loggedErrors . push ( x ) ;
398
+ } ,
399
+ } ,
400
+ ) ;
401
+ startWriting ( ) ;
402
+ } ) ;
403
+ expect ( loggedErrors ) . toEqual ( [ ] ) ;
404
+
405
+ // Attempt to hydrate the content.
406
+ const root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
407
+ root . render ( < App isClient = { true } /> ) ;
408
+ Scheduler . unstable_flushAll ( ) ;
409
+
410
+ // We're still loading because we're waiting for the server to stream more content.
411
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Loading...</ div > ) ;
412
+
413
+ expect ( loggedErrors ) . toEqual ( [ ] ) ;
414
+
415
+ const theError = new Error ( 'Test' ) ;
416
+ await act ( async ( ) => {
417
+ rejectElement ( theError ) ;
418
+ } ) ;
419
+
420
+ expect ( loggedErrors ) . toEqual ( [ theError ] ) ;
421
+
422
+ // We haven't ran the client hydration yet.
423
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Loading...</ div > ) ;
424
+
425
+ // Now we can client render it instead.
426
+ Scheduler . unstable_flushAll ( ) ;
427
+
428
+ // The client rendered HTML is now in place.
429
+ expect ( getVisibleChildren ( container ) ) . toEqual ( < div > Hello</ div > ) ;
430
+
431
+ expect ( loggedErrors ) . toEqual ( [ theError ] ) ;
432
+ } ) ;
433
+
210
434
// @gate experimental
211
435
it ( 'should asynchronously load the suspense boundary' , async ( ) => {
212
436
await act ( async ( ) => {
0 commit comments