@@ -934,6 +934,209 @@ describe('NgModelController', function() {
934934 } ) ;
935935} ) ;
936936
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+
9371140describe ( 'ngModel' , function ( ) {
9381141 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;
9391142
@@ -1348,6 +1551,16 @@ describe('input', function() {
13481551 expect ( scope . form . $$renameControl ) . not . toHaveBeenCalled ( ) ;
13491552 } ) ;
13501553
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+ } ) ;
13511564
13521565 describe ( 'compositionevents' , function ( ) {
13531566 it ( 'should not update the model between "compositionstart" and "compositionend" on non android' , inject ( function ( $sniffer ) {
@@ -2264,6 +2477,14 @@ describe('input', function() {
22642477 expect ( inputElm ) . toBeValid ( ) ;
22652478 expect ( scope . form . input . $error . minlength ) . not . toBe ( true ) ;
22662479 } ) ;
2480+
2481+ it ( 'should validate when the model is initalized as a number' , function ( ) {
2482+ scope . value = 12345 ;
2483+ compileInput ( '<input type="text" name="input" ng-model="value" minlength="3" />' ) ;
2484+ expect ( scope . value ) . toBe ( 12345 ) ;
2485+ expect ( scope . form . input . $error . minlength ) . toBeUndefined ( ) ;
2486+ } ) ;
2487+
22672488 } ) ;
22682489
22692490
@@ -2362,6 +2583,14 @@ describe('input', function() {
23622583 expect ( scope . value ) . toBe ( '12345' ) ;
23632584 } ) ;
23642585
2586+ // This works both for string formatter and toString() in validator
2587+ it ( 'should validate when the model is initalized as a number' , function ( ) {
2588+ scope . value = 12345 ;
2589+ compileInput ( '<input type="text" name="input" ng-model="value" maxlength="10" />' ) ;
2590+ expect ( scope . value ) . toBe ( 12345 ) ;
2591+ expect ( scope . form . input . $error . maxlength ) . toBeUndefined ( ) ;
2592+ } ) ;
2593+
23652594 } ) ;
23662595
23672596
0 commit comments