@@ -653,4 +653,128 @@ describe('ReactIncrementalUpdates', () => {
653653 expect ( Scheduler ) . toFlushAndYield ( [ 'Commit: goodbye' ] ) ;
654654 } ) ;
655655 } ) ;
656+
657+ it ( 'when rebasing, does not exclude updates that were already committed, regardless of priority' , async ( ) => {
658+ const { useState, useLayoutEffect} = React ;
659+
660+ let pushToLog ;
661+ function App ( ) {
662+ const [ log , setLog ] = useState ( '' ) ;
663+ pushToLog = msg => {
664+ setLog ( prevLog => prevLog + msg ) ;
665+ } ;
666+
667+ useLayoutEffect (
668+ ( ) => {
669+ Scheduler . unstable_yieldValue ( 'Committed: ' + log ) ;
670+ if ( log === 'B' ) {
671+ // Right after B commits, schedule additional updates.
672+ Scheduler . unstable_runWithPriority (
673+ Scheduler . unstable_UserBlockingPriority ,
674+ ( ) => {
675+ pushToLog ( 'C' ) ;
676+ } ,
677+ ) ;
678+ setLog ( prevLog => prevLog + 'D' ) ;
679+ }
680+ } ,
681+ [ log ] ,
682+ ) ;
683+
684+ return log ;
685+ }
686+
687+ const root = ReactNoop . createRoot ( ) ;
688+ await ReactNoop . act ( async ( ) => {
689+ root . render ( < App /> ) ;
690+ } ) ;
691+ expect ( Scheduler ) . toHaveYielded ( [ 'Committed: ' ] ) ;
692+ expect ( root ) . toMatchRenderedOutput ( '' ) ;
693+
694+ await ReactNoop . act ( async ( ) => {
695+ pushToLog ( 'A' ) ;
696+ Scheduler . unstable_runWithPriority (
697+ Scheduler . unstable_UserBlockingPriority ,
698+ ( ) => {
699+ pushToLog ( 'B' ) ;
700+ } ,
701+ ) ;
702+ } ) ;
703+ expect ( Scheduler ) . toHaveYielded ( [
704+ // A and B are pending. B is higher priority, so we'll render that first.
705+ 'Committed: B' ,
706+ // Because A comes first in the queue, we're now in rebase mode. B must
707+ // be rebased on top of A. Also, in a layout effect, we received two new
708+ // updates: C and D. C is user-blocking and D is synchronous.
709+ //
710+ // First render the synchronous update. What we're testing here is that
711+ // B *is not dropped* even though it has lower than sync priority. That's
712+ // because we already committed it. However, this render should not
713+ // include C, because that update wasn't already committed.
714+ 'Committed: BD' ,
715+ 'Committed: BCD' ,
716+ 'Committed: ABCD' ,
717+ ] ) ;
718+ expect ( root ) . toMatchRenderedOutput ( 'ABCD' ) ;
719+ } ) ;
720+
721+ it ( 'when rebasing, does not exclude updates that were already committed, regardless of priority (classes)' , async ( ) => {
722+ let pushToLog ;
723+ class App extends React . Component {
724+ state = { log : '' } ;
725+ pushToLog = msg => {
726+ this . setState ( prevState => ( { log : prevState . log + msg } ) ) ;
727+ } ;
728+ componentDidUpdate ( ) {
729+ Scheduler . unstable_yieldValue ( 'Committed: ' + this . state . log ) ;
730+ if ( this . state . log === 'B' ) {
731+ // Right after B commits, schedule additional updates.
732+ Scheduler . unstable_runWithPriority (
733+ Scheduler . unstable_UserBlockingPriority ,
734+ ( ) => {
735+ this . pushToLog ( 'C' ) ;
736+ } ,
737+ ) ;
738+ this . pushToLog ( 'D' ) ;
739+ }
740+ }
741+ render ( ) {
742+ pushToLog = this . pushToLog ;
743+ return this . state . log ;
744+ }
745+ }
746+
747+ const root = ReactNoop . createRoot ( ) ;
748+ await ReactNoop . act ( async ( ) => {
749+ root . render ( < App /> ) ;
750+ } ) ;
751+ expect ( Scheduler ) . toHaveYielded ( [ ] ) ;
752+ expect ( root ) . toMatchRenderedOutput ( '' ) ;
753+
754+ await ReactNoop . act ( async ( ) => {
755+ pushToLog ( 'A' ) ;
756+ Scheduler . unstable_runWithPriority (
757+ Scheduler . unstable_UserBlockingPriority ,
758+ ( ) => {
759+ pushToLog ( 'B' ) ;
760+ } ,
761+ ) ;
762+ } ) ;
763+ expect ( Scheduler ) . toHaveYielded ( [
764+ // A and B are pending. B is higher priority, so we'll render that first.
765+ 'Committed: B' ,
766+ // Because A comes first in the queue, we're now in rebase mode. B must
767+ // be rebased on top of A. Also, in a layout effect, we received two new
768+ // updates: C and D. C is user-blocking and D is synchronous.
769+ //
770+ // First render the synchronous update. What we're testing here is that
771+ // B *is not dropped* even though it has lower than sync priority. That's
772+ // because we already committed it. However, this render should not
773+ // include C, because that update wasn't already committed.
774+ 'Committed: BD' ,
775+ 'Committed: BCD' ,
776+ 'Committed: ABCD' ,
777+ ] ) ;
778+ expect ( root ) . toMatchRenderedOutput ( 'ABCD' ) ;
779+ } ) ;
656780} ) ;
0 commit comments