Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit f3cb274

Browse files
shahatapetebacondarwin
authored andcommitted
fix(ngModel): test & update correct model when running $validate
If `$validate` is invoked when the model is already invalid, `$validate` should pass `$$invalidModelValue` to the validators, not `$modelValue`. Moreover, if `$validate` is invoked and it is found that the invalid model has become valid, this previously invalid model should be assigned to `$modelValue`. Lastly, if `$validate` is invoked and it is found that the model has become invalid, the previously valid model should be assigned to `$$invalidModelValue`. Closes #7836 Closes #7837
1 parent 1a9cb0a commit f3cb274

File tree

2 files changed

+102
-31
lines changed

2 files changed

+102
-31
lines changed

src/ng/directive/input.js

+24-15
Original file line numberDiff line numberDiff line change
@@ -1781,13 +1781,24 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
17811781
* Runs each of the registered validations set on the $validators object.
17821782
*/
17831783
this.$validate = function() {
1784-
this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue);
1784+
// ignore $validate before model initialized
1785+
if (ctrl.$modelValue !== ctrl.$modelValue) {
1786+
return;
1787+
}
1788+
1789+
var prev = ctrl.$modelValue;
1790+
ctrl.$$runValidators(ctrl.$$invalidModelValue || ctrl.$modelValue, ctrl.$viewValue);
1791+
if (prev !== ctrl.$modelValue) {
1792+
ctrl.$$writeModelToScope();
1793+
}
17851794
};
17861795

17871796
this.$$runValidators = function(modelValue, viewValue) {
17881797
forEach(ctrl.$validators, function(fn, name) {
17891798
ctrl.$setValidity(name, fn(modelValue, viewValue));
17901799
});
1800+
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
1801+
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
17911802
};
17921803

17931804
/**
@@ -1826,22 +1837,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
18261837

18271838
if (ctrl.$modelValue !== modelValue &&
18281839
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {
1829-
18301840
ctrl.$$runValidators(modelValue, viewValue);
1831-
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
1832-
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
1833-
1834-
ngModelSet($scope, ctrl.$modelValue);
1835-
forEach(ctrl.$viewChangeListeners, function(listener) {
1836-
try {
1837-
listener();
1838-
} catch(e) {
1839-
$exceptionHandler(e);
1840-
}
1841-
});
1841+
ctrl.$$writeModelToScope();
18421842
}
18431843
};
18441844

1845+
this.$$writeModelToScope = function() {
1846+
ngModelSet($scope, ctrl.$modelValue);
1847+
forEach(ctrl.$viewChangeListeners, function(listener) {
1848+
try {
1849+
listener();
1850+
} catch(e) {
1851+
$exceptionHandler(e);
1852+
}
1853+
});
1854+
};
1855+
18451856
/**
18461857
* @ngdoc method
18471858
* @name ngModel.NgModelController#$setViewValue
@@ -1920,8 +1931,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
19201931
}
19211932

19221933
ctrl.$$runValidators(modelValue, viewValue);
1923-
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
1924-
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
19251934

19261935
if (ctrl.$viewValue !== viewValue) {
19271936
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;

test/ng/directive/inputSpec.js

+78-16
Original file line numberDiff line numberDiff line change
@@ -294,27 +294,13 @@ describe('NgModelController', function() {
294294
};
295295

296296
ctrl.$modelValue = 'test';
297+
ctrl.$$invalidModelValue = undefined;
297298
ctrl.$validate();
298299

299300
expect(ctrl.$valid).toBe(false);
300301

301302
ctrl.$modelValue = 'TEST';
302-
ctrl.$validate();
303-
304-
expect(ctrl.$valid).toBe(true);
305-
});
306-
307-
it('should perform validations when $validate() is called', function() {
308-
ctrl.$validators.uppercase = function(value) {
309-
return (/^[A-Z]+$/).test(value);
310-
};
311-
312-
ctrl.$modelValue = 'test';
313-
ctrl.$validate();
314-
315-
expect(ctrl.$valid).toBe(false);
316-
317-
ctrl.$modelValue = 'TEST';
303+
ctrl.$$invalidModelValue = undefined;
318304
ctrl.$validate();
319305

320306
expect(ctrl.$valid).toBe(true);
@@ -403,6 +389,7 @@ describe('NgModelController', function() {
403389
};
404390
};
405391

392+
ctrl.$modelValue = undefined;
406393
ctrl.$validators.a = curry(true);
407394
ctrl.$validators.b = curry(true);
408395
ctrl.$validators.c = curry(false);
@@ -423,6 +410,7 @@ describe('NgModelController', function() {
423410
};
424411
};
425412

413+
ctrl.$modelValue = undefined;
426414
ctrl.$validators.unique = curry(false);
427415
ctrl.$validators.tooLong = curry(false);
428416
ctrl.$validators.notNumeric = curry(true);
@@ -1489,6 +1477,80 @@ describe('input', function() {
14891477
expect(inputElm).toBeValid();
14901478
expect(scope.form.input.$error.maxlength).not.toBe(true);
14911479
});
1480+
1481+
it('should assign the correct model after an observed validator became valid', function() {
1482+
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
1483+
1484+
scope.$apply(function() {
1485+
scope.max = 1;
1486+
});
1487+
changeInputValueTo('12345');
1488+
expect(scope.value).toBeUndefined();
1489+
1490+
scope.$apply(function() {
1491+
scope.max = 6;
1492+
});
1493+
expect(scope.value).toBe('12345');
1494+
});
1495+
1496+
it('should assign the correct model after an observed validator became invalid', function() {
1497+
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
1498+
1499+
scope.$apply(function() {
1500+
scope.max = 6;
1501+
});
1502+
changeInputValueTo('12345');
1503+
expect(scope.value).toBe('12345');
1504+
1505+
scope.$apply(function() {
1506+
scope.max = 1;
1507+
});
1508+
expect(scope.value).toBeUndefined();
1509+
});
1510+
1511+
it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
1512+
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
1513+
scope.$apply(function() {
1514+
scope.max = 1;
1515+
});
1516+
1517+
changeInputValueTo('12345');
1518+
expect(inputElm).toBeInvalid();
1519+
expect(scope.form.input.$error.maxlength).toBe(true);
1520+
expect(scope.value).toBeUndefined();
1521+
1522+
scope.$apply(function() {
1523+
scope.max = 3;
1524+
});
1525+
1526+
expect(inputElm).toBeInvalid();
1527+
expect(scope.form.input.$error.maxlength).toBe(true);
1528+
expect(scope.value).toBeUndefined();
1529+
});
1530+
1531+
it('should not notify if observed maxlength changed, but is still invalid', function() {
1532+
compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
1533+
'maxlength="{{ max }}" />');
1534+
1535+
scope.$apply(function() {
1536+
scope.max = 1;
1537+
});
1538+
changeInputValueTo('12345');
1539+
1540+
scope.ngChangeSpy = jasmine.createSpy();
1541+
scope.$apply(function() {
1542+
scope.max = 3;
1543+
});
1544+
1545+
expect(scope.ngChangeSpy).not.toHaveBeenCalled();
1546+
});
1547+
1548+
it('should leave the model untouched when validating before model initialization', function() {
1549+
scope.value = '12345';
1550+
compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
1551+
expect(scope.value).toBe('12345');
1552+
});
1553+
14921554
});
14931555

14941556

0 commit comments

Comments
 (0)