@@ -285,6 +285,117 @@ describe('ReactSuspense', () => {
285
285
expect ( root ) . toMatchRenderedOutput ( 'AsyncAfter SuspenseSibling' ) ;
286
286
} ) ;
287
287
288
+ it ( 'throttles fallback committing globally' , ( ) => {
289
+ function Foo ( ) {
290
+ Scheduler . unstable_yieldValue ( 'Foo' ) ;
291
+ return (
292
+ < Suspense fallback = { < Text text = "Loading..." /> } >
293
+ < AsyncText text = "A" ms = { 200 } />
294
+ < Suspense fallback = { < Text text = "Loading more..." /> } >
295
+ < AsyncText text = "B" ms = { 300 } />
296
+ </ Suspense >
297
+ </ Suspense >
298
+ ) ;
299
+ }
300
+
301
+ // Committing fallbacks should be throttled.
302
+ // First, advance some time to skip the first threshold.
303
+ jest . advanceTimersByTime ( 600 ) ;
304
+ Scheduler . unstable_advanceTime ( 600 ) ;
305
+
306
+ const root = ReactTestRenderer . create ( < Foo /> , {
307
+ unstable_isConcurrent : true ,
308
+ } ) ;
309
+
310
+ expect ( Scheduler ) . toFlushAndYield ( [
311
+ 'Foo' ,
312
+ 'Suspend! [A]' ,
313
+ 'Suspend! [B]' ,
314
+ 'Loading more...' ,
315
+ 'Loading...' ,
316
+ ] ) ;
317
+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
318
+
319
+ // Resolve A.
320
+ jest . advanceTimersByTime ( 200 ) ;
321
+ Scheduler . unstable_advanceTime ( 200 ) ;
322
+ expect ( Scheduler ) . toHaveYielded ( [ 'Promise resolved [A]' ] ) ;
323
+ expect ( Scheduler ) . toFlushAndYield ( [ 'A' , 'Suspend! [B]' , 'Loading more...' ] ) ;
324
+
325
+ // By this point, we have enough info to show "A" and "Loading more..."
326
+ // However, we've just shown the outer fallback. So we'll delay
327
+ // showing the inner fallback hoping that B will resolve soon enough.
328
+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
329
+
330
+ // Resolve B.
331
+ jest . advanceTimersByTime ( 100 ) ;
332
+ Scheduler . unstable_advanceTime ( 100 ) ;
333
+ expect ( Scheduler ) . toHaveYielded ( [ 'Promise resolved [B]' ] ) ;
334
+
335
+ // By this point, B has resolved.
336
+ // We're still showing the outer fallback.
337
+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
338
+ expect ( Scheduler ) . toFlushAndYield ( [ 'A' , 'B' ] ) ;
339
+ // Then contents of both should pop in together.
340
+ expect ( root ) . toMatchRenderedOutput ( 'AB' ) ;
341
+ } ) ;
342
+
343
+ it ( 'does not throttle fallback committing for too long' , ( ) => {
344
+ function Foo ( ) {
345
+ Scheduler . unstable_yieldValue ( 'Foo' ) ;
346
+ return (
347
+ < Suspense fallback = { < Text text = "Loading..." /> } >
348
+ < AsyncText text = "A" ms = { 200 } />
349
+ < Suspense fallback = { < Text text = "Loading more..." /> } >
350
+ < AsyncText text = "B" ms = { 1200 } />
351
+ </ Suspense >
352
+ </ Suspense >
353
+ ) ;
354
+ }
355
+
356
+ // Committing fallbacks should be throttled.
357
+ // First, advance some time to skip the first threshold.
358
+ jest . advanceTimersByTime ( 600 ) ;
359
+ Scheduler . unstable_advanceTime ( 600 ) ;
360
+
361
+ const root = ReactTestRenderer . create ( < Foo /> , {
362
+ unstable_isConcurrent : true ,
363
+ } ) ;
364
+
365
+ expect ( Scheduler ) . toFlushAndYield ( [
366
+ 'Foo' ,
367
+ 'Suspend! [A]' ,
368
+ 'Suspend! [B]' ,
369
+ 'Loading more...' ,
370
+ 'Loading...' ,
371
+ ] ) ;
372
+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
373
+
374
+ // Resolve A.
375
+ jest . advanceTimersByTime ( 200 ) ;
376
+ Scheduler . unstable_advanceTime ( 200 ) ;
377
+ expect ( Scheduler ) . toHaveYielded ( [ 'Promise resolved [A]' ] ) ;
378
+ expect ( Scheduler ) . toFlushAndYield ( [ 'A' , 'Suspend! [B]' , 'Loading more...' ] ) ;
379
+
380
+ // By this point, we have enough info to show "A" and "Loading more..."
381
+ // However, we've just shown the outer fallback. So we'll delay
382
+ // showing the inner fallback hoping that B will resolve soon enough.
383
+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
384
+
385
+ // Wait some more. B is still not resolving.
386
+ jest . advanceTimersByTime ( 500 ) ;
387
+ Scheduler . unstable_advanceTime ( 500 ) ;
388
+ // Give up and render A with a spinner for B.
389
+ expect ( root ) . toMatchRenderedOutput ( 'ALoading more...' ) ;
390
+
391
+ // Resolve B.
392
+ jest . advanceTimersByTime ( 500 ) ;
393
+ Scheduler . unstable_advanceTime ( 500 ) ;
394
+ expect ( Scheduler ) . toHaveYielded ( [ 'Promise resolved [B]' ] ) ;
395
+ expect ( Scheduler ) . toFlushAndYield ( [ 'B' ] ) ;
396
+ expect ( root ) . toMatchRenderedOutput ( 'AB' ) ;
397
+ } ) ;
398
+
288
399
// @gate !enableSyncDefaultUpdates
289
400
it (
290
401
'interrupts current render when something suspends with a ' +
0 commit comments