@@ -839,8 +839,14 @@ describe('ReactIncremental', () => {
839
839
it ( 'can call sCU while resuming a partly mounted component' , ( ) => {
840
840
var ops = [ ] ;
841
841
842
+ var instances = new Set ( ) ;
843
+
842
844
class Bar extends React . Component {
843
845
state = { y : 'A' } ;
846
+ constructor ( ) {
847
+ super ( ) ;
848
+ instances . add ( this ) ;
849
+ }
844
850
shouldComponentUpdate ( newProps , newState ) {
845
851
return this . props . x !== newProps . x ||
846
852
this . state . y !== newState . y ;
@@ -855,20 +861,29 @@ describe('ReactIncremental', () => {
855
861
ops . push ( 'Foo' ) ;
856
862
return [
857
863
< Bar key = "a" x = "A" /> ,
858
- < Bar key = "b" x = "B" /> ,
864
+ < Bar key = "b" x = { props . step === 0 ? 'B' : 'B2' } /> ,
859
865
< Bar key = "c" x = "C" /> ,
866
+ < Bar key = "d" x = "D" /> ,
860
867
] ;
861
868
}
862
869
863
- ReactNoop . render ( < Foo /> ) ;
864
- ReactNoop . flushDeferredPri ( 30 ) ;
865
- expect ( ops ) . toEqual ( [ 'Foo' , 'Bar:A' , 'Bar:B' ] ) ;
870
+ ReactNoop . render ( < Foo step = { 0 } /> ) ;
871
+ ReactNoop . flushDeferredPri ( 40 ) ;
872
+ expect ( ops ) . toEqual ( [ 'Foo' , 'Bar:A' , 'Bar:B' , 'Bar:C' ] ) ;
873
+
874
+ expect ( instances . size ) . toBe ( 3 ) ;
866
875
867
876
ops = [ ] ;
868
877
869
- ReactNoop . render ( < Foo /> ) ;
870
- ReactNoop . flushDeferredPri ( 40 ) ;
871
- expect ( ops ) . toEqual ( [ 'Foo' , 'Bar:B' , 'Bar:C' ] ) ;
878
+ ReactNoop . render ( < Foo step = { 1 } /> ) ;
879
+ ReactNoop . flushDeferredPri ( 50 ) ;
880
+ // A completed and was reused. B completed but couldn't be reused because
881
+ // props differences. C didn't complete and therefore couldn't be reused.
882
+ // D never even started so it needed a new instance.
883
+ expect ( ops ) . toEqual ( [ 'Foo' , 'Bar:B2' , 'Bar:C' , 'Bar:D' ] ) ;
884
+
885
+ // We expect each rerender to correspond to a new instance.
886
+ expect ( instances . size ) . toBe ( 6 ) ;
872
887
} ) ;
873
888
874
889
it ( 'gets new props when setting state on a partly updated component' , ( ) => {
@@ -927,4 +942,296 @@ describe('ReactIncremental', () => {
927
942
expect ( ops ) . toEqual ( [ 'Bar:A-1' , 'Baz' , 'Baz' ] ) ;
928
943
} ) ;
929
944
945
+ it ( 'calls componentWillMount twice if the initial render is aborted' , ( ) => {
946
+ var ops = [ ] ;
947
+
948
+ class LifeCycle extends React . Component {
949
+ state = { x : this . props . x } ;
950
+ componentWillMount ( ) {
951
+ ops . push ( 'componentWillMount:' + this . state . x + '-' + this . props . x ) ;
952
+ }
953
+ componentDidMount ( ) {
954
+ ops . push ( 'componentDidMount:' + this . state . x + '-' + this . props . x ) ;
955
+ }
956
+ render ( ) {
957
+ return < span /> ;
958
+ }
959
+ }
960
+
961
+ function Trail ( ) {
962
+ ops . push ( 'Trail' ) ;
963
+ }
964
+
965
+ function App ( props ) {
966
+ ops . push ( 'App' ) ;
967
+ return (
968
+ < div >
969
+ < LifeCycle x = { props . x } />
970
+ < Trail />
971
+ </ div >
972
+ ) ;
973
+ }
974
+
975
+ ReactNoop . render ( < App x = { 0 } /> ) ;
976
+ ReactNoop . flushDeferredPri ( 30 ) ;
977
+
978
+ expect ( ops ) . toEqual ( [
979
+ 'App' ,
980
+ 'componentWillMount:0-0' ,
981
+ ] ) ;
982
+
983
+ ops = [ ] ;
984
+
985
+ ReactNoop . render ( < App x = { 1 } /> ) ;
986
+ ReactNoop . flush ( ) ;
987
+
988
+ expect ( ops ) . toEqual ( [
989
+ 'App' ,
990
+ 'componentWillMount:1-1' ,
991
+ 'Trail' ,
992
+ 'componentDidMount:1-1' ,
993
+ ] ) ;
994
+ } ) ;
995
+
996
+ it ( 'calls componentWill* twice if an update render is aborted' , ( ) => {
997
+ var ops = [ ] ;
998
+
999
+ class LifeCycle extends React . Component {
1000
+ componentWillMount ( ) {
1001
+ ops . push ( 'componentWillMount:' + this . props . x ) ;
1002
+ }
1003
+ componentDidMount ( ) {
1004
+ ops . push ( 'componentDidMount:' + this . props . x ) ;
1005
+ }
1006
+ componentWillReceiveProps ( nextProps ) {
1007
+ ops . push ( 'componentWillReceiveProps:' + this . props . x + '-' + nextProps . x ) ;
1008
+ }
1009
+ shouldComponentUpdate ( nextProps ) {
1010
+ ops . push ( 'shouldComponentUpdate:' + this . props . x + '-' + nextProps . x ) ;
1011
+ return true ;
1012
+ }
1013
+ componentWillUpdate ( nextProps ) {
1014
+ ops . push ( 'componentWillUpdate:' + this . props . x + '-' + nextProps . x ) ;
1015
+ }
1016
+ componentDidUpdate ( prevProps ) {
1017
+ ops . push ( 'componentDidUpdate:' + this . props . x + '-' + prevProps . x ) ;
1018
+ }
1019
+ render ( ) {
1020
+ ops . push ( 'render:' + this . props . x ) ;
1021
+ return < span /> ;
1022
+ }
1023
+ }
1024
+
1025
+ function Sibling ( ) {
1026
+ // The sibling is used to confirm that we've completed the first child,
1027
+ // but not yet flushed.
1028
+ ops . push ( 'Sibling' ) ;
1029
+ return < span /> ;
1030
+ }
1031
+
1032
+ function App ( props ) {
1033
+ ops . push ( 'App' ) ;
1034
+
1035
+ return [
1036
+ < LifeCycle key = "a" x = { props . x } /> ,
1037
+ < Sibling key = "b" /> ,
1038
+ ] ;
1039
+ }
1040
+
1041
+ ReactNoop . render ( < App x = { 0 } /> ) ;
1042
+ ReactNoop . flush ( ) ;
1043
+
1044
+ expect ( ops ) . toEqual ( [
1045
+ 'App' ,
1046
+ 'componentWillMount:0' ,
1047
+ 'render:0' ,
1048
+ 'Sibling' ,
1049
+ 'componentDidMount:0' ,
1050
+ ] ) ;
1051
+
1052
+ ops = [ ] ;
1053
+
1054
+ ReactNoop . render ( < App x = { 1 } /> ) ;
1055
+ ReactNoop . flushDeferredPri ( 30 ) ;
1056
+
1057
+ expect ( ops ) . toEqual ( [
1058
+ 'App' ,
1059
+ 'componentWillReceiveProps:0-1' ,
1060
+ 'shouldComponentUpdate:0-1' ,
1061
+ 'componentWillUpdate:0-1' ,
1062
+ 'render:1' ,
1063
+ 'Sibling' ,
1064
+ // no componentDidUpdate
1065
+ ] ) ;
1066
+
1067
+ ops = [ ] ;
1068
+
1069
+ ReactNoop . render ( < App x = { 2 } /> ) ;
1070
+ ReactNoop . flush ( ) ;
1071
+
1072
+ expect ( ops ) . toEqual ( [
1073
+ 'App' ,
1074
+ 'componentWillReceiveProps:1-2' ,
1075
+ 'shouldComponentUpdate:1-2' ,
1076
+ 'componentWillUpdate:1-2' ,
1077
+ 'render:2' ,
1078
+ 'Sibling' ,
1079
+ // When componentDidUpdate finally gets called, it covers both updates.
1080
+ 'componentDidUpdate:2-0' ,
1081
+ ] ) ;
1082
+ } ) ;
1083
+
1084
+ it ( 'does not call componentWillReceiveProps for state-only updates' , ( ) => {
1085
+ var ops = [ ] ;
1086
+
1087
+ var instances = [ ] ;
1088
+
1089
+ class LifeCycle extends React . Component {
1090
+ state = { x : 0 } ;
1091
+ tick ( ) {
1092
+ this . setState ( {
1093
+ x : this . state . x + 1 ,
1094
+ } ) ;
1095
+ }
1096
+ componentWillMount ( ) {
1097
+ instances . push ( this ) ;
1098
+ ops . push ( 'componentWillMount:' + this . state . x ) ;
1099
+ }
1100
+ componentDidMount ( ) {
1101
+ ops . push ( 'componentDidMount:' + this . state . x ) ;
1102
+ }
1103
+ componentWillReceiveProps ( nextProps ) {
1104
+ ops . push ( 'componentWillReceiveProps' ) ;
1105
+ }
1106
+ shouldComponentUpdate ( nextProps , nextState ) {
1107
+ ops . push ( 'shouldComponentUpdate:' + this . state . x + '-' + nextState . x ) ;
1108
+ return true ;
1109
+ }
1110
+ componentWillUpdate ( nextProps , nextState ) {
1111
+ ops . push ( 'componentWillUpdate:' + this . state . x + '-' + nextState . x ) ;
1112
+ }
1113
+ componentDidUpdate ( prevProps , prevState ) {
1114
+ ops . push ( 'componentDidUpdate:' + this . state . x + '-' + prevState . x ) ;
1115
+ }
1116
+ render ( ) {
1117
+ ops . push ( 'render:' + this . state . x ) ;
1118
+ return < span /> ;
1119
+ }
1120
+ }
1121
+
1122
+ // This wrap is a bit contrived because we can't pause a completed root and
1123
+ // there is currently an issue where a component can't reuse its render
1124
+ // output unless it fully completed.
1125
+ class Wrap extends React . Component {
1126
+ state = { y : 0 } ;
1127
+ componentWillMount ( ) {
1128
+ instances . push ( this ) ;
1129
+ }
1130
+ tick ( ) {
1131
+ this . setState ( {
1132
+ y : this . state . y + 1 ,
1133
+ } ) ;
1134
+ }
1135
+ render ( ) {
1136
+ ops . push ( 'Wrap' ) ;
1137
+ return < LifeCycle y = { this . state . y } /> ;
1138
+ }
1139
+ }
1140
+
1141
+ function Sibling ( ) {
1142
+ // The sibling is used to confirm that we've completed the first child,
1143
+ // but not yet flushed.
1144
+ ops . push ( 'Sibling' ) ;
1145
+ return < span /> ;
1146
+ }
1147
+
1148
+ function App ( props ) {
1149
+ ops . push ( 'App' ) ;
1150
+ return [
1151
+ < Wrap key = "a" /> ,
1152
+ < Sibling key = "b" /> ,
1153
+ ] ;
1154
+ }
1155
+
1156
+ ReactNoop . render ( < App y = { 0 } /> ) ;
1157
+ ReactNoop . flush ( ) ;
1158
+
1159
+ expect ( ops ) . toEqual ( [
1160
+ 'App' ,
1161
+ 'Wrap' ,
1162
+ 'componentWillMount:0' ,
1163
+ 'render:0' ,
1164
+ 'Sibling' ,
1165
+ 'componentDidMount:0' ,
1166
+ ] ) ;
1167
+
1168
+ ops = [ ] ;
1169
+
1170
+ // LifeCycle
1171
+ instances [ 1 ] . tick ( ) ;
1172
+
1173
+ ReactNoop . flushDeferredPri ( 25 ) ;
1174
+
1175
+ expect ( ops ) . toEqual ( [
1176
+ // no componentWillReceiveProps
1177
+ 'shouldComponentUpdate:0-1' ,
1178
+ 'componentWillUpdate:0-1' ,
1179
+ 'render:1' ,
1180
+ // no componentDidUpdate
1181
+ ] ) ;
1182
+
1183
+ ops = [ ] ;
1184
+
1185
+ // LifeCycle
1186
+ instances [ 1 ] . tick ( ) ;
1187
+
1188
+ ReactNoop . flush ( ) ;
1189
+
1190
+ expect ( ops ) . toEqual ( [
1191
+ // no componentWillReceiveProps
1192
+ 'shouldComponentUpdate:1-2' ,
1193
+ 'componentWillUpdate:1-2' ,
1194
+ 'render:2' ,
1195
+ // When componentDidUpdate finally gets called, it covers both updates.
1196
+ 'componentDidUpdate:2-0' ,
1197
+ ] ) ;
1198
+
1199
+ ops = [ ] ;
1200
+
1201
+ // Next we will update props of LifeCycle by updating its parent.
1202
+
1203
+ instances [ 0 ] . tick ( ) ;
1204
+
1205
+ ReactNoop . flushDeferredPri ( 30 ) ;
1206
+
1207
+ expect ( ops ) . toEqual ( [
1208
+ 'Wrap' ,
1209
+ 'componentWillReceiveProps' ,
1210
+ 'shouldComponentUpdate:2-2' ,
1211
+ 'componentWillUpdate:2-2' ,
1212
+ 'render:2' ,
1213
+ // no componentDidUpdate
1214
+ ] ) ;
1215
+
1216
+ ops = [ ] ;
1217
+
1218
+ // Next we will update LifeCycle directly but not with new props.
1219
+ instances [ 1 ] . tick ( ) ;
1220
+
1221
+ ReactNoop . flush ( ) ;
1222
+
1223
+ expect ( ops ) . toEqual ( [
1224
+ // This should not trigger another componentWillReceiveProps because
1225
+ // we never got new props.
1226
+ 'shouldComponentUpdate:2-3' ,
1227
+ 'componentWillUpdate:2-3' ,
1228
+ 'render:3' ,
1229
+ 'componentDidUpdate:3-2' ,
1230
+ ] ) ;
1231
+
1232
+ // TODO: Test that we get the expected values for the same scenario with
1233
+ // incomplete parents.
1234
+
1235
+ } ) ;
1236
+
930
1237
} ) ;
0 commit comments