@@ -934,6 +934,209 @@ describe('NgModelController', function() {
934
934
} ) ;
935
935
} ) ;
936
936
937
+ describe ( 'initialization' , function ( ) {
938
+ var formElm , inputElm , ctrl , scope , $compile , $sniffer , $compileProvider , changeInputValueTo ;
939
+
940
+ function compileInput ( inputHtml ) {
941
+ inputElm = jqLite ( inputHtml ) ;
942
+ formElm = jqLite ( '<form name="form"></form>' ) ;
943
+ formElm . append ( inputElm ) ;
944
+ $compile ( formElm ) ( scope ) ;
945
+ ctrl = inputElm . controller ( 'ngModel' ) ;
946
+ scope . $digest ( ) ;
947
+ }
948
+
949
+ function addValidator ( validity , shouldObserve ) {
950
+ if ( ! isDefined ( shouldObserve ) ) {
951
+ shouldObserve = true ;
952
+ }
953
+
954
+ $compileProvider . directive ( 'obs' , function ( ) {
955
+ return {
956
+ require : 'ngModel' ,
957
+ link : function ( scope , element , attrs , ngModelCtrl ) {
958
+
959
+ ngModelCtrl . $validators . obs = isFunction ( validity ) ? validity : function ( value ) {
960
+ return validity ;
961
+ } ;
962
+
963
+ if ( shouldObserve ) {
964
+ attrs . $observe ( 'obs' , function ( ) {
965
+ ngModelCtrl . $validate ( ) ;
966
+ } ) ;
967
+ }
968
+ }
969
+ } ;
970
+
971
+ } ) ;
972
+ }
973
+
974
+ function addFormatter ( formatFunction ) {
975
+ $compileProvider . directive ( 'format' , function ( ) {
976
+ return {
977
+ require : 'ngModel' ,
978
+ link : function ( scope , element , attrs , ctrl ) {
979
+
980
+ ctrl . $formatters . push ( formatFunction ) ;
981
+ }
982
+ } ;
983
+
984
+ } ) ;
985
+ }
986
+
987
+ function addParser ( parseFunction ) {
988
+ $compileProvider . directive ( 'parse' , function ( ) {
989
+ return {
990
+ require : 'ngModel' ,
991
+ link : function ( scope , element , attrs , ctrl ) {
992
+
993
+ ctrl . $parsers . push ( parseFunction ) ;
994
+ }
995
+ } ;
996
+
997
+ } ) ;
998
+ }
999
+
1000
+ beforeEach ( module ( function ( _$compileProvider_ ) {
1001
+ $compileProvider = _$compileProvider_ ;
1002
+ } ) ) ;
1003
+
1004
+ beforeEach ( inject ( function ( _$compile_ , _$rootScope_ , _$sniffer_ ) {
1005
+ $compile = _$compile_ ;
1006
+ $sniffer = _$sniffer_ ;
1007
+ scope = _$rootScope_ ;
1008
+
1009
+ changeInputValueTo = function ( value ) {
1010
+ inputElm . val ( value ) ;
1011
+ browserTrigger ( inputElm , $sniffer . hasEvent ( 'input' ) ? 'input' : 'change' ) ;
1012
+ } ;
1013
+ } ) ) ;
1014
+
1015
+ afterEach ( function ( ) {
1016
+ dealoc ( formElm ) ;
1017
+ } ) ;
1018
+
1019
+ // https://github.com/angular/angular.js/issues/9959
1020
+ it ( 'should not change model of type number to string with validator using observer' , function ( ) {
1021
+ addValidator ( true ) ;
1022
+ scope . value = 12345 ;
1023
+ scope . attr = 'mock' ;
1024
+ scope . ngChangeSpy = jasmine . createSpy ( ) ;
1025
+
1026
+ compileInput ( '<input type="text" name="input" ng-model="value"' +
1027
+ 'ng-change="ngChangeSpy()" obs="{{attr}}" />' ) ;
1028
+
1029
+ expect ( scope . value ) . toBe ( 12345 ) ;
1030
+ expect ( scope . ngChangeSpy ) . not . toHaveBeenCalled ( ) ;
1031
+ } ) ;
1032
+
1033
+ //https://github.com/angular/angular.js/issues/9063
1034
+ it ( 'should not set a null model that is invalid to undefined' , function ( ) {
1035
+ addValidator ( false ) ;
1036
+ scope . value = null ;
1037
+ scope . required = true ;
1038
+ compileInput ( '<input type="text" name="textInput" ng-model="value"' +
1039
+ 'ng-required="required" obs="{{attr}}" />' ) ;
1040
+
1041
+ expect ( inputElm ) . toBeInvalid ( ) ;
1042
+ expect ( scope . value ) . toBe ( null ) ;
1043
+ expect ( scope . form . textInput . $error . obs ) . toBeTruthy ( ) ;
1044
+ } ) ;
1045
+
1046
+ //https://github.com/angular/angular.js/issues/9996
1047
+ it ( 'should not change an undefined model that uses ng-required and formatters and parsers' , function ( ) {
1048
+ addParser ( function ( viewValue ) {
1049
+ return null ;
1050
+ } ) ;
1051
+ addFormatter ( function ( modelValue ) {
1052
+ return '' ;
1053
+ } ) ;
1054
+
1055
+ scope . ngChangeSpy = jasmine . createSpy ( ) ;
1056
+ compileInput ( '<input type="text" parse format name="textInput" ng-model="value"' +
1057
+ 'ng-required="undefinedProp" ng-change="ngChangeSpy()" />' ) ;
1058
+
1059
+ expect ( inputElm ) . toBeValid ( ) ;
1060
+ expect ( scope . value ) . toBeUndefined ( ) ;
1061
+ expect ( scope . ngChangeSpy ) . not . toHaveBeenCalled ( ) ;
1062
+ } ) ;
1063
+
1064
+ // https://github.com/angular/angular.js/issues/10025
1065
+ it ( 'should not change a model that uses custom $formatters and $parsers' , function ( ) {
1066
+ addValidator ( true ) ;
1067
+ addFormatter ( function ( modelValue ) {
1068
+ return 'abc' ;
1069
+ } ) ;
1070
+ addParser ( function ( viewValue ) {
1071
+ return 'xyz' ;
1072
+ } ) ;
1073
+ scope . value = 'abc' ;
1074
+ scope . attr = 'mock' ;
1075
+ compileInput ( '<input type="text" parse format name="textInput" ng-model="value"' +
1076
+ 'obs="{{attr}}" />' ) ;
1077
+
1078
+ expect ( inputElm ) . toBeValid ( ) ;
1079
+ expect ( scope . value ) . toBe ( 'abc' ) ;
1080
+ } ) ;
1081
+
1082
+ describe ( '$validate' , function ( ) {
1083
+
1084
+ // Sanity test: since a parse error sets the modelValue to undefined, the
1085
+ // $$rawModelValue will always be undefined, hence $validate does not have
1086
+ // a 'good' value to update
1087
+ it ( 'should not update a model that has a parse error' , function ( ) {
1088
+ scope . value = 'abc' ;
1089
+ addParser ( function ( ) {
1090
+ return undefined ;
1091
+ } ) ;
1092
+
1093
+ addValidator ( true , false ) ;
1094
+
1095
+ compileInput ( '<input type="text" name="textInput" obs parse ng-model="value"/>' ) ;
1096
+ expect ( inputElm ) . toBeValid ( ) ;
1097
+ expect ( scope . value ) . toBe ( 'abc' ) ;
1098
+
1099
+ changeInputValueTo ( 'xyz' ) ;
1100
+ expect ( inputElm ) . toBeInvalid ( ) ;
1101
+ expect ( scope . value ) . toBeUndefined ( ) ;
1102
+ expect ( ctrl . $error . parse ) . toBe ( true ) ;
1103
+
1104
+ ctrl . $validate ( ) ;
1105
+ expect ( inputElm ) . toBeInvalid ( ) ;
1106
+ expect ( scope . value ) . toBeUndefined ( ) ;
1107
+ } ) ;
1108
+
1109
+ it ( 'should restore the last valid modelValue when a validator becomes valid' , function ( ) {
1110
+ scope . value = 'abc' ;
1111
+ scope . count = 0 ;
1112
+
1113
+ addValidator ( function ( ) {
1114
+ scope . count ++ ;
1115
+ dump ( 'count' , scope . count ) ;
1116
+ return scope . count === 1 ? true : scope . count === 2 ? false : true ;
1117
+ } ) ;
1118
+
1119
+ compileInput ( '<input type="text" name="textInput" obs ng-model="value"/>' ) ;
1120
+ expect ( inputElm ) . toBeValid ( ) ;
1121
+ expect ( scope . value ) . toBe ( 'abc' ) ;
1122
+ expect ( ctrl . $viewValue ) . toBe ( 'abc' ) ;
1123
+
1124
+ ctrl . $validate ( ) ;
1125
+ scope . $digest ( ) ;
1126
+ expect ( inputElm ) . toBeInvalid ( ) ;
1127
+ expect ( scope . value ) . toBeUndefined ( ) ;
1128
+ expect ( ctrl . $viewValue ) . toBe ( 'abc' ) ;
1129
+
1130
+ ctrl . $validate ( ) ;
1131
+ scope . $digest ( ) ;
1132
+ expect ( inputElm ) . toBeValid ( ) ;
1133
+ expect ( scope . value ) . toBe ( 'abc' ) ;
1134
+ } ) ;
1135
+
1136
+
1137
+ } ) ;
1138
+ } ) ;
1139
+
937
1140
describe ( 'ngModel' , function ( ) {
938
1141
var EMAIL_REGEXP = / ^ [ a - z 0 - 9 ! # $ % & ' * + \/ = ? ^ _ ` { | } ~ . - ] + @ [ a - z 0 - 9 ] ( [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] ) ? ( \. [ a - z 0 - 9 ] ( [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] ) ? ) * $ / i;
939
1142
@@ -1348,6 +1551,16 @@ describe('input', function() {
1348
1551
expect ( scope . form . $$renameControl ) . not . toHaveBeenCalled ( ) ;
1349
1552
} ) ;
1350
1553
1554
+ it ( 'should not invoke viewChangeListeners before input is touched' , function ( ) {
1555
+ scope . value = 1 ;
1556
+ var change = scope . change = jasmine . createSpy ( 'change' ) ;
1557
+ var element = $compile ( '<div><div ng-repeat="i in [1]">' +
1558
+ '<input type="text" ng-model="value" maxlength="1" ng-change="change()" />' +
1559
+ '</div></div>' ) ( scope ) ;
1560
+ scope . $digest ( ) ;
1561
+ expect ( change ) . not . toHaveBeenCalled ( ) ;
1562
+ dealoc ( element ) ;
1563
+ } ) ;
1351
1564
1352
1565
describe ( 'compositionevents' , function ( ) {
1353
1566
it ( 'should not update the model between "compositionstart" and "compositionend" on non android' , inject ( function ( $sniffer ) {
@@ -2311,6 +2524,14 @@ describe('input', function() {
2311
2524
expect ( inputElm ) . toBeValid ( ) ;
2312
2525
expect ( scope . form . input . $error . minlength ) . not . toBe ( true ) ;
2313
2526
} ) ;
2527
+
2528
+ it ( 'should validate when the model is initalized as a number' , function ( ) {
2529
+ scope . value = 12345 ;
2530
+ compileInput ( '<input type="text" name="input" ng-model="value" minlength="3" />' ) ;
2531
+ expect ( scope . value ) . toBe ( 12345 ) ;
2532
+ expect ( scope . form . input . $error . minlength ) . toBeUndefined ( ) ;
2533
+ } ) ;
2534
+
2314
2535
} ) ;
2315
2536
2316
2537
@@ -2409,6 +2630,14 @@ describe('input', function() {
2409
2630
expect ( scope . value ) . toBe ( '12345' ) ;
2410
2631
} ) ;
2411
2632
2633
+ // This works both for string formatter and toString() in validator
2634
+ it ( 'should validate when the model is initalized as a number' , function ( ) {
2635
+ scope . value = 12345 ;
2636
+ compileInput ( '<input type="text" name="input" ng-model="value" maxlength="10" />' ) ;
2637
+ expect ( scope . value ) . toBe ( 12345 ) ;
2638
+ expect ( scope . form . input . $error . maxlength ) . toBeUndefined ( ) ;
2639
+ } ) ;
2640
+
2412
2641
} ) ;
2413
2642
2414
2643
0 commit comments