@@ -653,4 +653,128 @@ describe('ReactIncrementalUpdates', () => {
653
653
expect ( Scheduler ) . toFlushAndYield ( [ 'Commit: goodbye' ] ) ;
654
654
} ) ;
655
655
} ) ;
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
+ } ) ;
656
780
} ) ;
0 commit comments