@@ -285,6 +285,117 @@ describe('ReactSuspense', () => {
285285 expect ( root ) . toMatchRenderedOutput ( 'AsyncAfter SuspenseSibling' ) ;
286286 } ) ;
287287
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+
288399 // @gate !enableSyncDefaultUpdates
289400 it (
290401 'interrupts current render when something suspends with a ' +
0 commit comments