@@ -5,6 +5,7 @@ let ReactNoop;
55let  Scheduler ; 
66let  act ; 
77let  use ; 
8+ let  useState ; 
89let  Suspense ; 
910let  startTransition ; 
1011let  pendingTextRequests ; 
@@ -18,6 +19,7 @@ describe('ReactThenable', () => {
1819    Scheduler  =  require ( 'scheduler' ) ; 
1920    act  =  require ( 'jest-react' ) . act ; 
2021    use  =  React . use ; 
22+     useState  =  React . useState ; 
2123    Suspense  =  React . Suspense ; 
2224    startTransition  =  React . startTransition ; 
2325
@@ -668,4 +670,92 @@ describe('ReactThenable', () => {
668670    expect ( Scheduler ) . toHaveYielded ( [ 'Hi' ] ) ; 
669671    expect ( root ) . toMatchRenderedOutput ( 'Hi' ) ; 
670672  } ) ; 
673+ 
674+   // @gate  enableUseHook 
675+   test ( 'does not suspend indefinitely if an interleaved update was skipped' ,  async  ( )  =>  { 
676+     function  Child ( { childShouldSuspend} )  { 
677+       return  ( 
678+         < Text 
679+           text = { 
680+             childShouldSuspend 
681+               ? use ( getAsyncText ( 'Will never resolve' ) ) 
682+               : 'Child' 
683+           } 
684+         /> 
685+       ) ; 
686+     } 
687+ 
688+     let  setChildShouldSuspend ; 
689+     let  setShowChild ; 
690+     function  Parent ( )  { 
691+       const  [ showChild ,  _setShowChild ]  =  useState ( true ) ; 
692+       setShowChild  =  _setShowChild ; 
693+ 
694+       const  [ childShouldSuspend ,  _setChildShouldSuspend ]  =  useState ( false ) ; 
695+       setChildShouldSuspend  =  _setChildShouldSuspend ; 
696+ 
697+       Scheduler . unstable_yieldValue ( 
698+         `childShouldSuspend: ${ childShouldSuspend } ${ showChild }  , 
699+       ) ; 
700+       return  showChild  ? ( 
701+         < Child  childShouldSuspend = { childShouldSuspend }  /> 
702+       )  : ( 
703+         < Text  text = "(empty)"  /> 
704+       ) ; 
705+     } 
706+ 
707+     const  root  =  ReactNoop . createRoot ( ) ; 
708+     await  act ( ( )  =>  { 
709+       root . render ( < Parent  /> ) ; 
710+     } ) ; 
711+     expect ( Scheduler ) . toHaveYielded ( [ 
712+       'childShouldSuspend: false, showChild: true' , 
713+       'Child' , 
714+     ] ) ; 
715+     expect ( root ) . toMatchRenderedOutput ( 'Child' ) ; 
716+ 
717+     await  act ( ( )  =>  { 
718+       // Perform an update that causes the app to suspend 
719+       startTransition ( ( )  =>  { 
720+         setChildShouldSuspend ( true ) ; 
721+       } ) ; 
722+       expect ( Scheduler ) . toFlushAndYieldThrough ( [ 
723+         'childShouldSuspend: true, showChild: true' , 
724+       ] ) ; 
725+       // While the update is in progress, schedule another update. 
726+       startTransition ( ( )  =>  { 
727+         setShowChild ( false ) ; 
728+       } ) ; 
729+     } ) ; 
730+     expect ( Scheduler ) . toHaveYielded ( [ 
731+       // Because the interleaved update is not higher priority than what we were 
732+       // already working on, it won't interrupt. The first update will continue, 
733+       // and will suspend. 
734+       'Async text requested [Will never resolve]' , 
735+ 
736+       // Instead of waiting for the promise to resolve, React notices there's 
737+       // another pending update that it hasn't tried yet. It will switch to 
738+       // rendering that instead. 
739+       // 
740+       // This time, the update hides the component that previous was suspending, 
741+       // so it finishes successfully. 
742+       'childShouldSuspend: false, showChild: false' , 
743+       '(empty)' , 
744+ 
745+       // Finally, React attempts to render the first update again. It also 
746+       // finishes successfully, because it was rebased on top of the update that 
747+       // hid the suspended component. 
748+       // NOTE: These this render happened to not be entangled with the previous 
749+       // one. If they had been, this update would have been included in the 
750+       // previous render, and there wouldn't be an extra one here. This could 
751+       // change if we change our entanglement heurstics. Semantically, it 
752+       // shouldn't matter, though in general we try to work on transitions in 
753+       // parallel whenever possible. So even though in this particular case, the 
754+       // extra render is unnecessary, it's a nice property that it wasn't 
755+       // entangled with the other transition. 
756+       'childShouldSuspend: true, showChild: false' , 
757+       '(empty)' , 
758+     ] ) ; 
759+     expect ( root ) . toMatchRenderedOutput ( '(empty)' ) ; 
760+   } ) ; 
671761} ) ; 
0 commit comments