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);
+ });
});
});