mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
refactor(ngModelController,formController): centralize and simplify logic
The previous logic for async validation in
`ngModelController` and `formController` was not maintainable:
- control logic is in multiple parts, e.g. `ctrl.$setValidity`
waits for end of promises and continuous the control flow
for async validation
- logic for updating the flags `ctrl.$error`, `ctrl.$pending`, `ctrl.$valid`
is super complicated, especially in `formController`
This refactoring makes the following changes:
- simplify async validation: centralize control logic
into one method in `ngModelController`:
* remove counters `invalidCount` and `pendingCount`
* use a flag `currentValidationRunId` to separate
async validator runs from each other
* use `$q.all` to determine when all async validators are done
- centralize way how `ctrl.$modelValue` and `ctrl.$invalidModelValue`
is updated
- simplify `ngModelController/formCtrl.$setValidity` and merge
`$$setPending/$$clearControlValidity/$$clearValidity/$$clearPending`
into one method, that is used by `ngModelController` AND
`formController`
* remove diff calculation, always calculate the correct state anew,
only cache the css classes that have been set to not
trigger too many css animations.
* remove fields from `ctrl.$error` that are valid and add private `ctrl.$$success`:
allows to correctly separate states for valid, invalid, skipped and pending,
especially transitively across parent forms.
- fix bug in `ngModelController`:
* only read out `input.validity.badInput`, but not
`input.validity.typeMismatch`,
to determine parser error: We still want our `email`
validator to run event when the model is validated.
- fix bugs in tests that were found as the logic is now consistent between
`ngModelController` and `formController`
BREAKING CHANGE:
- `ctrl.$error` does no more contain entries for validators that were
successful.
- `ctrl.$setValidity` now differentiates between `true`, `false`,
`undefined` and `null`, instead of previously only truthy vs falsy.
Closes #8941
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* global -nullFormCtrl, -SUBMITTED_CLASS */
|
||||
/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
|
||||
*/
|
||||
var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$removeControl: noop,
|
||||
@@ -23,12 +24,11 @@ SUBMITTED_CLASS = 'ng-submitted';
|
||||
* @property {boolean} $invalid True if at least one containing control or form is invalid.
|
||||
* @property {boolean} $submitted True if user has submitted the form even if its invalid.
|
||||
*
|
||||
* @property {Object} $error Is an object hash, containing references to all invalid controls or
|
||||
* forms, where:
|
||||
* @property {Object} $error Is an object hash, containing references to controls or
|
||||
* forms with failing validators, where:
|
||||
*
|
||||
* - keys are validation tokens (error names),
|
||||
* - values are arrays of controls or forms that are invalid for given error name.
|
||||
*
|
||||
* - values are arrays of controls or forms that have a failing validator for given error name.
|
||||
*
|
||||
* Built-in validation tokens:
|
||||
*
|
||||
@@ -55,12 +55,12 @@ FormController.$inject = ['$element', '$attrs', '$scope', '$animate'];
|
||||
function FormController(element, attrs, $scope, $animate) {
|
||||
var form = this,
|
||||
parentForm = element.parent().controller('form') || nullFormCtrl,
|
||||
invalidCount = 0, // used to easily determine if we are valid
|
||||
pendingCount = 0,
|
||||
controls = [],
|
||||
errors = form.$error = {};
|
||||
controls = [];
|
||||
|
||||
// init state
|
||||
form.$error = {};
|
||||
form.$$success = {};
|
||||
form.$pending = undefined;
|
||||
form.$name = attrs.name || attrs.ngForm;
|
||||
form.$dirty = false;
|
||||
form.$pristine = true;
|
||||
@@ -72,14 +72,6 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
|
||||
// Setup initial state of the control
|
||||
element.addClass(PRISTINE_CLASS);
|
||||
toggleValidCss(true);
|
||||
|
||||
// convenience method for easy toggling of classes
|
||||
function toggleValidCss(isValid, validationErrorKey) {
|
||||
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
|
||||
$animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
|
||||
$animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -148,34 +140,16 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
if (control.$name && form[control.$name] === control) {
|
||||
delete form[control.$name];
|
||||
}
|
||||
forEach(form.$pending, function(value, name) {
|
||||
form.$setValidity(name, null, control);
|
||||
});
|
||||
forEach(form.$error, function(value, name) {
|
||||
form.$setValidity(name, null, control);
|
||||
});
|
||||
|
||||
form.$$clearControlValidity(control);
|
||||
arrayRemove(controls, control);
|
||||
};
|
||||
|
||||
form.$$clearControlValidity = function(control) {
|
||||
forEach(form.$pending, clear);
|
||||
forEach(errors, clear);
|
||||
|
||||
function clear(queue, validationToken) {
|
||||
form.$setValidity(validationToken, true, control);
|
||||
}
|
||||
};
|
||||
|
||||
form.$$setPending = function(validationToken, control) {
|
||||
var pending = form.$pending && form.$pending[validationToken];
|
||||
|
||||
if (!pending || !includes(pending, control)) {
|
||||
pendingCount++;
|
||||
form.$valid = form.$invalid = undefined;
|
||||
form.$pending = form.$pending || {};
|
||||
if (!pending) {
|
||||
pending = form.$pending[validationToken] = [];
|
||||
}
|
||||
pending.push(control);
|
||||
parentForm.$$setPending(validationToken, form);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -186,72 +160,33 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
*
|
||||
* This method will also propagate to parent forms.
|
||||
*/
|
||||
form.$setValidity = function(validationToken, isValid, control) {
|
||||
var queue = errors[validationToken];
|
||||
var pendingChange, pending = form.$pending && form.$pending[validationToken];
|
||||
|
||||
if (pending) {
|
||||
pendingChange = pending.indexOf(control) >= 0;
|
||||
if (pendingChange) {
|
||||
arrayRemove(pending, control);
|
||||
pendingCount--;
|
||||
|
||||
if (pending.length === 0) {
|
||||
delete form.$pending[validationToken];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pendingNoMore = form.$pending && pendingCount === 0;
|
||||
if (pendingNoMore) {
|
||||
form.$pending = undefined;
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
if (queue || pendingChange) {
|
||||
if (queue) {
|
||||
arrayRemove(queue, control);
|
||||
}
|
||||
if (!queue || !queue.length) {
|
||||
if (errors[validationToken]) {
|
||||
invalidCount--;
|
||||
}
|
||||
if (!invalidCount) {
|
||||
if (!form.$pending) {
|
||||
toggleValidCss(isValid);
|
||||
form.$valid = true;
|
||||
form.$invalid = false;
|
||||
}
|
||||
} else if(pendingNoMore) {
|
||||
toggleValidCss(false);
|
||||
form.$valid = false;
|
||||
form.$invalid = true;
|
||||
}
|
||||
errors[validationToken] = false;
|
||||
toggleValidCss(true, validationToken);
|
||||
parentForm.$setValidity(validationToken, true, form);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!form.$pending) {
|
||||
form.$valid = false;
|
||||
form.$invalid = true;
|
||||
}
|
||||
|
||||
if (!invalidCount) {
|
||||
toggleValidCss(isValid);
|
||||
}
|
||||
if (queue) {
|
||||
if (includes(queue, control)) return;
|
||||
addSetValidityMethod({
|
||||
ctrl: this,
|
||||
$element: element,
|
||||
set: function(object, property, control) {
|
||||
var list = object[property];
|
||||
if (!list) {
|
||||
object[property] = [control];
|
||||
} else {
|
||||
errors[validationToken] = queue = [];
|
||||
invalidCount++;
|
||||
toggleValidCss(false, validationToken);
|
||||
parentForm.$setValidity(validationToken, false, form);
|
||||
var index = list.indexOf(control);
|
||||
if (index === -1) {
|
||||
list.push(control);
|
||||
}
|
||||
}
|
||||
queue.push(control);
|
||||
}
|
||||
};
|
||||
},
|
||||
unset: function(object, property, control) {
|
||||
var list = object[property];
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
arrayRemove(list, control);
|
||||
if (list.length === 0) {
|
||||
delete object[property];
|
||||
}
|
||||
},
|
||||
parentForm: parentForm,
|
||||
$animate: $animate
|
||||
});
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
||||
@@ -21,6 +21,7 @@ var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d))?$/;
|
||||
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
|
||||
var $ngModelMinErr = new minErr('ngModel');
|
||||
|
||||
var inputType = {
|
||||
|
||||
/**
|
||||
@@ -1121,7 +1122,11 @@ function badInputChecker(scope, element, attr, ctrl) {
|
||||
if (nativeValidation) {
|
||||
ctrl.$parsers.push(function(value) {
|
||||
var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
|
||||
return validity.badInput || validity.typeMismatch ? undefined : value;
|
||||
// Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
|
||||
// - also sets validity.badInput (should only be validity.typeMismatch).
|
||||
// - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
|
||||
// - can ignore this case as we can still read out the erroneous email...
|
||||
return validity.badInput && !validity.typeMismatch ? undefined : value;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1181,7 +1186,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
badInputChecker(scope, element, attr, ctrl);
|
||||
// Note: no badInputChecker here by purpose as `url` is only a validation
|
||||
// in browsers, i.e. we can always read out input.value even if it is not valid!
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
stringBasedInputType(ctrl);
|
||||
|
||||
@@ -1193,7 +1199,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
}
|
||||
|
||||
function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
badInputChecker(scope, element, attr, ctrl);
|
||||
// Note: no badInputChecker here by purpose as `url` is only a validation
|
||||
// in browsers, i.e. we can always read out input.value even if it is not valid!
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
stringBasedInputType(ctrl);
|
||||
|
||||
@@ -1512,7 +1519,8 @@ var VALID_CLASS = 'ng-valid',
|
||||
* view value has changed. It is called with no arguments, and its return value is ignored.
|
||||
* This can be used in place of additional $watches against the model value.
|
||||
*
|
||||
* @property {Object} $error An object hash with all errors as keys.
|
||||
* @property {Object} $error An object hash with all failing validator ids as keys.
|
||||
* @property {Object} $pending An object hash with all pending validator ids as keys.
|
||||
*
|
||||
* @property {boolean} $untouched True if control has not lost focus yet.
|
||||
* @property {boolean} $touched True if control has lost focus.
|
||||
@@ -1520,7 +1528,6 @@ var VALID_CLASS = 'ng-valid',
|
||||
* @property {boolean} $dirty True if user has already interacted with the control.
|
||||
* @property {boolean} $valid True if there is no error.
|
||||
* @property {boolean} $invalid True if at least one error on the control.
|
||||
* @property {Object.<string, boolean>} $pending True if one or more asynchronous validators is still yet to be delivered.
|
||||
*
|
||||
* @description
|
||||
*
|
||||
@@ -1623,13 +1630,12 @@ var VALID_CLASS = 'ng-valid',
|
||||
*
|
||||
*
|
||||
*/
|
||||
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope',
|
||||
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope) {
|
||||
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q',
|
||||
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q) {
|
||||
this.$viewValue = Number.NaN;
|
||||
this.$modelValue = Number.NaN;
|
||||
this.$validators = {};
|
||||
this.$asyncValidators = {};
|
||||
this.$validators = {};
|
||||
this.$parsers = [];
|
||||
this.$formatters = [];
|
||||
this.$viewChangeListeners = [];
|
||||
@@ -1639,6 +1645,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$dirty = false;
|
||||
this.$valid = true;
|
||||
this.$invalid = false;
|
||||
this.$error = {}; // keep invalid keys here
|
||||
this.$$success = {}; // keep valid keys here
|
||||
this.$pending = undefined; // keep pending keys here
|
||||
this.$name = $attr.name;
|
||||
|
||||
|
||||
@@ -1700,131 +1709,44 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
};
|
||||
|
||||
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
|
||||
invalidCount = 0, // used to easily determine if we are valid
|
||||
pendingCount = 0, // used to easily determine if there are any pending validations
|
||||
$error = this.$error = {}; // keep invalid keys here
|
||||
|
||||
currentValidationRunId = 0;
|
||||
|
||||
// Setup initial state of the control
|
||||
$element
|
||||
.addClass(PRISTINE_CLASS)
|
||||
.addClass(UNTOUCHED_CLASS);
|
||||
toggleValidCss(true);
|
||||
|
||||
// convenience method for easy toggling of classes
|
||||
function toggleValidCss(isValid, validationErrorKey) {
|
||||
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
|
||||
$animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
|
||||
$animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
|
||||
}
|
||||
|
||||
this.$$clearValidity = function() {
|
||||
$animate.removeClass($element, PENDING_CLASS);
|
||||
forEach(ctrl.$error, function(val, key) {
|
||||
var validationKey = snake_case(key, '-');
|
||||
$animate.removeClass($element, VALID_CLASS + validationKey);
|
||||
$animate.removeClass($element, INVALID_CLASS + validationKey);
|
||||
});
|
||||
|
||||
// just incase an asnyc validator is still running while
|
||||
// the parser fails
|
||||
if (ctrl.$pending) {
|
||||
ctrl.$$clearPending();
|
||||
}
|
||||
|
||||
invalidCount = 0;
|
||||
$error = ctrl.$error = {};
|
||||
|
||||
parentForm.$$clearControlValidity(ctrl);
|
||||
};
|
||||
|
||||
this.$$clearPending = function() {
|
||||
pendingCount = 0;
|
||||
ctrl.$pending = undefined;
|
||||
$animate.removeClass($element, PENDING_CLASS);
|
||||
};
|
||||
|
||||
this.$$setPending = function(validationErrorKey, promise, currentValue) {
|
||||
ctrl.$pending = ctrl.$pending || {};
|
||||
if (angular.isUndefined(ctrl.$pending[validationErrorKey])) {
|
||||
ctrl.$pending[validationErrorKey] = true;
|
||||
pendingCount++;
|
||||
}
|
||||
|
||||
ctrl.$valid = ctrl.$invalid = undefined;
|
||||
parentForm.$$setPending(validationErrorKey, ctrl);
|
||||
|
||||
$animate.addClass($element, PENDING_CLASS);
|
||||
$animate.removeClass($element, INVALID_CLASS);
|
||||
$animate.removeClass($element, VALID_CLASS);
|
||||
|
||||
//Special-case for (undefined|null|false|NaN) values to avoid
|
||||
//having to compare each of them with each other
|
||||
currentValue = currentValue || '';
|
||||
promise.then(resolve(true), resolve(false));
|
||||
|
||||
function resolve(bool) {
|
||||
return function() {
|
||||
var value = ctrl.$viewValue || '';
|
||||
if (ctrl.$pending && ctrl.$pending[validationErrorKey] && currentValue === value) {
|
||||
pendingCount--;
|
||||
delete ctrl.$pending[validationErrorKey];
|
||||
ctrl.$setValidity(validationErrorKey, bool);
|
||||
if (pendingCount === 0) {
|
||||
ctrl.$$clearPending();
|
||||
ctrl.$$updateValidModelValue(value);
|
||||
ctrl.$$writeModelToScope();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$setValidity
|
||||
*
|
||||
* @description
|
||||
* Change the validity state, and notifies the form when the control changes validity. (i.e. it
|
||||
* does not notify form if given validator is already marked as invalid).
|
||||
* Change the validity state, and notifies the form.
|
||||
*
|
||||
* This method can be called within $parsers/$formatters. However, if possible, please use the
|
||||
* `ngModel.$validators` pipeline which is designed to handle validations with true/false values.
|
||||
* `ngModel.$validators` pipeline which is designed to call this method automatically.
|
||||
*
|
||||
* @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
|
||||
* to `$error[validationErrorKey]=!isValid` so that it is available for data-binding.
|
||||
* to `$error[validationErrorKey]` and `$pending[validationErrorKey]`
|
||||
* so that it is available for data-binding.
|
||||
* The `validationErrorKey` should be in camelCase and will get converted into dash-case
|
||||
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
|
||||
* class and can be bound to as `{{someForm.someControl.$error.myError}}` .
|
||||
* @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
|
||||
* @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
|
||||
* or skipped (null).
|
||||
*/
|
||||
this.$setValidity = function(validationErrorKey, isValid) {
|
||||
|
||||
// avoid doing anything if the validation value has not changed
|
||||
// jshint -W018
|
||||
if (!ctrl.$pending && $error[validationErrorKey] === !isValid) return;
|
||||
// jshint +W018
|
||||
|
||||
if (isValid) {
|
||||
if ($error[validationErrorKey]) invalidCount--;
|
||||
if (!invalidCount && !pendingCount) {
|
||||
toggleValidCss(true);
|
||||
ctrl.$valid = true;
|
||||
ctrl.$invalid = false;
|
||||
}
|
||||
} else if (!$error[validationErrorKey]) {
|
||||
invalidCount++;
|
||||
if (!pendingCount) {
|
||||
toggleValidCss(false);
|
||||
ctrl.$invalid = true;
|
||||
ctrl.$valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
$error[validationErrorKey] = !isValid;
|
||||
toggleValidCss(isValid, validationErrorKey);
|
||||
parentForm.$setValidity(validationErrorKey, isValid, ctrl);
|
||||
};
|
||||
addSetValidityMethod({
|
||||
ctrl: this,
|
||||
$element: $element,
|
||||
set: function(object, property) {
|
||||
object[property] = true;
|
||||
},
|
||||
unset: function(object, property) {
|
||||
delete object[property];
|
||||
},
|
||||
parentForm: parentForm,
|
||||
$animate: $animate
|
||||
});
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -1959,49 +1881,102 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
}
|
||||
|
||||
var prev = ctrl.$modelValue;
|
||||
ctrl.$$runValidators(ctrl.$$invalidModelValue || ctrl.$modelValue, ctrl.$viewValue);
|
||||
if (prev !== ctrl.$modelValue) {
|
||||
ctrl.$$writeModelToScope();
|
||||
}
|
||||
};
|
||||
|
||||
this.$$runValidators = function(modelValue, viewValue) {
|
||||
// this is called in the event if incase the input value changes
|
||||
// while a former asynchronous validator is still doing its thing
|
||||
if (ctrl.$pending) {
|
||||
ctrl.$$clearPending();
|
||||
}
|
||||
|
||||
var continueValidation = validate(ctrl.$validators, function(validator, result) {
|
||||
ctrl.$setValidity(validator, result);
|
||||
ctrl.$$runValidators(undefined, ctrl.$$invalidModelValue || ctrl.$modelValue, ctrl.$viewValue, function() {
|
||||
if (prev !== ctrl.$modelValue) {
|
||||
ctrl.$$writeModelToScope();
|
||||
}
|
||||
});
|
||||
|
||||
if (continueValidation) {
|
||||
validate(ctrl.$asyncValidators, function(validator, result) {
|
||||
if (!isPromiseLike(result)) {
|
||||
throw $ngModelMinErr("$asyncValidators",
|
||||
"Expected asynchronous validator to return a promise but got '{0}' instead.", result);
|
||||
}
|
||||
ctrl.$$setPending(validator, result, modelValue);
|
||||
});
|
||||
}
|
||||
|
||||
ctrl.$$updateValidModelValue(modelValue);
|
||||
|
||||
function validate(validators, callback) {
|
||||
var status = true;
|
||||
forEach(validators, function(fn, name) {
|
||||
var result = fn(modelValue, viewValue);
|
||||
callback(name, result);
|
||||
status = status && result;
|
||||
});
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
this.$$updateValidModelValue = function(modelValue) {
|
||||
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
|
||||
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
|
||||
this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
|
||||
currentValidationRunId++;
|
||||
var localValidationRunId = currentValidationRunId;
|
||||
|
||||
// We can update the $$invalidModelValue immediately as we don't have to wait for validators!
|
||||
ctrl.$$invalidModelValue = modelValue;
|
||||
|
||||
// check parser error
|
||||
if (!processParseErrors(parseValid)) {
|
||||
return;
|
||||
}
|
||||
if (!processSyncValidators()) {
|
||||
return;
|
||||
}
|
||||
processAsyncValidators();
|
||||
|
||||
function processParseErrors(parseValid) {
|
||||
var errorKey = ctrl.$$parserName || 'parse';
|
||||
if (parseValid === undefined) {
|
||||
setValidity(errorKey, null);
|
||||
} else {
|
||||
setValidity(errorKey, parseValid);
|
||||
if (!parseValid) {
|
||||
forEach(ctrl.$validators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
forEach(ctrl.$asyncValidators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
validationDone();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function processSyncValidators() {
|
||||
var syncValidatorsValid = true;
|
||||
forEach(ctrl.$validators, function(validator, name) {
|
||||
var result = validator(modelValue, viewValue);
|
||||
syncValidatorsValid = syncValidatorsValid && result;
|
||||
setValidity(name, result);
|
||||
});
|
||||
if (!syncValidatorsValid) {
|
||||
forEach(ctrl.$asyncValidators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
validationDone();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function processAsyncValidators() {
|
||||
var validatorPromises = [];
|
||||
forEach(ctrl.$asyncValidators, function(validator, name) {
|
||||
var promise = validator(modelValue, viewValue);
|
||||
if (!isPromiseLike(promise)) {
|
||||
throw $ngModelMinErr("$asyncValidators",
|
||||
"Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
|
||||
}
|
||||
setValidity(name, undefined);
|
||||
validatorPromises.push(promise.then(function() {
|
||||
setValidity(name, true);
|
||||
}, function(error) {
|
||||
setValidity(name, false);
|
||||
}));
|
||||
});
|
||||
if (!validatorPromises.length) {
|
||||
validationDone();
|
||||
} else {
|
||||
$q.all(validatorPromises).then(validationDone);
|
||||
}
|
||||
}
|
||||
|
||||
function setValidity(name, isValid) {
|
||||
if (localValidationRunId === currentValidationRunId) {
|
||||
ctrl.$setValidity(name, isValid);
|
||||
}
|
||||
}
|
||||
|
||||
function validationDone() {
|
||||
if (localValidationRunId === currentValidationRunId) {
|
||||
// set the validated model value
|
||||
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
|
||||
|
||||
doneCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2037,27 +2012,18 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
parentForm.$setDirty();
|
||||
}
|
||||
|
||||
var hasBadInput, modelValue = viewValue;
|
||||
var parserValid = true, modelValue = viewValue;
|
||||
for(var i = 0; i < ctrl.$parsers.length; i++) {
|
||||
modelValue = ctrl.$parsers[i](modelValue);
|
||||
if (isUndefined(modelValue)) {
|
||||
hasBadInput = true;
|
||||
parserValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var parserName = ctrl.$$parserName || 'parse';
|
||||
if (hasBadInput) {
|
||||
ctrl.$$invalidModelValue = ctrl.$modelValue = undefined;
|
||||
ctrl.$$clearValidity();
|
||||
ctrl.$setValidity(parserName, false);
|
||||
ctrl.$$runValidators(parserValid, modelValue, viewValue, function() {
|
||||
ctrl.$$writeModelToScope();
|
||||
} else if (ctrl.$modelValue !== modelValue &&
|
||||
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {
|
||||
ctrl.$setValidity(parserName, true);
|
||||
ctrl.$$runValidators(modelValue, viewValue);
|
||||
ctrl.$$writeModelToScope();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.$$writeModelToScope = function() {
|
||||
@@ -2175,12 +2141,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
while(idx--) {
|
||||
viewValue = formatters[idx](viewValue);
|
||||
}
|
||||
|
||||
ctrl.$$runValidators(modelValue, viewValue);
|
||||
|
||||
if (ctrl.$viewValue !== viewValue) {
|
||||
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
|
||||
ctrl.$render();
|
||||
var lastViewValue = ctrl.$viewValue;
|
||||
if (lastViewValue !== viewValue) {
|
||||
ctrl.$$runValidators(undefined, modelValue, viewValue, function() {
|
||||
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
|
||||
ctrl.$render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2920,3 +2886,106 @@ var ngModelOptionsDirective = function() {
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
// helper methods
|
||||
function addSetValidityMethod(context) {
|
||||
var ctrl = context.ctrl,
|
||||
$element = context.$element,
|
||||
classCache = {},
|
||||
set = context.set,
|
||||
unset = context.unset,
|
||||
parentForm = context.parentForm,
|
||||
$animate = context.$animate;
|
||||
|
||||
ctrl.$setValidity = setValidity;
|
||||
toggleValidationCss('', true);
|
||||
|
||||
function setValidity(validationErrorKey, state, options) {
|
||||
if (state === undefined) {
|
||||
createAndSet('$pending', validationErrorKey, options);
|
||||
} else {
|
||||
unsetAndCleanup('$pending', validationErrorKey, options);
|
||||
}
|
||||
if (!isBoolean(state)) {
|
||||
unset(ctrl.$error, validationErrorKey, options);
|
||||
unset(ctrl.$$success, validationErrorKey, options);
|
||||
} else {
|
||||
if (state) {
|
||||
unset(ctrl.$error, validationErrorKey, options);
|
||||
set(ctrl.$$success, validationErrorKey, options);
|
||||
} else {
|
||||
set(ctrl.$error, validationErrorKey, options);
|
||||
unset(ctrl.$$success, validationErrorKey, options);
|
||||
}
|
||||
}
|
||||
if (ctrl.$pending) {
|
||||
cachedToggleClass(PENDING_CLASS, true);
|
||||
ctrl.$valid = ctrl.$invalid = undefined;
|
||||
toggleValidationCss('', null);
|
||||
} else {
|
||||
cachedToggleClass(PENDING_CLASS, false);
|
||||
ctrl.$valid = isObjectEmpty(ctrl.$error);
|
||||
ctrl.$invalid = !ctrl.$valid;
|
||||
toggleValidationCss('', ctrl.$valid);
|
||||
}
|
||||
|
||||
// re-read the state as the set/unset methods could have
|
||||
// combined state in ctrl.$error[validationError] (used for forms),
|
||||
// where setting/unsetting only increments/decrements the value,
|
||||
// and does not replace it.
|
||||
var combinedState;
|
||||
if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
|
||||
combinedState = undefined;
|
||||
} else if (ctrl.$error[validationErrorKey]) {
|
||||
combinedState = false;
|
||||
} else if (ctrl.$$success[validationErrorKey]) {
|
||||
combinedState = true;
|
||||
} else {
|
||||
combinedState = null;
|
||||
}
|
||||
toggleValidationCss(validationErrorKey, combinedState);
|
||||
parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
|
||||
}
|
||||
|
||||
function createAndSet(name, value, options) {
|
||||
if (!ctrl[name]) {
|
||||
ctrl[name] = {};
|
||||
}
|
||||
set(ctrl[name], value, options);
|
||||
}
|
||||
|
||||
function unsetAndCleanup(name, value, options) {
|
||||
if (ctrl[name]) {
|
||||
unset(ctrl[name], value, options);
|
||||
}
|
||||
if (isObjectEmpty(ctrl[name])) {
|
||||
ctrl[name] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function cachedToggleClass(className, switchValue) {
|
||||
if (switchValue && !classCache[className]) {
|
||||
$animate.addClass($element, className);
|
||||
classCache[className] = true;
|
||||
} else if (!switchValue && classCache[className]) {
|
||||
$animate.removeClass($element, className);
|
||||
classCache[className] = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleValidationCss(validationErrorKey, isValid) {
|
||||
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
|
||||
|
||||
cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
|
||||
cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
|
||||
}
|
||||
}
|
||||
|
||||
function isObjectEmpty(obj) {
|
||||
if (obj) {
|
||||
for (var prop in obj) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('form', function() {
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
|
||||
expect(form.$error.required).toBe(false);
|
||||
expect(form.$error.required).toBeFalsy();
|
||||
expect(form.alias).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -125,8 +125,8 @@ describe('form', function() {
|
||||
expect(scope.firstName).toBe('val1');
|
||||
expect(scope.lastName).toBe('val2');
|
||||
|
||||
expect(scope.formA.$error.required).toBe(false);
|
||||
expect(scope.formB.$error.required).toBe(false);
|
||||
expect(scope.formA.$error.required).toBeFalsy();
|
||||
expect(scope.formB.$error.required).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
@@ -399,8 +399,8 @@ describe('form', function() {
|
||||
expect(child.$error.MyError).toEqual([inputB]);
|
||||
|
||||
inputB.$setValidity('MyError', true);
|
||||
expect(parent.$error.MyError).toBe(false);
|
||||
expect(child.$error.MyError).toBe(false);
|
||||
expect(parent.$error.MyError).toBeFalsy();
|
||||
expect(child.$error.MyError).toBeFalsy();
|
||||
|
||||
child.$setDirty();
|
||||
expect(parent.$dirty).toBeTruthy();
|
||||
@@ -430,7 +430,7 @@ describe('form', function() {
|
||||
|
||||
expect(parent.child).toBeUndefined();
|
||||
expect(scope.child).toBeUndefined();
|
||||
expect(parent.$error.required).toBe(false);
|
||||
expect(parent.$error.required).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@ describe('form', function() {
|
||||
|
||||
expect(parent.child).toBeUndefined();
|
||||
expect(scope.child.form).toBeUndefined();
|
||||
expect(parent.$error.required).toBe(false);
|
||||
expect(parent.$error.required).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
@@ -486,12 +486,12 @@ describe('form', function() {
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
|
||||
expect(parent.$error.required).toBe(false);
|
||||
expect(child.$error.required).toBe(false);
|
||||
expect(parent.$error.required).toBeFalsy();
|
||||
expect(child.$error.required).toBeFalsy();
|
||||
expect(doc.hasClass('ng-valid')).toBe(true);
|
||||
expect(doc.hasClass('ng-valid-required')).toBe(true);
|
||||
expect(doc.hasClass('ng-valid-required')).toBe(false);
|
||||
expect(doc.find('div').hasClass('ng-valid')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-valid-required')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-valid-required')).toBe(false);
|
||||
});
|
||||
|
||||
it('should leave the parent form invalid when deregister a removed input', function() {
|
||||
@@ -551,8 +551,8 @@ describe('form', function() {
|
||||
expect(parent.$error.myRule).toEqual([child]);
|
||||
|
||||
input.$setValidity('myRule', true);
|
||||
expect(parent.$error.myRule).toBe(false);
|
||||
expect(child.$error.myRule).toBe(false);
|
||||
expect(parent.$error.myRule).toBeFalsy();
|
||||
expect(child.$error.myRule).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -596,8 +596,15 @@ describe('form', function() {
|
||||
expect(doc.hasClass('ng-invalid-error')).toBe(false);
|
||||
expect(doc.hasClass('ng-valid-another')).toBe(true);
|
||||
expect(doc.hasClass('ng-invalid-another')).toBe(false);
|
||||
});
|
||||
|
||||
// validators are skipped, e.g. becuase of a parser error
|
||||
control.$setValidity('error', null);
|
||||
control.$setValidity('another', null);
|
||||
expect(doc.hasClass('ng-valid-error')).toBe(false);
|
||||
expect(doc.hasClass('ng-invalid-error')).toBe(false);
|
||||
expect(doc.hasClass('ng-valid-another')).toBe(false);
|
||||
expect(doc.hasClass('ng-invalid-another')).toBe(false);
|
||||
});
|
||||
|
||||
it('should have ng-pristine/ng-dirty css class', function() {
|
||||
expect(doc).toBePristine();
|
||||
@@ -618,7 +625,7 @@ describe('form', function() {
|
||||
var defer, form = doc.data('$formController');
|
||||
|
||||
var ctrl = {};
|
||||
form.$$setPending('matias', ctrl);
|
||||
form.$setValidity('matias', undefined, ctrl);
|
||||
|
||||
expect(form.$valid).toBeUndefined();
|
||||
expect(form.$invalid).toBeUndefined();
|
||||
@@ -799,8 +806,7 @@ describe('form animations', function() {
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-valid-required');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-invalid-required');
|
||||
assertValidAnimation($animate.queue[2], 'addClass', 'ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when valid', inject(function($animate) {
|
||||
@@ -810,10 +816,9 @@ describe('form animations', function() {
|
||||
|
||||
form.$setValidity('required', true);
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-invalid-required');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-valid-required');
|
||||
assertValidAnimation($animate.queue[0], 'addClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[2], 'addClass', 'ng-valid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when dirty', inject(function($animate) {
|
||||
@@ -838,15 +843,14 @@ describe('form animations', function() {
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-invalid-custom-error');
|
||||
assertValidAnimation($animate.queue[2], 'addClass', 'ng-invalid-custom-error');
|
||||
|
||||
$animate.queue = [];
|
||||
form.$setValidity('custom-error', true);
|
||||
|
||||
assertValidAnimation($animate.queue[0], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[1], 'addClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[2], 'removeClass', 'ng-invalid-custom-error');
|
||||
assertValidAnimation($animate.queue[3], 'addClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation($animate.queue[0], 'addClass', 'ng-valid');
|
||||
assertValidAnimation($animate.queue[1], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation($animate.queue[2], 'addClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation($animate.queue[3], 'removeClass', 'ng-invalid-custom-error');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -52,27 +52,53 @@ describe('NgModelController', function() {
|
||||
|
||||
describe('setValidity', function() {
|
||||
|
||||
it('should propagate invalid to the parent form only when valid', function() {
|
||||
function expectOneError() {
|
||||
expect(ctrl.$error).toEqual({someError: true});
|
||||
expect(ctrl.$$success).toEqual({});
|
||||
expect(ctrl.$pending).toBeUndefined();
|
||||
}
|
||||
|
||||
function expectOneSuccess() {
|
||||
expect(ctrl.$error).toEqual({});
|
||||
expect(ctrl.$$success).toEqual({someError: true});
|
||||
expect(ctrl.$pending).toBeUndefined();
|
||||
}
|
||||
|
||||
function expectOnePending() {
|
||||
expect(ctrl.$error).toEqual({});
|
||||
expect(ctrl.$$success).toEqual({});
|
||||
expect(ctrl.$pending).toEqual({someError: true});
|
||||
}
|
||||
|
||||
function expectCleared() {
|
||||
expect(ctrl.$error).toEqual({});
|
||||
expect(ctrl.$$success).toEqual({});
|
||||
expect(ctrl.$pending).toBeUndefined();
|
||||
}
|
||||
|
||||
it('should propagate validity to the parent form', function() {
|
||||
expect(parentFormCtrl.$setValidity).not.toHaveBeenCalled();
|
||||
ctrl.$setValidity('ERROR', false);
|
||||
expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('ERROR', false, ctrl);
|
||||
|
||||
parentFormCtrl.$setValidity.reset();
|
||||
ctrl.$setValidity('ERROR', false);
|
||||
expect(parentFormCtrl.$setValidity).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should transition from states correctly', function() {
|
||||
expectCleared();
|
||||
|
||||
it('should set and unset the error', function() {
|
||||
ctrl.$setValidity('required', false);
|
||||
expect(ctrl.$error.required).toBe(true);
|
||||
ctrl.$setValidity('someError', false);
|
||||
expectOneError();
|
||||
|
||||
ctrl.$setValidity('required', true);
|
||||
expect(ctrl.$error.required).toBe(false);
|
||||
ctrl.$setValidity('someError', undefined);
|
||||
expectOnePending();
|
||||
|
||||
ctrl.$setValidity('someError', true);
|
||||
expectOneSuccess();
|
||||
|
||||
ctrl.$setValidity('someError', null);
|
||||
expectCleared();
|
||||
});
|
||||
|
||||
|
||||
it('should set valid/invalid', function() {
|
||||
it('should set valid/invalid with multiple errors', function() {
|
||||
ctrl.$setValidity('first', false);
|
||||
expect(ctrl.$valid).toBe(false);
|
||||
expect(ctrl.$invalid).toBe(true);
|
||||
@@ -81,6 +107,14 @@ describe('NgModelController', function() {
|
||||
expect(ctrl.$valid).toBe(false);
|
||||
expect(ctrl.$invalid).toBe(true);
|
||||
|
||||
ctrl.$setValidity('third', undefined);
|
||||
expect(ctrl.$valid).toBe(undefined);
|
||||
expect(ctrl.$invalid).toBe(undefined);
|
||||
|
||||
ctrl.$setValidity('third', null);
|
||||
expect(ctrl.$valid).toBe(false);
|
||||
expect(ctrl.$invalid).toBe(true);
|
||||
|
||||
ctrl.$setValidity('second', true);
|
||||
expect(ctrl.$valid).toBe(false);
|
||||
expect(ctrl.$invalid).toBe(true);
|
||||
@@ -90,18 +124,6 @@ describe('NgModelController', function() {
|
||||
expect(ctrl.$invalid).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should emit $valid only when $invalid', function() {
|
||||
ctrl.$setValidity('error', true);
|
||||
expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('error', true, ctrl);
|
||||
parentFormCtrl.$setValidity.reset();
|
||||
|
||||
ctrl.$setValidity('error', false);
|
||||
expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('error', false, ctrl);
|
||||
parentFormCtrl.$setValidity.reset();
|
||||
ctrl.$setValidity('error', true);
|
||||
expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('error', true, ctrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPristine', function() {
|
||||
@@ -225,10 +247,10 @@ describe('NgModelController', function() {
|
||||
a = b = true;
|
||||
|
||||
ctrl.$setViewValue('3');
|
||||
expect(ctrl.$error).toEqual({ parse: false, high : true, even : true });
|
||||
expect(ctrl.$error).toEqual({ high : true, even : true });
|
||||
|
||||
ctrl.$setViewValue('10');
|
||||
expect(ctrl.$error).toEqual({ parse: false, high : false, even : false });
|
||||
expect(ctrl.$error).toEqual({});
|
||||
|
||||
a = undefined;
|
||||
|
||||
@@ -250,7 +272,7 @@ describe('NgModelController', function() {
|
||||
a = b = false; //not undefined
|
||||
|
||||
ctrl.$setViewValue('2');
|
||||
expect(ctrl.$error).toEqual({ parse: false, high : true, even : false });
|
||||
expect(ctrl.$error).toEqual({ high : true });
|
||||
});
|
||||
|
||||
it('should remove all non-parse-related CSS classes from the form when a parser fails',
|
||||
@@ -274,13 +296,15 @@ describe('NgModelController', function() {
|
||||
ctrl.$setViewValue('123');
|
||||
scope.$digest();
|
||||
|
||||
expect(element).not.toHaveClass('ng-valid-parse');
|
||||
expect(element).toHaveClass('ng-valid-parse');
|
||||
expect(element).not.toHaveClass('ng-invalid-parse');
|
||||
expect(element).toHaveClass('ng-invalid-always-fail');
|
||||
|
||||
parserIsFailing = true;
|
||||
ctrl.$setViewValue('12345');
|
||||
scope.$digest();
|
||||
|
||||
expect(element).not.toHaveClass('ng-valid-parse');
|
||||
expect(element).toHaveClass('ng-invalid-parse');
|
||||
expect(element).not.toHaveClass('ng-invalid-always-fail');
|
||||
|
||||
@@ -602,9 +626,7 @@ describe('NgModelController', function() {
|
||||
}));
|
||||
|
||||
it('should clear and ignore all pending promises when the input values changes', inject(function($q) {
|
||||
var isPending = false;
|
||||
ctrl.$validators.sync = function(value) {
|
||||
isPending = isObject(ctrl.$pending);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -616,14 +638,14 @@ describe('NgModelController', function() {
|
||||
};
|
||||
|
||||
scope.$apply('value = "123"');
|
||||
expect(isPending).toBe(false);
|
||||
expect(ctrl.$pending).toEqual({async: true});
|
||||
expect(ctrl.$valid).toBe(undefined);
|
||||
expect(ctrl.$invalid).toBe(undefined);
|
||||
expect(defers.length).toBe(1);
|
||||
expect(isObject(ctrl.$pending)).toBe(true);
|
||||
|
||||
scope.$apply('value = "456"');
|
||||
expect(isPending).toBe(false);
|
||||
expect(ctrl.$pending).toEqual({async: true});
|
||||
expect(ctrl.$valid).toBe(undefined);
|
||||
expect(ctrl.$invalid).toBe(undefined);
|
||||
expect(defers.length).toBe(2);
|
||||
@@ -734,6 +756,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;
|
||||
|
||||
it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty, ng-untouched, ng-touched)',
|
||||
inject(function($compile, $rootScope, $sniffer) {
|
||||
@@ -3443,7 +3466,7 @@ describe('input', function() {
|
||||
|
||||
expect(scope.email).toBe('vojta@google.com');
|
||||
expect(inputElm).toBeValid();
|
||||
expect(widget.$error.email).toBe(false);
|
||||
expect(widget.$error.email).toBeFalsy();
|
||||
|
||||
changeInputValueTo('invalid@');
|
||||
expect(scope.email).toBeUndefined();
|
||||
@@ -3477,7 +3500,7 @@ describe('input', function() {
|
||||
changeInputValueTo('http://www.something.com');
|
||||
expect(scope.url).toBe('http://www.something.com');
|
||||
expect(inputElm).toBeValid();
|
||||
expect(widget.$error.url).toBe(false);
|
||||
expect(widget.$error.url).toBeFalsy();
|
||||
|
||||
changeInputValueTo('invalid.com');
|
||||
expect(scope.url).toBeUndefined();
|
||||
@@ -4094,8 +4117,7 @@ describe('NgModel animations', function() {
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'removeClass', 'ng-valid-required');
|
||||
assertValidAnimation(animations[3], 'addClass', 'ng-invalid-required');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when valid', inject(function($animate) {
|
||||
@@ -4106,10 +4128,10 @@ describe('NgModel animations', function() {
|
||||
model.$setValidity('required', true);
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-valid');
|
||||
assertValidAnimation(animations[2], 'removeClass', 'ng-invalid-required');
|
||||
assertValidAnimation(animations[3], 'addClass', 'ng-valid-required');
|
||||
assertValidAnimation(animations[0], 'addClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-valid-required');
|
||||
assertValidAnimation(animations[3], 'removeClass', 'ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when dirty', inject(function($animate) {
|
||||
@@ -4150,16 +4172,15 @@ describe('NgModel animations', function() {
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'removeClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation(animations[3], 'addClass', 'ng-invalid-custom-error');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-invalid-custom-error');
|
||||
|
||||
$animate.queue = [];
|
||||
model.$setValidity('custom-error', true);
|
||||
|
||||
animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-valid');
|
||||
assertValidAnimation(animations[2], 'removeClass', 'ng-invalid-custom-error');
|
||||
assertValidAnimation(animations[3], 'addClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation(animations[0], 'addClass', 'ng-valid');
|
||||
assertValidAnimation(animations[1], 'removeClass', 'ng-invalid');
|
||||
assertValidAnimation(animations[2], 'addClass', 'ng-valid-custom-error');
|
||||
assertValidAnimation(animations[3], 'removeClass', 'ng-invalid-custom-error');
|
||||
}));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user