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

Commit

Permalink
fix(ngModel): test & update correct model when running $validate
Browse files Browse the repository at this point in the history
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
  • Loading branch information
shahata authored and petebacondarwin committed Jul 3, 2014
1 parent 1a9cb0a commit f3cb274
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 31 deletions.
39 changes: 24 additions & 15 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -1781,13 +1781,24 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* Runs each of the registered validations set on the $validators object.
*/
this.$validate = function() {
this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue);
// ignore $validate before model initialized
if (ctrl.$modelValue !== ctrl.$modelValue) {
return;
}

var prev = ctrl.$modelValue;
ctrl.$$runValidators(ctrl.$$invalidModelValue || ctrl.$modelValue, ctrl.$viewValue);
if (prev !== ctrl.$modelValue) {
ctrl.$$writeModelToScope();
}
};

this.$$runValidators = function(modelValue, viewValue) {
forEach(ctrl.$validators, function(fn, name) {
ctrl.$setValidity(name, fn(modelValue, viewValue));
});
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
};

/**
Expand Down Expand Up @@ -1826,22 +1837,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$

if (ctrl.$modelValue !== modelValue &&
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {

ctrl.$$runValidators(modelValue, viewValue);
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;

ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
listener();
} catch(e) {
$exceptionHandler(e);
}
});
ctrl.$$writeModelToScope();
}
};

this.$$writeModelToScope = function() {
ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
listener();
} catch(e) {
$exceptionHandler(e);
}
});
};

/**
* @ngdoc method
* @name ngModel.NgModelController#$setViewValue
Expand Down Expand Up @@ -1920,8 +1931,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
}

ctrl.$$runValidators(modelValue, viewValue);
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;

if (ctrl.$viewValue !== viewValue) {
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
Expand Down
94 changes: 78 additions & 16 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,27 +294,13 @@ describe('NgModelController', function() {
};

ctrl.$modelValue = 'test';
ctrl.$$invalidModelValue = undefined;
ctrl.$validate();

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

ctrl.$modelValue = 'TEST';
ctrl.$validate();

expect(ctrl.$valid).toBe(true);
});

it('should perform validations when $validate() is called', function() {
ctrl.$validators.uppercase = function(value) {
return (/^[A-Z]+$/).test(value);
};

ctrl.$modelValue = 'test';
ctrl.$validate();

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

ctrl.$modelValue = 'TEST';
ctrl.$$invalidModelValue = undefined;
ctrl.$validate();

expect(ctrl.$valid).toBe(true);
Expand Down Expand Up @@ -403,6 +389,7 @@ describe('NgModelController', function() {
};
};

ctrl.$modelValue = undefined;
ctrl.$validators.a = curry(true);
ctrl.$validators.b = curry(true);
ctrl.$validators.c = curry(false);
Expand All @@ -423,6 +410,7 @@ describe('NgModelController', function() {
};
};

ctrl.$modelValue = undefined;
ctrl.$validators.unique = curry(false);
ctrl.$validators.tooLong = curry(false);
ctrl.$validators.notNumeric = curry(true);
Expand Down Expand Up @@ -1489,6 +1477,80 @@ describe('input', function() {
expect(inputElm).toBeValid();
expect(scope.form.input.$error.maxlength).not.toBe(true);
});

it('should assign the correct model after an observed validator became valid', function() {
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');

scope.$apply(function() {
scope.max = 1;
});
changeInputValueTo('12345');
expect(scope.value).toBeUndefined();

scope.$apply(function() {
scope.max = 6;
});
expect(scope.value).toBe('12345');
});

it('should assign the correct model after an observed validator became invalid', function() {
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');

scope.$apply(function() {
scope.max = 6;
});
changeInputValueTo('12345');
expect(scope.value).toBe('12345');

scope.$apply(function() {
scope.max = 1;
});
expect(scope.value).toBeUndefined();
});

it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
scope.$apply(function() {
scope.max = 1;
});

changeInputValueTo('12345');
expect(inputElm).toBeInvalid();
expect(scope.form.input.$error.maxlength).toBe(true);
expect(scope.value).toBeUndefined();

scope.$apply(function() {
scope.max = 3;
});

expect(inputElm).toBeInvalid();
expect(scope.form.input.$error.maxlength).toBe(true);
expect(scope.value).toBeUndefined();
});

it('should not notify if observed maxlength changed, but is still invalid', function() {
compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
'maxlength="{{ max }}" />');

scope.$apply(function() {
scope.max = 1;
});
changeInputValueTo('12345');

scope.ngChangeSpy = jasmine.createSpy();
scope.$apply(function() {
scope.max = 3;
});

expect(scope.ngChangeSpy).not.toHaveBeenCalled();
});

it('should leave the model untouched when validating before model initialization', function() {
scope.value = '12345';
compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
expect(scope.value).toBe('12345');
});

});


Expand Down

0 comments on commit f3cb274

Please sign in to comment.