This repository has been archived by the owner on Apr 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(ngModel): don't run parsers when executing $validate
Previously, $validate would execute the parsers to obtain a modelValue for validation. This was necessary, because a validator that is called outside of model / view update (e.g. from an observer) otherwise might only an undefined modelValue, because a previous view update has found a validation $error and set the model to undefined (as is tradition in angular) This is problematic as validators that are run immediately after the ngModelController initializes would parse the modelValue and replace the model, even though there had been no user input. The solution is to go back to an older design: the ngModelController will now internally record the $$rawModelValue. This means a model or view update will store the set / parsed modelValue regardless of validity, that is, it will never set it to undefined because of validation errors. When $validate is called, the $$rawModelValue will passed to the validators. If the validity has changed, the usual behavior is kept: if it became invalid, set the model to undefined, if valid, restore the last available modelValue - the $$rawModelValue. Additionally, $validate will only update the model when the validity changed. This is to prevent setting initially invalid models other than undefined to undefined (see #9063) Fixes: #9063 Fixes: #9959 Fixes: #9996 Fixes: #10025 Closes: #9890 Closes: #9913 Closes: #9997 Closes: #10048
- Loading branch information
Showing
2 changed files
with
173 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -505,6 +505,121 @@ describe('NgModelController', function() { | |
expect(ctrl.$valid).toBe(true); | ||
}); | ||
|
||
it('should pass the last parsed modelValue to the validators', function() { | ||
ctrl.$parsers.push(function(modelValue) { | ||
return modelValue + 'def'; | ||
}); | ||
|
||
ctrl.$setViewValue('abc'); | ||
|
||
ctrl.$validators.test = function(modelValue, viewValue) { | ||
return true; | ||
}; | ||
|
||
spyOn(ctrl.$validators, 'test'); | ||
|
||
ctrl.$validate(); | ||
|
||
expect(ctrl.$validators.test).toHaveBeenCalledWith('abcdef', 'abc'); | ||
}); | ||
|
||
it('should set the model to undefined when it becomes invalid', function() { | ||
var valid = true; | ||
ctrl.$validators.test = function(modelValue, viewValue) { | ||
return valid; | ||
}; | ||
|
||
scope.$apply('value = "abc"'); | ||
expect(scope.value).toBe('abc'); | ||
|
||
valid = false; | ||
ctrl.$validate(); | ||
|
||
expect(scope.value).toBeUndefined(); | ||
}); | ||
|
||
it('should update the model when it becomes valid', function() { | ||
var valid = true; | ||
ctrl.$validators.test = function(modelValue, viewValue) { | ||
return valid; | ||
}; | ||
|
||
scope.$apply('value = "abc"'); | ||
expect(scope.value).toBe('abc'); | ||
|
||
valid = false; | ||
ctrl.$validate(); | ||
expect(scope.value).toBeUndefined(); | ||
|
||
valid = true; | ||
ctrl.$validate(); | ||
expect(scope.value).toBe('abc'); | ||
}); | ||
|
||
it('should not update the model when it is valid, but there is a parse error', function() { | ||
ctrl.$parsers.push(function(modelValue) { | ||
return undefined; | ||
}); | ||
|
||
ctrl.$setViewValue('abc'); | ||
expect(ctrl.$error.parse).toBe(true); | ||
expect(scope.value).toBeUndefined(); | ||
|
||
ctrl.$validators.test = function(modelValue, viewValue) { | ||
return true; | ||
}; | ||
|
||
ctrl.$validate(); | ||
expect(ctrl.$error).toEqual({parse: true}); | ||
expect(scope.value).toBeUndefined(); | ||
}); | ||
|
||
it('should not set an invalid model to undefined when validity is the same', function() { | ||
ctrl.$validators.test = function() { | ||
return false; | ||
}; | ||
|
||
scope.$apply('value = "invalid"'); | ||
expect(ctrl.$valid).toBe(false); | ||
expect(scope.value).toBe('invalid'); | ||
|
||
ctrl.$validate(); | ||
expect(ctrl.$valid).toBe(false); | ||
expect(scope.value).toBe('invalid'); | ||
}); | ||
|
||
it('should not change a model that has a formatter', function() { | ||
ctrl.$validators.test = function() { | ||
return true; | ||
}; | ||
|
||
ctrl.$formatters.push(function(modelValue) { | ||
return 'xyz'; | ||
}); | ||
|
||
scope.$apply('value = "abc"'); | ||
expect(ctrl.$viewValue).toBe('xyz'); | ||
|
||
ctrl.$validate(); | ||
expect(scope.value).toBe('abc'); | ||
}); | ||
|
||
it('should not change a model that has a parser', function() { | ||
ctrl.$validators.test = function() { | ||
return true; | ||
}; | ||
|
||
ctrl.$parsers.push(function(modelValue) { | ||
return 'xyz'; | ||
}); | ||
|
||
scope.$apply('value = "abc"'); | ||
|
||
ctrl.$validate(); | ||
expect(scope.value).toBe('abc'); | ||
}); | ||
}); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
Narretz
Author
Contributor
|
||
|
||
describe('view -> model update', function() { | ||
it('should always perform validations using the parsed model value', function() { | ||
var captures; | ||
|
@@ -937,6 +1052,7 @@ describe('NgModelController', function() { | |
}); | ||
}); | ||
|
||
|
||
describe('ngModel', function() { | ||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; | ||
|
||
|
@@ -1351,7 +1467,6 @@ describe('input', function() { | |
expect(scope.form.$$renameControl).not.toHaveBeenCalled(); | ||
}); | ||
|
||
|
||
describe('compositionevents', function() { | ||
it('should not update the model between "compositionstart" and "compositionend" on non android', inject(function($sniffer) { | ||
$sniffer.android = false; | ||
|
@@ -2314,6 +2429,14 @@ describe('input', function() { | |
expect(inputElm).toBeValid(); | ||
expect(scope.form.input.$error.minlength).not.toBe(true); | ||
}); | ||
|
||
it('should validate when the model is initalized as a number', function() { | ||
scope.value = 12345; | ||
compileInput('<input type="text" name="input" ng-model="value" minlength="3" />'); | ||
expect(scope.value).toBe(12345); | ||
expect(scope.form.input.$error.minlength).toBeUndefined(); | ||
}); | ||
|
||
}); | ||
|
||
|
||
|
@@ -2412,6 +2535,13 @@ describe('input', function() { | |
expect(scope.value).toBe('12345'); | ||
}); | ||
|
||
it('should validate when the model is initalized as a number', function() { | ||
scope.value = 12345; | ||
compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />'); | ||
expect(scope.value).toBe(12345); | ||
expect(scope.form.input.$error.maxlength).toBeUndefined(); | ||
}); | ||
|
||
}); | ||
|
||
|
||
|
I think this closing brace was missing from the previous commit.