diff --git a/src/.jshintrc b/src/.jshintrc
index f32caa451ed6..5ca02f406eab 100644
--- a/src/.jshintrc
+++ b/src/.jshintrc
@@ -157,6 +157,9 @@
"INVALID_CLASS": false,
"PRISTINE_CLASS": false,
"DIRTY_CLASS": false,
+ "WORKING_CLASS": false,
+ "IDLE_CLASS": false,
+ "VALIDATING_CLASS": false,
/* ng/directive/form.js */
"nullFormCtrl": false
diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js
index 06ffad868d0c..aae421940b46 100644
--- a/src/ng/directive/form.js
+++ b/src/ng/directive/form.js
@@ -6,7 +6,11 @@ var nullFormCtrl = {
$removeControl: noop,
$setValidity: noop,
$setDirty: noop,
- $setPristine: noop
+ $setPristine: noop,
+ $setWorking: noop,
+ $setIdle: noop,
+ $setValidating: noop,
+ $clearValidating: noop
};
/**
@@ -51,7 +55,9 @@ function FormController(element, attrs) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
+ validatingCount = 0, //used to easily determine if we are validating
errors = form.$error = {},
+ pendingValidations = form.$pendingValidations = {},
controls = [];
// init state
@@ -60,11 +66,15 @@ function FormController(element, attrs) {
form.$pristine = true;
form.$valid = true;
form.$invalid = false;
+ form.$working = false;
+ form.$idle = true;
+ form.$validating = false;
parentForm.$addControl(form);
// Setup initial state of the control
element.addClass(PRISTINE_CLASS);
+ element.addClass(IDLE_CLASS);
toggleValidCss(true);
// convenience method for easy toggling of classes
@@ -94,7 +104,7 @@ function FormController(element, attrs) {
if (control.$name) {
form[control.$name] = control;
}
- };
+ };
/**
* @ngdoc function
@@ -113,9 +123,12 @@ function FormController(element, attrs) {
forEach(errors, function(queue, validationToken) {
form.$setValidity(validationToken, true, control);
});
+ forEach(pendingValidations, function (queue, validationToken) {
+ form.$clearValidating(validationToken, control);
+ });
arrayRemove(controls, control);
- };
+};
/**
* @ngdoc function
@@ -205,7 +218,91 @@ function FormController(element, attrs) {
forEach(controls, function(control) {
control.$setPristine();
});
- };
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$setWorking
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Sets the form to the working state
+ *
+ */
+ form.$setWorking = function () {
+ element.removeClass(IDLE_CLASS).addClass(WORKING_CLASS);
+ form.$working = true;
+ form.$idle = false;
+ parentForm.$setWorking();
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$setIdle
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Sets the form to the idle state
+ *
+ */
+ form.$setIdle = function () {
+ element.removeClass(WORKING_CLASS).addClass(IDLE_CLASS);
+ form.$working = false;
+ form.$idle = true;
+ parentForm.$setIdle();
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$setValidating
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Sets the form to the validating state
+ *
+ */
+ form.$setValidating = function (validationToken, control) {
+ var queue = pendingValidations[validationToken];
+
+ if (!validatingCount) {
+ element.addClass(VALIDATING_CLASS);
+ }
+ if (queue) {
+ if (includes(queue, control)) return;
+ } else {
+ pendingValidations[validationToken] = queue = [];
+ validatingCount++;
+ parentForm.$setValidating(validationToken, form);
+ }
+ queue.push(control);
+ form.$validating = true;
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$clearValidating
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Clears the form from the validating state
+ *
+ */
+ form.$clearValidating = function (validationToken, control) {
+ var queue = pendingValidations[validationToken];
+
+ if (queue) {
+ arrayRemove(queue, control);
+ if (!queue.length) {
+ validatingCount--;
+ if (!validatingCount) {
+ element.removeClass(VALIDATING_CLASS);
+ form.$validating = false;
+ }
+ pendingValidations[validationToken] = false;
+ parentForm.$clearValidating(validationToken, form);
+ }
+ }
+ };
}
@@ -253,6 +350,9 @@ function FormController(element, attrs) {
* - `ng-invalid` is set if the form is invalid.
* - `ng-pristine` is set if the form is pristine.
* - `ng-dirty` is set if the form is dirty.
+ * - `ng-working` is set if the form is working.
+ * - `ng-idle` is set if the form is idle.
+ * - `ng-validating` is set if the form is validating.
*
*
* # Submitting a form and preventing the default action
@@ -319,61 +419,61 @@ function FormController(element, attrs) {
*/
-var formDirectiveFactory = function(isNgForm) {
- return ['$timeout', function($timeout) {
- var formDirective = {
- name: 'form',
- restrict: isNgForm ? 'EAC' : 'E',
- controller: FormController,
- compile: function() {
- return {
- pre: function(scope, formElement, attr, controller) {
- if (!attr.action) {
- // we can't use jq events because if a form is destroyed during submission the default
- // action is not prevented. see #1238
- //
- // IE 9 is not affected because it doesn't fire a submit event and try to do a full
- // page reload if the form was destroyed by submission of the form via a click handler
- // on a button in the form. Looks like an IE9 specific bug.
- var preventDefaultListener = function(event) {
- event.preventDefault
+var formDirectiveFactory = function (isNgForm) {
+ return ['$timeout', function ($timeout) {
+ var formDirective = {
+ name: 'form',
+ restrict: isNgForm ? 'EAC' : 'E',
+ controller: FormController,
+ compile: function () {
+ return {
+ pre: function (scope, formElement, attr, controller) {
+ if (!attr.action) {
+ // we can't use jq events because if a form is destroyed during submission the default
+ // action is not prevented. see #1238
+ //
+ // IE 9 is not affected because it doesn't fire a submit event and try to do a full
+ // page reload if the form was destroyed by submission of the form via a click handler
+ // on a button in the form. Looks like an IE9 specific bug.
+ var preventDefaultListener = function (event) {
+ event.preventDefault
? event.preventDefault()
: event.returnValue = false; // IE
- };
+ };
- addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
+ addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
- // unregister the preventDefault listener so that we don't not leak memory but in a
- // way that will achieve the prevention of the default action.
- formElement.on('$destroy', function() {
- $timeout(function() {
- removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
- }, 0, false);
- });
- }
+ // unregister the preventDefault listener so that we don't not leak memory but in a
+ // way that will achieve the prevention of the default action.
+ formElement.on('$destroy', function () {
+ $timeout(function () {
+ removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
+ }, 0, false);
+ });
+ }
- var parentFormCtrl = formElement.parent().controller('form'),
+ var parentFormCtrl = formElement.parent().controller('form'),
alias = attr.name || attr.ngForm;
- if (alias) {
- setter(scope, alias, controller, alias);
- }
- if (parentFormCtrl) {
- formElement.on('$destroy', function() {
- parentFormCtrl.$removeControl(controller);
- if (alias) {
- setter(scope, alias, undefined, alias);
- }
- extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
- });
+ if (alias) {
+ setter(scope, alias, controller, alias);
+ }
+ if (parentFormCtrl) {
+ formElement.on('$destroy', function () {
+ parentFormCtrl.$removeControl(controller);
+ if (alias) {
+ setter(scope, alias, undefined, alias);
+ }
+ extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
+ });
+ }
+ }
+ };
}
- }
};
- }
- };
- return formDirective;
- }];
+ return formDirective;
+ } ];
};
var formDirective = formDirectiveFactory();
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 53a8ddd4d70a..c947dd96d5d4 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -5,7 +5,10 @@
-VALID_CLASS,
-INVALID_CLASS,
-PRISTINE_CLASS,
- -DIRTY_CLASS
+ -DIRTY_CLASS,
+ -WORKING_CLASS,
+ -IDLE_CLASS,
+ -VALIDATING_CLASS
*/
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
@@ -795,7 +798,10 @@ var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
var VALID_CLASS = 'ng-valid',
INVALID_CLASS = 'ng-invalid',
PRISTINE_CLASS = 'ng-pristine',
- DIRTY_CLASS = 'ng-dirty';
+ DIRTY_CLASS = 'ng-dirty',
+ WORKING_CLASS = 'ng-working',
+ IDLE_CLASS = 'ng-idle',
+ VALIDATING_CLASS = 'ng-validating';
/**
* @ngdoc object
@@ -923,210 +929,256 @@ var VALID_CLASS = 'ng-valid',
*
*/
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
- function($scope, $exceptionHandler, $attr, $element, $parse) {
- this.$viewValue = Number.NaN;
- this.$modelValue = Number.NaN;
- this.$parsers = [];
- this.$formatters = [];
- this.$viewChangeListeners = [];
- this.$pristine = true;
- this.$dirty = false;
- this.$valid = true;
- this.$invalid = false;
- this.$name = $attr.name;
-
- var ngModelGet = $parse($attr.ngModel),
+ function ($scope, $exceptionHandler, $attr, $element, $parse) {
+ this.$viewValue = Number.NaN;
+ this.$modelValue = Number.NaN;
+ this.$parsers = [];
+ this.$formatters = [];
+ this.$viewChangeListeners = [];
+ this.$pristine = true;
+ this.$dirty = false;
+ this.$valid = true;
+ this.$invalid = false;
+ this.$validating = false;
+ this.$name = $attr.name;
+
+ var ngModelGet = $parse($attr.ngModel),
ngModelSet = ngModelGet.assign;
- if (!ngModelSet) {
- throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
+ if (!ngModelSet) {
+ throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
$attr.ngModel, startingTag($element));
- }
-
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$render
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * Called when the view needs to be updated. It is expected that the user of the ng-model
- * directive will implement this method.
- */
- this.$render = noop;
+ }
- /**
- * @ngdoc function
- * @name { ng.directive:ngModel.NgModelController#$isEmpty
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * This is called when we need to determine if the value of the input is empty.
- *
- * For instance, the required directive does this to work out if the input has data or not.
- * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
- *
- * You can override this for input directives whose concept of being empty is different to the
- * default. The `checkboxInputType` directive does this because in its case a value of `false`
- * implies empty.
- */
- this.$isEmpty = function(value) {
- return isUndefined(value) || value === '' || value === null || value !== value;
- };
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$render
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Called when the view needs to be updated. It is expected that the user of the ng-model
+ * directive will implement this method.
+ */
+ this.$render = noop;
+
+ /**
+ * @ngdoc function
+ * @name { ng.directive:ngModel.NgModelController#$isEmpty
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * This is called when we need to determine if the value of the input is empty.
+ *
+ * For instance, the required directive does this to work out if the input has data or not.
+ * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
+ *
+ * You can override this for input directives whose concept of being empty is different to the
+ * default. The `checkboxInputType` directive does this because in its case a value of `false`
+ * implies empty.
+ */
+ this.$isEmpty = function (value) {
+ return isUndefined(value) || value === '' || value === null || value !== value;
+ };
- var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
+ var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
$error = this.$error = {}; // keep invalid keys here
- // Setup initial state of the control
- $element.addClass(PRISTINE_CLASS);
- toggleValidCss(true);
+ // 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, '-') : '';
- $element.
+ // convenience method for easy toggling of classes
+ function toggleValidCss(isValid, validationErrorKey) {
+ validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
+ $element.
removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
- }
+ }
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$setValidity
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @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).
- *
- * This method should be called by validators - i.e. the parser or formatter functions.
- *
- * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
- * to `$error[validationErrorKey]=isValid` 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).
- */
- this.$setValidity = function(validationErrorKey, isValid) {
- // Purposeful use of ! here to cast isValid to boolean in case it is undefined
- // jshint -W018
- if ($error[validationErrorKey] === !isValid) return;
- // jshint +W018
-
- if (isValid) {
- if ($error[validationErrorKey]) invalidCount--;
- if (!invalidCount) {
- toggleValidCss(true);
- this.$valid = true;
- this.$invalid = false;
- }
- } else {
- toggleValidCss(false);
- this.$invalid = true;
- this.$valid = false;
- invalidCount++;
- }
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$setValidity
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @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).
+ *
+ * This method should be called by validators - i.e. the parser or formatter functions.
+ *
+ * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
+ * to `$error[validationErrorKey]=isValid` 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).
+ */
+ this.$setValidity = function (validationErrorKey, isValid) {
+ // Purposeful use of ! here to cast isValid to boolean in case it is undefined
+ // jshint -W018
+ if ($error[validationErrorKey] === !isValid) return;
+ // jshint +W018
+
+ if (isValid) {
+ if ($error[validationErrorKey]) invalidCount--;
+ if (!invalidCount) {
+ toggleValidCss(true);
+ this.$valid = true;
+ this.$invalid = false;
+ }
+ } else {
+ toggleValidCss(false);
+ this.$invalid = true;
+ this.$valid = false;
+ invalidCount++;
+ }
- $error[validationErrorKey] = !isValid;
- toggleValidCss(isValid, validationErrorKey);
+ $error[validationErrorKey] = !isValid;
+ toggleValidCss(isValid, validationErrorKey);
- parentForm.$setValidity(validationErrorKey, isValid, this);
- };
+ parentForm.$setValidity(validationErrorKey, isValid, this);
+ };
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$setPristine
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * Sets the control to its pristine state.
- *
- * This method can be called to remove the 'ng-dirty' class and set the control to its pristine
- * state (ng-pristine class).
- */
- this.$setPristine = function () {
- this.$dirty = false;
- this.$pristine = true;
- $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
- };
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$setValidating
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Change the validating state, and notifies the form when the control changes to the validating state.
+ *
+ * This method should be called by validators that perform async validations
+ *
+ * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
+ * to `$error[validationErrorKey]=isValid` 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}}` .
+ */
+ this.$setValidating = function (validationErrorKey) {
+ $element.addClass(VALIDATING_CLASS);
+ this.$validating = true;
+ parentForm.$setValidating(validationErrorKey, this);
+ };
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$setViewValue
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * Update the view value.
- *
- * This method should be called when the view value changes, typically from within a DOM event handler.
- * For example {@link ng.directive:input input} and
- * {@link ng.directive:select select} directives call it.
- *
- * It will update the $viewValue, then pass this value through each of the functions in `$parsers`,
- * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to
- * `$modelValue` and the **expression** specified in the `ng-model` attribute.
- *
- * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
- *
- * Note that calling this function does not trigger a `$digest`.
- *
- * @param {string} value Value from the view.
- */
- this.$setViewValue = function(value) {
- this.$viewValue = value;
-
- // change to dirty
- if (this.$pristine) {
- this.$dirty = true;
- this.$pristine = false;
- $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
- parentForm.$setDirty();
- }
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$clearValidating
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Clears the validating state, and notifies the form when the control changes from the validating state.
+ *
+ * This method should be called by validators that perform async validations
+ *
+ * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
+ * to `$error[validationErrorKey]=isValid` 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}}` .
+ */
+ this.$clearValidating = function (validationErrorKey) {
+ $element.removeClass(VALIDATING_CLASS);
+ this.$validating = false;
+ parentForm.$clearValidating(validationErrorKey, this);
+ };
- forEach(this.$parsers, function(fn) {
- value = fn(value);
- });
- if (this.$modelValue !== value) {
- this.$modelValue = value;
- ngModelSet($scope, value);
- forEach(this.$viewChangeListeners, function(listener) {
- try {
- listener();
- } catch(e) {
- $exceptionHandler(e);
- }
- });
- }
- };
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$setPristine
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Sets the control to its pristine state.
+ *
+ * This method can be called to remove the 'ng-dirty' class and set the control to its pristine
+ * state (ng-pristine class).
+ */
+ this.$setPristine = function () {
+ this.$dirty = false;
+ this.$pristine = true;
+ $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$setViewValue
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Update the view value.
+ *
+ * This method should be called when the view value changes, typically from within a DOM event handler.
+ * For example {@link ng.directive:input input} and
+ * {@link ng.directive:select select} directives call it.
+ *
+ * It will update the $viewValue, then pass this value through each of the functions in `$parsers`,
+ * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to
+ * `$modelValue` and the **expression** specified in the `ng-model` attribute.
+ *
+ * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
+ *
+ * Note that calling this function does not trigger a `$digest`.
+ *
+ * @param {string} value Value from the view.
+ */
+ this.$setViewValue = function (value) {
+ this.$viewValue = value;
+
+ // change to dirty
+ if (this.$pristine) {
+ this.$dirty = true;
+ this.$pristine = false;
+ $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ parentForm.$setDirty();
+ }
+
+ forEach(this.$parsers, function (fn) {
+ value = fn(value);
+ });
+
+ if (this.$modelValue !== value) {
+ this.$modelValue = value;
+ ngModelSet($scope, value);
+ forEach(this.$viewChangeListeners, function (listener) {
+ try {
+ listener();
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ }
+ };
- // model -> value
- var ctrl = this;
+ // model -> value
+ var ctrl = this;
- $scope.$watch(function ngModelWatch() {
- var value = ngModelGet($scope);
+ $scope.$watch(function ngModelWatch() {
+ var value = ngModelGet($scope);
- // if scope model value and ngModel value are out of sync
- if (ctrl.$modelValue !== value) {
+ // if scope model value and ngModel value are out of sync
+ if (ctrl.$modelValue !== value) {
- var formatters = ctrl.$formatters,
+ var formatters = ctrl.$formatters,
idx = formatters.length;
- ctrl.$modelValue = value;
- while(idx--) {
- value = formatters[idx](value);
- }
+ ctrl.$modelValue = value;
+ while (idx--) {
+ value = formatters[idx](value);
+ }
- if (ctrl.$viewValue !== value) {
- ctrl.$viewValue = value;
- ctrl.$render();
- }
- }
+ if (ctrl.$viewValue !== value) {
+ ctrl.$viewValue = value;
+ ctrl.$render();
+ }
+ }
- return value;
- });
-}];
+ return value;
+ });
+ } ];
/**
diff --git a/test/helpers/matchers.js b/test/helpers/matchers.js
index c5d7d6cfd3c6..8684b6b81204 100644
--- a/test/helpers/matchers.js
+++ b/test/helpers/matchers.js
@@ -46,6 +46,8 @@ beforeEach(function() {
toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
+ toBeWorking: cssMatcher('ng-working', 'ng-idle'),
+ toBeIdle: cssMatcher('ng-idle', 'ng-working'),
toBeShown: function() {
this.message = valueFn(
"Expected element " + (this.isNot ? "": "not ") + "to have 'ng-hide' class");
diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js
index dde6f0a026c8..47956b5f528b 100644
--- a/test/ng/directive/formSpec.js
+++ b/test/ng/directive/formSpec.js
@@ -1,103 +1,105 @@
'use strict';
-describe('form', function() {
- var doc, control, scope, $compile, changeInputValue;
-
- beforeEach(module(function($compileProvider) {
- $compileProvider.directive('storeModelCtrl', function() {
- return {
- require: 'ngModel',
- link: function(scope, elm, attr, ctrl) {
- control = ctrl;
- }
- };
- });
- }));
+describe('form', function () {
+ var doc, control, scope, $compile, changeInputValue;
+
+ beforeEach(module(function ($compileProvider) {
+ $compileProvider.directive('storeModelCtrl', function () {
+ return {
+ require: 'ngModel',
+ link: function (scope, elm, attr, ctrl) {
+ control = ctrl;
+ }
+ };
+ });
+ }));
- beforeEach(inject(function($injector, $sniffer) {
- $compile = $injector.get('$compile');
- scope = $injector.get('$rootScope');
+ beforeEach(inject(function ($injector, $sniffer) {
+ $compile = $injector.get('$compile');
+ scope = $injector.get('$rootScope');
- changeInputValue = function(elm, value) {
- elm.val(value);
- browserTrigger(elm, $sniffer.hasEvent('input') ? 'input' : 'change');
- };
- }));
+ changeInputValue = function (elm, value) {
+ elm.val(value);
+ browserTrigger(elm, $sniffer.hasEvent('input') ? 'input' : 'change');
+ };
+ }));
- afterEach(function() {
- dealoc(doc);
- });
+ afterEach(function () {
+ dealoc(doc);
+ });
- it('should instantiate form and attach it to DOM', function() {
- doc = $compile('
')(scope);
- scope.inputPresent = true;
- scope.$digest();
-
- var form = scope.myForm;
- control.$setValidity('required', false);
- expect(form.alias).toBe(control);
- expect(form.$error.required).toEqual([control]);
-
- // remove nested control
- scope.inputPresent = false;
- scope.$apply();
-
- expect(form.$error.required).toBe(false);
- expect(form.alias).toBeUndefined();
- });
-
+ scope.inputPresent = true;
+ scope.$digest();
+
+ var form = scope.myForm;
+ control.$setValidity('required', false);
+ control.$setValidating('asyncValidation');
+ expect(form.alias).toBe(control);
+ expect(form.$error.required).toEqual([control]);
+ expect(form.$validating).toBe(true);
+
+ // remove nested control
+ scope.inputPresent = false;
+ scope.$apply();
+
+ expect(form.$validating).toBe(false);
+ expect(form.$error.required).toBe(false);
+ expect(form.alias).toBeUndefined();
+ });
- it('should use ngForm value as form name', function() {
- doc = $compile(
+ it('should use ngForm value as form name', function () {
+ doc = $compile(
'
' +
'' +
'
')(scope);
- expect(scope.myForm).toBeDefined();
- expect(scope.myForm.alias).toBeDefined();
- });
+ expect(scope.myForm).toBeDefined();
+ expect(scope.myForm.alias).toBeDefined();
+ });
- it('should use ngForm value as form name when nested inside form', function () {
- doc = $compile(
+ it('should use ngForm value as form name when nested inside form', function () {
+ doc = $compile(
'')(scope);
- expect(scope.myForm).toBeDefined();
- expect(scope.myForm.nestedForm).toBeDefined();
- expect(scope.myForm.nestedForm.alias).toBeDefined();
- });
+ expect(scope.myForm).toBeDefined();
+ expect(scope.myForm.nestedForm).toBeDefined();
+ expect(scope.myForm.nestedForm.alias).toBeDefined();
+ });
- it('should publish form to scope when name attr is defined', function() {
- doc = $compile('')(scope);
- expect(scope.myForm).toBeTruthy();
- expect(doc.data('$formController')).toBeTruthy();
- expect(doc.data('$formController')).toEqual(scope.myForm);
- });
+ it('should publish form to scope when name attr is defined', function () {
+ doc = $compile('')(scope);
+ expect(scope.myForm).toBeTruthy();
+ expect(doc.data('$formController')).toBeTruthy();
+ expect(doc.data('$formController')).toEqual(scope.myForm);
+ });
- it('should support expression in form name', function() {
- doc = $compile('')(scope);
+ it('should support expression in form name', function () {
+ doc = $compile('')(scope);
- expect(scope.obj).toBeDefined();
- expect(scope.obj.myForm).toBeTruthy();
- });
+ expect(scope.obj).toBeDefined();
+ expect(scope.obj.myForm).toBeTruthy();
+ });
- it('should support two forms on a single scope', function() {
- doc = $compile(
+ it('should support two forms on a single scope', function () {
+ doc = $compile(
'
' +
'
'
)(scope);
- scope.$apply();
+ scope.$apply();
- expect(scope.formA.$error.required.length).toBe(1);
- expect(scope.formA.$error.required).toEqual([scope.formA.firstName]);
- expect(scope.formB.$error.required.length).toBe(1);
- expect(scope.formB.$error.required).toEqual([scope.formB.lastName]);
+ expect(scope.formA.$error.required.length).toBe(1);
+ expect(scope.formA.$error.required).toEqual([scope.formA.firstName]);
+ expect(scope.formB.$error.required.length).toBe(1);
+ expect(scope.formB.$error.required).toEqual([scope.formB.lastName]);
- var inputA = doc.find('input').eq(0),
+ var inputA = doc.find('input').eq(0),
inputB = doc.find('input').eq(1);
- changeInputValue(inputA, 'val1');
- changeInputValue(inputB, 'val2');
+ changeInputValue(inputA, 'val1');
+ changeInputValue(inputB, 'val2');
- expect(scope.firstName).toBe('val1');
- expect(scope.lastName).toBe('val2');
+ 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).toBe(false);
+ expect(scope.formB.$error.required).toBe(false);
+ });
- it('should publish widgets', function() {
- doc = jqLite('');
- $compile(doc)(scope);
+ it('should publish widgets', function () {
+ doc = jqLite('');
+ $compile(doc)(scope);
- var widget = scope.form.w1;
- expect(widget).toBeDefined();
- expect(widget.$pristine).toBe(true);
- expect(widget.$dirty).toBe(false);
- expect(widget.$valid).toBe(true);
- expect(widget.$invalid).toBe(false);
- });
+ var widget = scope.form.w1;
+ expect(widget).toBeDefined();
+ expect(widget.$pristine).toBe(true);
+ expect(widget.$dirty).toBe(false);
+ expect(widget.$valid).toBe(true);
+ expect(widget.$invalid).toBe(false);
+ expect(widget.$validating).toBe(false);
+ });
- it('should throw an exception if an input has name="hasOwnProperty"', function() {
- doc = jqLite(
- '');
- expect(function() {
- $compile(doc)(scope);
- }).toThrowMinErr('ng', 'badname');
- });
+ expect(function () {
+ $compile(doc)(scope);
+ }).toThrowMinErr('ng', 'badname');
+ });
- describe('preventing default submission', function() {
+ describe('preventing default submission', function () {
- it('should prevent form submission', function() {
- var nextTurn = false,
+ it('should prevent form submission', function () {
+ var nextTurn = false,
submitted = false,
reloadPrevented;
- doc = jqLite('');
- var assertPreventDefaultListener = function(e) {
- reloadPrevented = e.defaultPrevented || (e.returnValue === false);
- };
+ var assertPreventDefaultListener = function (e) {
+ reloadPrevented = e.defaultPrevented || (e.returnValue === false);
+ };
- // native dom event listeners in IE8 fire in LIFO order so we have to register them
- // there in different order than in other browsers
- if (msie==8) addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener);
+ // native dom event listeners in IE8 fire in LIFO order so we have to register them
+ // there in different order than in other browsers
+ if (msie == 8) addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener);
- $compile(doc)(scope);
+ $compile(doc)(scope);
- scope.submitMe = function() {
- submitted = true;
- }
+ scope.submitMe = function () {
+ submitted = true;
+ }
- if (msie!=8) addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener);
+ if (msie != 8) addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener);
- browserTrigger(doc.find('input'));
+ browserTrigger(doc.find('input'));
- // let the browser process all events (and potentially reload the page)
- setTimeout(function() { nextTurn = true;});
+ // let the browser process all events (and potentially reload the page)
+ setTimeout(function () { nextTurn = true; });
- waitsFor(function() { return nextTurn; });
+ waitsFor(function () { return nextTurn; });
- runs(function() {
- expect(reloadPrevented).toBe(true);
- expect(submitted).toBe(true);
+ runs(function () {
+ expect(reloadPrevented).toBe(true);
+ expect(submitted).toBe(true);
- // prevent mem leak in test
- removeEventListenerFn(doc[0], 'submit', assertPreventDefaultListener);
- });
- });
+ // prevent mem leak in test
+ removeEventListenerFn(doc[0], 'submit', assertPreventDefaultListener);
+ });
+ });
- it('should prevent the default when the form is destroyed by a submission via a click event',
- inject(function($timeout) {
- doc = jqLite('
' +
+ it('should prevent the default when the form is destroyed by a submission via a click event',
+ inject(function ($timeout) {
+ doc = jqLite('
' +
'' +
'
');
- var form = doc.find('form'),
+ var form = doc.find('form'),
destroyed = false,
nextTurn = false,
submitted = false,
reloadPrevented;
- scope.destroy = function() {
- // yes, I know, scope methods should not do direct DOM manipulation, but I wanted to keep
- // this test small. Imagine that the destroy action will cause a model change (e.g.
- // $location change) that will cause some directive to destroy the dom (e.g. ngView+$route)
- doc.empty();
- destroyed = true;
- }
+ scope.destroy = function () {
+ // yes, I know, scope methods should not do direct DOM manipulation, but I wanted to keep
+ // this test small. Imagine that the destroy action will cause a model change (e.g.
+ // $location change) that will cause some directive to destroy the dom (e.g. ngView+$route)
+ doc.empty();
+ destroyed = true;
+ }
- scope.submitMe = function() {
- submitted = true;
- }
+ scope.submitMe = function () {
+ submitted = true;
+ }
- var assertPreventDefaultListener = function(e) {
- reloadPrevented = e.defaultPrevented || (e.returnValue === false);
- };
+ var assertPreventDefaultListener = function (e) {
+ reloadPrevented = e.defaultPrevented || (e.returnValue === false);
+ };
- // native dom event listeners in IE8 fire in LIFO order so we have to register them
- // there in different order than in other browsers
- if (msie == 8) addEventListenerFn(form[0], 'submit', assertPreventDefaultListener);
+ // native dom event listeners in IE8 fire in LIFO order so we have to register them
+ // there in different order than in other browsers
+ if (msie == 8) addEventListenerFn(form[0], 'submit', assertPreventDefaultListener);
- $compile(doc)(scope);
+ $compile(doc)(scope);
- if (msie != 8) addEventListenerFn(form[0], 'submit', assertPreventDefaultListener);
+ if (msie != 8) addEventListenerFn(form[0], 'submit', assertPreventDefaultListener);
- browserTrigger(doc.find('button'), 'click');
+ browserTrigger(doc.find('button'), 'click');
- // let the browser process all events (and potentially reload the page)
- setTimeout(function() { nextTurn = true;}, 100);
+ // let the browser process all events (and potentially reload the page)
+ setTimeout(function () { nextTurn = true; }, 100);
- waitsFor(function() { return nextTurn; });
+ waitsFor(function () { return nextTurn; });
- // I can't get IE8 to automatically trigger submit in this test, in production it does it
- // properly
- if (msie == 8) browserTrigger(form, 'submit');
+ // I can't get IE8 to automatically trigger submit in this test, in production it does it
+ // properly
+ if (msie == 8) browserTrigger(form, 'submit');
- runs(function() {
- expect(doc.html()).toBe('');
- expect(destroyed).toBe(true);
- expect(submitted).toBe(false); // this is known corner-case that is not currently handled
- // the issue is that the submit listener is destroyed before
- // the event propagates there. we can fix this if we see
- // the issue in the wild, I'm not going to bother to do it
- // now. (i)
+ runs(function () {
+ expect(doc.html()).toBe('');
+ expect(destroyed).toBe(true);
+ expect(submitted).toBe(false); // this is known corner-case that is not currently handled
+ // the issue is that the submit listener is destroyed before
+ // the event propagates there. we can fix this if we see
+ // the issue in the wild, I'm not going to bother to do it
+ // now. (i)
- // IE9 and IE10 are special and don't fire submit event when form was destroyed
- if (msie < 9) {
- expect(reloadPrevented).toBe(true);
- $timeout.flush();
- }
+ // IE9 and IE10 are special and don't fire submit event when form was destroyed
+ if (msie < 9) {
+ expect(reloadPrevented).toBe(true);
+ $timeout.flush();
+ }
- // prevent mem leak in test
- removeEventListenerFn(form[0], 'submit', assertPreventDefaultListener);
- });
- }));
+ // prevent mem leak in test
+ removeEventListenerFn(form[0], 'submit', assertPreventDefaultListener);
+ });
+ }));
- it('should NOT prevent form submission if action attribute present', function() {
- var callback = jasmine.createSpy('submit').andCallFake(function(event) {
- expect(event.isDefaultPrevented()).toBe(false);
- event.preventDefault();
- });
+ it('should NOT prevent form submission if action attribute present', function () {
+ var callback = jasmine.createSpy('submit').andCallFake(function (event) {
+ expect(event.isDefaultPrevented()).toBe(false);
+ event.preventDefault();
+ });
- doc = $compile('')(scope);
- doc.on('submit', callback);
+ doc = $compile('')(scope);
+ doc.on('submit', callback);
- browserTrigger(doc, 'submit');
- expect(callback).toHaveBeenCalledOnce();
+ browserTrigger(doc, 'submit');
+ expect(callback).toHaveBeenCalledOnce();
+ });
});
- });
- describe('nested forms', function() {
+ describe('nested forms', function () {
- it('should chain nested forms', function() {
- doc = jqLite(
+ it('should chain nested forms', function () {
+ doc = jqLite(
'' +
'' +
'' +
'' +
'' +
'');
- $compile(doc)(scope);
+ $compile(doc)(scope);
- var parent = scope.parent,
+ var parent = scope.parent,
child = scope.child,
inputA = child.inputA,
inputB = child.inputB;
- inputA.$setValidity('MyError', false);
- inputB.$setValidity('MyError', false);
- expect(parent.$error.MyError).toEqual([child]);
- expect(child.$error.MyError).toEqual([inputA, inputB]);
+ inputA.$setValidity('MyError', false);
+ inputB.$setValidity('MyError', false);
+ expect(parent.$error.MyError).toEqual([child]);
+ expect(child.$error.MyError).toEqual([inputA, inputB]);
- inputA.$setValidity('MyError', true);
- expect(parent.$error.MyError).toEqual([child]);
- expect(child.$error.MyError).toEqual([inputB]);
+ inputA.$setValidity('MyError', true);
+ expect(parent.$error.MyError).toEqual([child]);
+ expect(child.$error.MyError).toEqual([inputB]);
- inputB.$setValidity('MyError', true);
- expect(parent.$error.MyError).toBe(false);
- expect(child.$error.MyError).toBe(false);
+ inputB.$setValidity('MyError', true);
+ expect(parent.$error.MyError).toBe(false);
+ expect(child.$error.MyError).toBe(false);
- child.$setDirty();
- expect(parent.$dirty).toBeTruthy();
- });
+ child.$setDirty();
+ expect(parent.$dirty).toBeTruthy();
+
+ child.$setWorking();
+ expect(parent.$working).toBeTruthy();
+ expect(parent.$idle).toBe(false);
+
+ child.$setIdle();
+ expect(parent.$working).toBe(false);
+ expect(parent.$idle).toBeTruthy();
+
+ child.$setValidating();
+ expect(parent.$validating).toBeTruthy();
+
+ child.$clearValidating();
+ expect(parent.$validating).toBe(false);
+ });
- it('should deregister a child form when its DOM is removed', function() {
- doc = jqLite(
+ it('should deregister a child form when its DOM is removed', function () {
+ doc = jqLite(
'');
- $compile(doc)(scope);
- scope.$apply();
+ $compile(doc)(scope);
+ scope.$apply();
- var parent = scope.parent,
+ var parent = scope.parent,
child = scope.child;
- expect(parent).toBeDefined();
- expect(child).toBeDefined();
- expect(parent.$error.required).toEqual([child]);
- doc.children().remove(); //remove child
+ expect(parent).toBeDefined();
+ expect(child).toBeDefined();
+ expect(parent.$error.required).toEqual([child]);
+ doc.children().remove(); //remove child
- expect(parent.child).toBeUndefined();
- expect(scope.child).toBeUndefined();
- expect(parent.$error.required).toBe(false);
- });
+ expect(parent.child).toBeUndefined();
+ expect(scope.child).toBeUndefined();
+ expect(parent.$error.required).toBe(false);
+ });
- it('should deregister a child form whose name is an expression when its DOM is removed', function() {
- doc = jqLite(
+ it('should deregister a child form whose name is an expression when its DOM is removed', function () {
+ doc = jqLite(
'');
- $compile(doc)(scope);
- scope.$apply();
+ $compile(doc)(scope);
+ scope.$apply();
- var parent = scope.parent,
+ var parent = scope.parent,
child = scope.child.form;
- expect(parent).toBeDefined();
- expect(child).toBeDefined();
- expect(parent.$error.required).toEqual([child]);
- doc.children().remove(); //remove child
+ expect(parent).toBeDefined();
+ expect(child).toBeDefined();
+ expect(parent.$error.required).toEqual([child]);
+ doc.children().remove(); //remove child
- expect(parent.child).toBeUndefined();
- expect(scope.child.form).toBeUndefined();
- expect(parent.$error.required).toBe(false);
- });
+ expect(parent.child).toBeUndefined();
+ expect(scope.child.form).toBeUndefined();
+ expect(parent.$error.required).toBe(false);
+ });
- it('should deregister a input when it is removed from DOM', function() {
- doc = jqLite(
+ it('should deregister a input when it is removed from DOM', function () {
+ doc = jqLite(
'');
- $compile(doc)(scope);
- scope.inputPresent = true;
- scope.$apply();
+ $compile(doc)(scope);
+ scope.inputPresent = true;
+ scope.$apply();
- var parent = scope.parent,
+ var parent = scope.parent,
child = scope.child,
input = child.inputA;
- expect(parent).toBeDefined();
- expect(child).toBeDefined();
- expect(parent.$error.required).toEqual([child]);
- expect(child.$error.required).toEqual([input]);
- expect(doc.hasClass('ng-invalid')).toBe(true);
- expect(doc.hasClass('ng-invalid-required')).toBe(true);
- expect(doc.find('div').hasClass('ng-invalid')).toBe(true);
- expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true);
-
- //remove child input
- scope.inputPresent = false;
- scope.$apply();
-
- expect(parent.$error.required).toBe(false);
- expect(child.$error.required).toBe(false);
- expect(doc.hasClass('ng-valid')).toBe(true);
- expect(doc.hasClass('ng-valid-required')).toBe(true);
- expect(doc.find('div').hasClass('ng-valid')).toBe(true);
- expect(doc.find('div').hasClass('ng-valid-required')).toBe(true);
- });
-
-
- it('should chain nested forms in repeater', function() {
- doc = jqLite(
+ expect(parent).toBeDefined();
+ expect(child).toBeDefined();
+ expect(parent.$error.required).toEqual([child]);
+ expect(child.$error.required).toEqual([input]);
+ expect(doc.hasClass('ng-invalid')).toBe(true);
+ expect(doc.hasClass('ng-invalid-required')).toBe(true);
+ expect(doc.find('div').hasClass('ng-invalid')).toBe(true);
+ expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true);
+
+ //remove child input
+ scope.inputPresent = false;
+ scope.$apply();
+
+ expect(parent.$error.required).toBe(false);
+ expect(child.$error.required).toBe(false);
+ expect(doc.hasClass('ng-valid')).toBe(true);
+ expect(doc.hasClass('ng-valid-required')).toBe(true);
+ expect(doc.find('div').hasClass('ng-valid')).toBe(true);
+ expect(doc.find('div').hasClass('ng-valid-required')).toBe(true);
+ });
+
+
+ it('should chain nested forms in repeater', function () {
+ doc = jqLite(
'' +
'' +
'' +
'' +
'');
- $compile(doc)(scope);
+ $compile(doc)(scope);
- scope.$apply(function() {
- scope.forms = [1];
- });
+ scope.$apply(function () {
+ scope.forms = [1];
+ });
- var parent = scope.parent;
- var child = doc.find('input').scope().child;
- var input = child.text;
+ var parent = scope.parent;
+ var child = doc.find('input').scope().child;
+ var input = child.text;
- expect(parent).toBeDefined();
- expect(child).toBeDefined();
- expect(input).toBeDefined();
+ expect(parent).toBeDefined();
+ expect(child).toBeDefined();
+ expect(input).toBeDefined();
- input.$setValidity('myRule', false);
- expect(input.$error.myRule).toEqual(true);
- expect(child.$error.myRule).toEqual([input]);
- expect(parent.$error.myRule).toEqual([child]);
+ input.$setValidity('myRule', false);
+ expect(input.$error.myRule).toEqual(true);
+ expect(child.$error.myRule).toEqual([input]);
+ expect(parent.$error.myRule).toEqual([child]);
- input.$setValidity('myRule', true);
- expect(parent.$error.myRule).toBe(false);
- expect(child.$error.myRule).toBe(false);
- });
- })
+ input.$setValidity('myRule', true);
+ expect(parent.$error.myRule).toBe(false);
+ expect(child.$error.myRule).toBe(false);
+ });
+ })
- describe('validation', function() {
+ describe('validation', function () {
- beforeEach(function() {
- doc = $compile(
+ beforeEach(function () {
+ doc = $compile(
'')(scope);
- scope.$digest();
- });
+ scope.$digest();
+ });
- it('should have ng-valid/ng-invalid css class', function() {
- expect(doc).toBeValid();
-
- control.$setValidity('error', false);
- expect(doc).toBeInvalid();
- expect(doc.hasClass('ng-valid-error')).toBe(false);
- expect(doc.hasClass('ng-invalid-error')).toBe(true);
-
- control.$setValidity('another', false);
- expect(doc.hasClass('ng-valid-error')).toBe(false);
- expect(doc.hasClass('ng-invalid-error')).toBe(true);
- expect(doc.hasClass('ng-valid-another')).toBe(false);
- expect(doc.hasClass('ng-invalid-another')).toBe(true);
-
- control.$setValidity('error', true);
- expect(doc).toBeInvalid();
- expect(doc.hasClass('ng-valid-error')).toBe(true);
- expect(doc.hasClass('ng-invalid-error')).toBe(false);
- expect(doc.hasClass('ng-valid-another')).toBe(false);
- expect(doc.hasClass('ng-invalid-another')).toBe(true);
-
- control.$setValidity('another', true);
- expect(doc).toBeValid();
- expect(doc.hasClass('ng-valid-error')).toBe(true);
- expect(doc.hasClass('ng-invalid-error')).toBe(false);
- expect(doc.hasClass('ng-valid-another')).toBe(true);
- expect(doc.hasClass('ng-invalid-another')).toBe(false);
- });
+ it('should have ng-valid/ng-invalid css class', function () {
+ expect(doc).toBeValid();
+
+ control.$setValidity('error', false);
+ expect(doc).toBeInvalid();
+ expect(doc.hasClass('ng-valid-error')).toBe(false);
+ expect(doc.hasClass('ng-invalid-error')).toBe(true);
+
+ control.$setValidity('another', false);
+ expect(doc.hasClass('ng-valid-error')).toBe(false);
+ expect(doc.hasClass('ng-invalid-error')).toBe(true);
+ expect(doc.hasClass('ng-valid-another')).toBe(false);
+ expect(doc.hasClass('ng-invalid-another')).toBe(true);
+
+ control.$setValidity('error', true);
+ expect(doc).toBeInvalid();
+ expect(doc.hasClass('ng-valid-error')).toBe(true);
+ expect(doc.hasClass('ng-invalid-error')).toBe(false);
+ expect(doc.hasClass('ng-valid-another')).toBe(false);
+ expect(doc.hasClass('ng-invalid-another')).toBe(true);
+
+ control.$setValidity('another', true);
+ expect(doc).toBeValid();
+ expect(doc.hasClass('ng-valid-error')).toBe(true);
+ expect(doc.hasClass('ng-invalid-error')).toBe(false);
+ expect(doc.hasClass('ng-valid-another')).toBe(true);
+ expect(doc.hasClass('ng-invalid-another')).toBe(false);
+ control.$setValidating('async');
+ expect(doc.hasClass('ng-validating')).toBe(true);
+ control.$clearValidating('async');
+ expect(doc.hasClass('ng-validating')).toBe(false);
+ });
- it('should have ng-pristine/ng-dirty css class', function() {
- expect(doc).toBePristine();
- control.$setViewValue('');
- scope.$apply();
- expect(doc).toBeDirty();
+ it('should have ng-pristine/ng-dirty css class', function () {
+ expect(doc).toBePristine();
+
+ control.$setViewValue('');
+ scope.$apply();
+ expect(doc).toBeDirty();
+ });
});
- });
- describe('$setPristine', function() {
+ describe('$setPristine', function () {
- it('should reset pristine state of form and controls', function() {
+ it('should reset pristine state of form and controls', function () {
- doc = $compile(
+ doc = $compile(
'')(scope);
- scope.$digest();
+ scope.$digest();
- var form = doc,
+ var form = doc,
formCtrl = scope.testForm,
input1 = form.find('input').eq(0),
input1Ctrl = input1.controller('ngModel'),
input2 = form.find('input').eq(1),
input2Ctrl = input2.controller('ngModel');
- input1Ctrl.$setViewValue('xx');
- input2Ctrl.$setViewValue('yy');
- scope.$apply();
- expect(form).toBeDirty();
- expect(input1).toBeDirty();
- expect(input2).toBeDirty();
-
- formCtrl.$setPristine();
- expect(form).toBePristine();
- expect(formCtrl.$pristine).toBe(true);
- expect(formCtrl.$dirty).toBe(false);
- expect(input1).toBePristine();
- expect(input1Ctrl.$pristine).toBe(true);
- expect(input1Ctrl.$dirty).toBe(false);
- expect(input2).toBePristine();
- expect(input2Ctrl.$pristine).toBe(true);
- expect(input2Ctrl.$dirty).toBe(false);
- });
-
-
- it('should reset pristine state of anonymous form controls', function() {
-
- doc = $compile(
+ input1Ctrl.$setViewValue('xx');
+ input2Ctrl.$setViewValue('yy');
+ scope.$apply();
+ expect(form).toBeDirty();
+ expect(input1).toBeDirty();
+ expect(input2).toBeDirty();
+
+ formCtrl.$setPristine();
+ expect(form).toBePristine();
+ expect(formCtrl.$pristine).toBe(true);
+ expect(formCtrl.$dirty).toBe(false);
+ expect(input1).toBePristine();
+ expect(input1Ctrl.$pristine).toBe(true);
+ expect(input1Ctrl.$dirty).toBe(false);
+ expect(input2).toBePristine();
+ expect(input2Ctrl.$pristine).toBe(true);
+ expect(input2Ctrl.$dirty).toBe(false);
+ });
+
+
+ it('should reset pristine state of anonymous form controls', function () {
+
+ doc = $compile(
'')(scope);
- scope.$digest();
+ scope.$digest();
- var form = doc,
+ var form = doc,
formCtrl = scope.testForm,
input = form.find('input').eq(0),
inputCtrl = input.controller('ngModel');
- inputCtrl.$setViewValue('xx');
- scope.$apply();
- expect(form).toBeDirty();
- expect(input).toBeDirty();
-
- formCtrl.$setPristine();
- expect(form).toBePristine();
- expect(formCtrl.$pristine).toBe(true);
- expect(formCtrl.$dirty).toBe(false);
- expect(input).toBePristine();
- expect(inputCtrl.$pristine).toBe(true);
- expect(inputCtrl.$dirty).toBe(false);
- });
+ inputCtrl.$setViewValue('xx');
+ scope.$apply();
+ expect(form).toBeDirty();
+ expect(input).toBeDirty();
+ formCtrl.$setPristine();
+ expect(form).toBePristine();
+ expect(formCtrl.$pristine).toBe(true);
+ expect(formCtrl.$dirty).toBe(false);
+ expect(input).toBePristine();
+ expect(inputCtrl.$pristine).toBe(true);
+ expect(inputCtrl.$dirty).toBe(false);
+ });
- it('should reset pristine state of nested forms', function() {
- doc = $compile(
+ it('should reset pristine state of nested forms', function () {
+
+ doc = $compile(
'')(scope);
- scope.$digest();
+ scope.$digest();
- var form = doc,
+ var form = doc,
formCtrl = scope.testForm,
nestedForm = form.find('div'),
nestedFormCtrl = nestedForm.controller('form'),
nestedInput = form.find('input').eq(0),
nestedInputCtrl = nestedInput.controller('ngModel');
- nestedInputCtrl.$setViewValue('xx');
- scope.$apply();
- expect(form).toBeDirty();
- expect(nestedForm).toBeDirty();
- expect(nestedInput).toBeDirty();
-
- formCtrl.$setPristine();
- expect(form).toBePristine();
- expect(formCtrl.$pristine).toBe(true);
- expect(formCtrl.$dirty).toBe(false);
- expect(nestedForm).toBePristine();
- expect(nestedFormCtrl.$pristine).toBe(true);
- expect(nestedFormCtrl.$dirty).toBe(false);
- expect(nestedInput).toBePristine();
- expect(nestedInputCtrl.$pristine).toBe(true);
- expect(nestedInputCtrl.$dirty).toBe(false);
+ nestedInputCtrl.$setViewValue('xx');
+ scope.$apply();
+ expect(form).toBeDirty();
+ expect(nestedForm).toBeDirty();
+ expect(nestedInput).toBeDirty();
+
+ formCtrl.$setPristine();
+ expect(form).toBePristine();
+ expect(formCtrl.$pristine).toBe(true);
+ expect(formCtrl.$dirty).toBe(false);
+ expect(nestedForm).toBePristine();
+ expect(nestedFormCtrl.$pristine).toBe(true);
+ expect(nestedFormCtrl.$dirty).toBe(false);
+ expect(nestedInput).toBePristine();
+ expect(nestedInputCtrl.$pristine).toBe(true);
+ expect(nestedInputCtrl.$dirty).toBe(false);
+ });
+ });
+
+ describe('$setWorking', function () {
+
+ it('should set form $working and $idle and associated css classes', function () {
+
+ doc = $compile(
+ '')(scope);
+
+ scope.$digest();
+
+ var form = doc,
+ formCtrl = scope.testForm;
+
+
+ formCtrl.$setWorking();
+ expect(form).toBeWorking();
+ expect(formCtrl.$working).toBe(true);
+ expect(formCtrl.$idle).toBe(false);
+ });
+ });
+
+ describe('$setIdle', function () {
+
+ it('should set form $working and $idle and associated css classes', function () {
+
+ doc = $compile(
+ '')(scope);
+
+ scope.$digest();
+
+ var form = doc,
+ formCtrl = scope.testForm;
+
+
+ formCtrl.$setIdle();
+ expect(form).toBeIdle();
+ expect(formCtrl.$idle).toBe(true);
+ expect(formCtrl.$working).toBe(false);
+ });
+ });
+
+ describe('$setValidating', function () {
+
+ it('should set $validating and ng-validating', function () {
+
+ doc = $compile(
+ '')(scope);
+
+ scope.$digest();
+
+ var form = doc,
+ formCtrl = scope.testForm;
+
+
+ formCtrl.$setValidating();
+ expect(formCtrl.$validating).toBe(true);
+ expect(doc.hasClass('ng-validating')).toBe(true);
+
+ });
+ });
+
+ describe('$clearValidating', function () {
+
+ it('should clear $validating and ng-validating', function () {
+
+ doc = $compile(
+ '')(scope);
+
+ scope.$digest();
+
+ var form = doc,
+ formCtrl = scope.testForm;
+
+
+ formCtrl.$clearValidating();
+ expect(formCtrl.$validating).toBe(false);
+ expect(doc.hasClass('ng-validating')).toBe(false);
+
+ });
});
- });
});