@@ -3409,4 +3409,108 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3409
3409
) ;
3410
3410
} ,
3411
3411
) ;
3412
+
3413
+ it ( 'a high pri update can unhide a boundary that suspended at a different level' , async ( ) => {
3414
+ const { useState, useEffect} = React ;
3415
+ const root = ReactNoop . createRoot ( ) ;
3416
+
3417
+ let setOuterText ;
3418
+ function Parent ( { step} ) {
3419
+ const [ text , _setText ] = useState ( 'A' ) ;
3420
+ setOuterText = _setText ;
3421
+ return (
3422
+ < >
3423
+ < Text text = { 'Outer: ' + text + step } />
3424
+ < Suspense fallback = { < Text text = "Loading..." /> } >
3425
+ < Child step = { step } outerText = { text } />
3426
+ </ Suspense >
3427
+ </ >
3428
+ ) ;
3429
+ }
3430
+
3431
+ let setInnerText ;
3432
+ function Child ( { step, outerText} ) {
3433
+ const [ text , _setText ] = useState ( 'A' ) ;
3434
+ setInnerText = _setText ;
3435
+
3436
+ // This will log if the component commits in an inconsistent state
3437
+ useEffect ( ( ) => {
3438
+ if ( text === outerText ) {
3439
+ Scheduler . unstable_yieldValue ( 'Commit Child' ) ;
3440
+ } else {
3441
+ Scheduler . unstable_yieldValue (
3442
+ 'FIXME: Texts are inconsistent (tearing)' ,
3443
+ ) ;
3444
+ }
3445
+ } , [ text , outerText ] ) ;
3446
+
3447
+ return (
3448
+ < >
3449
+ < AsyncText text = { 'Inner: ' + text + step } />
3450
+ </ >
3451
+ ) ;
3452
+ }
3453
+
3454
+ // These always update simultaneously. They must be consistent.
3455
+ function setText ( text ) {
3456
+ setOuterText ( text ) ;
3457
+ setInnerText ( text ) ;
3458
+ }
3459
+
3460
+ // Mount an initial tree. Resolve A so that it doesn't suspend.
3461
+ await resolveText ( 'Inner: A0' ) ;
3462
+ await ReactNoop . act ( async ( ) => {
3463
+ root . render ( < Parent step = { 0 } /> ) ;
3464
+ } ) ;
3465
+ expect ( Scheduler ) . toHaveYielded ( [ 'Outer: A0' , 'Inner: A0' , 'Commit Child' ] ) ;
3466
+ expect ( root ) . toMatchRenderedOutput (
3467
+ < >
3468
+ < span prop = "Outer: A0" />
3469
+ < span prop = "Inner: A0" />
3470
+ </ > ,
3471
+ ) ;
3472
+
3473
+ // Update. This causes the inner component to suspend.
3474
+ await ReactNoop . act ( async ( ) => {
3475
+ setText ( 'B' ) ;
3476
+ } ) ;
3477
+ expect ( Scheduler ) . toHaveYielded ( [
3478
+ 'Outer: B0' ,
3479
+ 'Suspend! [Inner: B0]' ,
3480
+ 'Loading...' ,
3481
+ ] ) ;
3482
+ // Commit the placeholder
3483
+ await advanceTimers ( 250 ) ;
3484
+ expect ( root ) . toMatchRenderedOutput (
3485
+ < >
3486
+ < span prop = "Outer: B0" />
3487
+ < span hidden = { true } prop = "Inner: A0" />
3488
+ < span prop = "Loading..." />
3489
+ </ > ,
3490
+ ) ;
3491
+
3492
+ // Schedule a high pri update on the parent. This will unblock the content.
3493
+ await resolveText ( 'Inner: B1' ) ;
3494
+ await ReactNoop . act ( async ( ) => {
3495
+ ReactNoop . discreteUpdates ( ( ) => {
3496
+ root . render ( < Parent step = { 1 } /> ) ;
3497
+ } ) ;
3498
+ } ) ;
3499
+
3500
+ expect ( Scheduler ) . toHaveYielded ( [
3501
+ // First the outer part of the tree updates, at high pri.
3502
+ 'Outer: B1' ,
3503
+ 'Loading...' ,
3504
+
3505
+ // Then we retry the boundary.
3506
+ 'Inner: B1' ,
3507
+ 'Commit Child' ,
3508
+ ] ) ;
3509
+ expect ( root ) . toMatchRenderedOutput (
3510
+ < >
3511
+ < span prop = "Outer: B1" />
3512
+ < span prop = "Inner: B1" />
3513
+ </ > ,
3514
+ ) ;
3515
+ } ) ;
3412
3516
} ) ;
0 commit comments