@@ -839,8 +839,14 @@ describe('ReactIncremental', () => {
839839 it ( 'can call sCU while resuming a partly mounted component' , ( ) => {
840840 var ops = [ ] ;
841841
842+ var instances = new Set ( ) ;
843+
842844 class Bar extends React . Component {
843845 state = { y : 'A' } ;
846+ constructor ( ) {
847+ super ( ) ;
848+ instances . add ( this ) ;
849+ }
844850 shouldComponentUpdate ( newProps , newState ) {
845851 return this . props . x !== newProps . x ||
846852 this . state . y !== newState . y ;
@@ -855,20 +861,29 @@ describe('ReactIncremental', () => {
855861 ops . push ( 'Foo' ) ;
856862 return [
857863 < Bar key = "a" x = "A" /> ,
858- < Bar key = "b" x = "B" /> ,
864+ < Bar key = "b" x = { props . step === 0 ? 'B' : 'B2' } /> ,
859865 < Bar key = "c" x = "C" /> ,
866+ < Bar key = "d" x = "D" /> ,
860867 ] ;
861868 }
862869
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 ) ;
866875
867876 ops = [ ] ;
868877
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 ) ;
872887 } ) ;
873888
874889 it ( 'gets new props when setting state on a partly updated component' , ( ) => {
@@ -927,4 +942,296 @@ describe('ReactIncremental', () => {
927942 expect ( ops ) . toEqual ( [ 'Bar:A-1' , 'Baz' , 'Baz' ] ) ;
928943 } ) ;
929944
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+
9301237} ) ;
0 commit comments