diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 2f75defe1944..0a9eacd9fd8b 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1497,7 +1497,7 @@ function createDateParser(regexp, mapping) { } function createDateInputType(type, regexp, parseDate, format) { - return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { + return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { badInputChecker(scope, element, attr, ctrl, type); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); @@ -1540,24 +1540,34 @@ function createDateInputType(type, regexp, parseDate, format) { }); if (isDefined(attr.min) || attr.ngMin) { - var minVal; + var minVal = attr.min || $parse(attr.ngMin)(scope); + var parsedMinVal = parseObservedDateValue(minVal); + ctrl.$validators.min = function(value) { - return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; + return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal; }; attr.$observe('min', function(val) { - minVal = parseObservedDateValue(val); - ctrl.$validate(); + if (val !== minVal) { + parsedMinVal = parseObservedDateValue(val); + minVal = val; + ctrl.$validate(); + } }); } if (isDefined(attr.max) || attr.ngMax) { - var maxVal; + var maxVal = attr.max || $parse(attr.ngMax)(scope); + var parsedMaxVal = parseObservedDateValue(maxVal); + ctrl.$validators.max = function(value) { - return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; + return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal; }; attr.$observe('max', function(val) { - maxVal = parseObservedDateValue(val); - ctrl.$validate(); + if (val !== maxVal) { + parsedMaxVal = parseObservedDateValue(val); + maxVal = val; + ctrl.$validate(); + } }); } @@ -1709,50 +1719,68 @@ function isValidForStep(viewValue, stepBase, step) { return (value - stepBase) % step === 0; } -function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { badInputChecker(scope, element, attr, ctrl, 'number'); numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var minVal; - var maxVal; + var parsedMinVal; if (isDefined(attr.min) || attr.ngMin) { + var minVal = attr.min || $parse(attr.ngMin)(scope); + parsedMinVal = parseNumberAttrVal(minVal); + ctrl.$validators.min = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; + return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal; }; attr.$observe('min', function(val) { - minVal = parseNumberAttrVal(val); - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); + if (val !== minVal) { + parsedMinVal = parseNumberAttrVal(val); + minVal = val; + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } }); } if (isDefined(attr.max) || attr.ngMax) { + var maxVal = attr.max || $parse(attr.ngMax)(scope); + var parsedMaxVal = parseNumberAttrVal(maxVal); + ctrl.$validators.max = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; + return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal; }; attr.$observe('max', function(val) { - maxVal = parseNumberAttrVal(val); - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); + if (val !== maxVal) { + parsedMaxVal = parseNumberAttrVal(val); + maxVal = val; + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } }); } if (isDefined(attr.step) || attr.ngStep) { - var stepVal; + var stepVal = attr.step || $parse(attr.ngStep)(scope); + var parsedStepVal = parseNumberAttrVal(stepVal); + ctrl.$validators.step = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || - isValidForStep(viewValue, minVal || 0, stepVal); + return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) || + isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal); }; attr.$observe('step', function(val) { - stepVal = parseNumberAttrVal(val); // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); + if (val !== stepVal) { + parsedStepVal = parseNumberAttrVal(val); + stepVal = val; + ctrl.$validate(); + } + }); + } } @@ -1782,6 +1810,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { originalRender; if (hasMinAttr) { + minVal = parseNumberAttrVal(attr.min); + ctrl.$validators.min = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMinValidator() { return true; } : @@ -1794,6 +1824,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { } if (hasMaxAttr) { + maxVal = parseNumberAttrVal(attr.max); + ctrl.$validators.max = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMaxValidator() { return true; } : @@ -1806,6 +1838,8 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { } if (hasStepAttr) { + stepVal = parseNumberAttrVal(attr.step); + ctrl.$validators.step = supportsRange ? function nativeStepValidator() { // Currently, only FF implements the spec on step change correctly (i.e. adjusting the @@ -1827,7 +1861,13 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { // attribute value when the input is first rendered, so that the browser can adjust the // input value based on the min/max value element.attr(htmlAttrName, attr[htmlAttrName]); - attr.$observe(htmlAttrName, changeFn); + var oldVal = attr[htmlAttrName]; + attr.$observe(htmlAttrName, function wrappedObserver(val) { + if (val !== oldVal) { + oldVal = val; + changeFn(val); + } + }); } function minChange(val) { @@ -1881,11 +1921,11 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { } // Some browsers don't adjust the input value correctly, but set the stepMismatch error - if (supportsRange && ctrl.$viewValue !== element.val()) { - ctrl.$setViewValue(element.val()); - } else { + if (!supportsRange) { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); + } else if (ctrl.$viewValue !== element.val()) { + ctrl.$setViewValue(element.val()); } } } diff --git a/src/ng/directive/ngModel.js b/src/ng/directive/ngModel.js index 5d73c33ceb28..951d909342bc 100644 --- a/src/ng/directive/ngModel.js +++ b/src/ng/directive/ngModel.js @@ -562,6 +562,7 @@ NgModelController.prototype = { * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. */ $validate: function() { + // ignore $validate before model is initialized if (isNumberNaN(this.$modelValue)) { return; diff --git a/src/ng/directive/validators.js b/src/ng/directive/validators.js index 1a07a5501015..85682fb07ea8 100644 --- a/src/ng/directive/validators.js +++ b/src/ng/directive/validators.js @@ -62,24 +62,29 @@ * * */ -var requiredDirective = function() { +var requiredDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; + var oldVal = attr.required || $parse(attr.ngRequired)(scope); + attr.required = true; // force truthy in case we are on non input element ctrl.$validators.required = function(modelValue, viewValue) { return !attr.required || !ctrl.$isEmpty(viewValue); }; - attr.$observe('required', function() { - ctrl.$validate(); + attr.$observe('required', function(val) { + if (oldVal !== val) { + oldVal = val; + ctrl.$validate(); + } }); } }; -}; +}]; /** * @ngdoc directive @@ -162,36 +167,59 @@ var requiredDirective = function() { * * */ -var patternDirective = function() { +var patternDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; + compile: function(tElm, tAttr) { + var patternExp; + var parseFn; + + if (tAttr.ngPattern) { + patternExp = tAttr.ngPattern; - var regexp, patternExp = attr.ngPattern || attr.pattern; - attr.$observe('pattern', function(regex) { - if (isString(regex) && regex.length > 0) { - regex = new RegExp('^' + regex + '$'); + // ngPattern might be a scope expression, or an inlined regex, which is not parsable. + // We get value of the attribute here, so we can compare the old and the new value + // in the observer to avoid unnecessary validations + if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) { + parseFn = function() { return tAttr.ngPattern; }; + } else { + parseFn = $parse(tAttr.ngPattern); } + } + + return function(scope, elm, attr, ctrl) { + if (!ctrl) return; - if (regex && !regex.test) { - throw minErr('ngPattern')('noregexp', - 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, - regex, startingTag(elm)); + var attrVal = attr.pattern; + + if (attr.ngPattern) { + attrVal = parseFn(scope); + } else { + patternExp = attr.pattern; } - regexp = regex || undefined; - ctrl.$validate(); - }); + var regexp = parsePatternAttr(attrVal, patternExp, elm); + + attr.$observe('pattern', function(newVal) { + var oldRegexp = regexp; - ctrl.$validators.pattern = function(modelValue, viewValue) { - // HTML5 pattern constraint validates the input value, so we validate the viewValue - return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); + regexp = parsePatternAttr(newVal, patternExp, elm); + + if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) { + ctrl.$validate(); + } + }); + + ctrl.$validators.pattern = function(modelValue, viewValue) { + // HTML5 pattern constraint validates the input value, so we validate the viewValue + return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); + }; }; } + }; -}; +}]; /** * @ngdoc directive @@ -264,25 +292,29 @@ var patternDirective = function() { * * */ -var maxlengthDirective = function() { +var maxlengthDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; - var maxlength = -1; + var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope); + var maxlengthParsed = parseLength(maxlength); + attr.$observe('maxlength', function(value) { - var intVal = toInt(value); - maxlength = isNumberNaN(intVal) ? -1 : intVal; - ctrl.$validate(); + if (maxlength !== value) { + maxlengthParsed = parseLength(value); + maxlength = value; + ctrl.$validate(); + } }); ctrl.$validators.maxlength = function(modelValue, viewValue) { - return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); + return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed); }; } }; -}; +}]; /** * @ngdoc directive @@ -353,21 +385,49 @@ var maxlengthDirective = function() { * * */ -var minlengthDirective = function() { +var minlengthDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; - var minlength = 0; + var minlength = attr.minlength || $parse(attr.ngMinlength)(scope); + var minlengthParsed = parseLength(minlength) || -1; + attr.$observe('minlength', function(value) { - minlength = toInt(value) || 0; - ctrl.$validate(); + if (minlength !== value) { + minlengthParsed = parseLength(value) || -1; + minlength = value; + ctrl.$validate(); + } + }); ctrl.$validators.minlength = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; + return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed; }; } }; -}; +}]; + + +function parsePatternAttr(regex, patternExp, elm) { + if (!regex) return undefined; + + if (isString(regex)) { + regex = new RegExp('^' + regex + '$'); + } + + if (!regex.test) { + throw minErr('ngPattern')('noregexp', + 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, + regex, startingTag(elm)); + } + + return regex; +} + +function parseLength(val) { + var intVal = toInt(val); + return isNumberNaN(intVal) ? -1 : intVal; +} diff --git a/test/helpers/testabilityPatch.js b/test/helpers/testabilityPatch.js index 64544e586444..37d4ef694bad 100644 --- a/test/helpers/testabilityPatch.js +++ b/test/helpers/testabilityPatch.js @@ -312,7 +312,28 @@ window.dump = function() { function generateInputCompilerHelper(helper) { beforeEach(function() { + helper.validationCounter = {}; + module(function($compileProvider) { + $compileProvider.directive('validationSpy', function() { + return { + priority: 1, + require: 'ngModel', + link: function(scope, element, attrs, ctrl) { + var validationName = attrs.validationSpy; + + var originalValidator = ctrl.$validators[validationName]; + helper.validationCounter[validationName] = 0; + + ctrl.$validators[validationName] = function(modelValue, viewValue) { + helper.validationCounter[validationName]++; + + return originalValidator(modelValue, viewValue); + }; + } + }; + }); + $compileProvider.directive('attrCapture', function() { return function(scope, element, $attrs) { helper.attrs = $attrs; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 79b44c910170..e7159cba9ba7 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -839,6 +839,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); }); describe('max', function() { @@ -898,6 +914,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); expect($rootScope.form.alias.$valid).toBeTruthy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); }); }); @@ -1114,6 +1146,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); }); describe('max', function() { @@ -1176,6 +1224,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); expect($rootScope.form.alias.$valid).toBeTruthy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); }); }); @@ -1506,6 +1570,23 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); + }); describe('max', function() { @@ -1565,6 +1646,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); expect($rootScope.form.alias.$valid).toBeTruthy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); }); @@ -1972,6 +2069,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); }); describe('max', function() { @@ -2019,6 +2132,22 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); expect($rootScope.form.alias.$valid).toBeTruthy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); }); @@ -2361,6 +2490,26 @@ describe('input', function() { expect($rootScope.form.alias.$error.min).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.minVal = '2000-01-01'; + $rootScope.value = new Date(2010, 1, 1, 0, 0, 0); + + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); + }); describe('max', function() { @@ -2428,6 +2577,25 @@ describe('input', function() { expect($rootScope.form.alias.$error.max).toBeFalsy(); expect($rootScope.form.alias.$valid).toBeTruthy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.maxVal = '2000-01-01'; + $rootScope.value = new Date(2020, 1, 1, 0, 0, 0); + + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + + inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); }); @@ -3063,6 +3231,18 @@ describe('input', function() { $rootScope.$digest(); expect(inputElm).toBeValid(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.value = 5; + $rootScope.minVal = 3; + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); + }); describe('ngMin', function() { @@ -3131,6 +3311,17 @@ describe('input', function() { $rootScope.$digest(); expect(inputElm).toBeValid(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.value = 5; + $rootScope.minVal = 3; + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); }); @@ -3200,6 +3391,18 @@ describe('input', function() { $rootScope.$digest(); expect(inputElm).toBeValid(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.value = 5; + $rootScope.maxVal = 3; + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); + }); describe('ngMax', function() { @@ -3268,6 +3471,17 @@ describe('input', function() { $rootScope.$digest(); expect(inputElm).toBeValid(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.value = 5; + $rootScope.maxVal = 3; + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); }); @@ -3364,7 +3578,7 @@ describe('input', function() { expect(inputElm.val()).toBe('10'); expect(inputElm).toBeInvalid(); expect(ngModel.$error.step).toBe(true); - expect($rootScope.value).toBeUndefined(); + expect($rootScope.value).toBe(10); // an initially invalid value should not be changed helper.changeInputValueTo('15'); expect(inputElm).toBeValid(); @@ -3444,6 +3658,17 @@ describe('input', function() { expect($rootScope.value).toBe(1.16); } ); + + it('should validate only once after compilation inside ngRepeat', function() { + $rootScope.step = 10; + $rootScope.value = 20; + var inputElm = helper.compileInput('
' + + '' + + '
'); + + expect(helper.validationCounter.step).toBe(1); + }); + }); }); @@ -3485,6 +3710,16 @@ describe('input', function() { expect(inputElm).toBeValid(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.value = 'text'; + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.required).toBe(1); + }); }); describe('ngRequired', function() { @@ -3534,6 +3769,17 @@ describe('input', function() { expect($rootScope.value).toBeUndefined(); expect($rootScope.form.numberInput.$error.required).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.value = 'text'; + $rootScope.isRequired = true; + var inputElm = helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.required).toBe(1); + }); }); describe('when the ngRequired expression initially evaluates to false', function() { @@ -3848,6 +4094,17 @@ describe('input', function() { expect(inputElm.val()).toBe('20'); }); + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.minVal = 5; + $rootScope.value = 10; + helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); + } else { // input[type=range] will become type=text in browsers that don't support it @@ -3926,6 +4183,16 @@ describe('input', function() { expect(inputElm.val()).toBe('15'); }); + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.minVal = 5; + $rootScope.value = 10; + helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.min).toBe(1); + }); } }); @@ -4006,6 +4273,17 @@ describe('input', function() { expect(inputElm.val()).toBe('0'); }); + it('should only validate once after compilation when inside ngRepeat and the value is valid', function() { + $rootScope.maxVal = 5; + $rootScope.value = 5; + helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); + } else { it('should validate if "range" is not implemented', function() { var inputElm = helper.compileInput(''); @@ -4081,6 +4359,17 @@ describe('input', function() { expect(scope.value).toBe(5); expect(inputElm.val()).toBe('5'); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.maxVal = 5; + $rootScope.value = 10; + helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.max).toBe(1); + }); } }); @@ -4183,6 +4472,18 @@ describe('input', function() { expect(scope.value).toBe(10); expect(scope.form.alias.$error.step).toBeFalsy(); }); + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.stepVal = 5; + $rootScope.value = 10; + helper.compileInput('
' + + '' + + '
'); + $rootScope.$digest(); + + expect(helper.validationCounter.step).toBe(1); + }); + } else { it('should validate if "range" is not implemented', function() { @@ -4269,7 +4570,7 @@ describe('input', function() { expect(inputElm.val()).toBe('10'); expect(inputElm).toBeInvalid(); expect(ngModel.$error.step).toBe(true); - expect($rootScope.value).toBeUndefined(); + expect($rootScope.value).toBe(10); helper.changeInputValueTo('15'); expect(inputElm).toBeValid(); diff --git a/test/ng/directive/ngModelSpec.js b/test/ng/directive/ngModelSpec.js index 825973bdcf2f..f8eda1fe76ff 100644 --- a/test/ng/directive/ngModelSpec.js +++ b/test/ng/directive/ngModelSpec.js @@ -863,7 +863,6 @@ describe('ngModel', function() { }); }); - describe('view -> model update', function() { it('should always perform validations using the parsed model value', function() { diff --git a/test/ng/directive/validatorsSpec.js b/test/ng/directive/validatorsSpec.js index 9b152da7f386..2d89ce8abfce 100644 --- a/test/ng/directive/validatorsSpec.js +++ b/test/ng/directive/validatorsSpec.js @@ -230,6 +230,29 @@ describe('validators', function() { expect(ctrl.$error.pattern).toBe(true); expect(ctrlNg.$error.pattern).toBe(true); })); + + it('should only validate once after compilation when inside ngRepeat', function() { + + $rootScope.pattern = /\d{4}/; + + helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.pattern).toBe(1); + + helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.pattern).toBe(1); + }); }); @@ -312,9 +335,31 @@ describe('validators', function() { expect(ctrl.$error.minlength).toBe(true); expect(ctrlNg.$error.minlength).toBe(true); })); - }); + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.minlength = 5; + + var element = helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.minlength).toBe(1); + + element = helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.minlength).toBe(1); + }); + }); + describe('maxlength', function() { it('should invalidate values that are longer than the given maxlength', function() { @@ -500,6 +545,29 @@ describe('validators', function() { expect(ctrl.$error.maxlength).toBe(true); expect(ctrlNg.$error.maxlength).toBe(true); })); + + + it('should only validate once after compilation when inside ngRepeat', function() { + $rootScope.maxlength = 5; + + var element = helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.maxlength).toBe(1); + + element = helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.maxlength).toBe(1); + }); }); @@ -626,5 +694,41 @@ describe('validators', function() { expect(ctrl.$error.required).toBe(true); expect(ctrlNg.$error.required).toBe(true); })); + + + it('should validate only once after compilation when inside ngRepeat', function() { + helper.compileInput( + '
' + + '' + + '
'); + + $rootScope.$digest(); + + expect(helper.validationCounter.required).toBe(1); + }); + + + it('should validate only once after compilation when inside ngRepeat and ngRequired is true', function() { + $rootScope.isRequired = true; + + helper.compileInput( + '
' + + '' + + '
'); + + expect(helper.validationCounter.required).toBe(1); + }); + + + it('should validate only once after compilation when inside ngRepeat and ngRequired is false', function() { + $rootScope.isRequired = false; + + helper.compileInput( + '
' + + '' + + '
'); + + expect(helper.validationCounter.required).toBe(1); + }); }); });