
package.src.directive.model.model.spec.js Maven / Gradle / Ivy
import { JQLite, dealoc } from "../../shared/jqlite/jqlite";
import { Angular } from "../../loader";
import { NgModelController } from "./model";
import { isDefined, valueFn, isObject } from "../../shared/utils";
import { browserTrigger } from "../../shared/test-utils";
describe("ngModel", () => {
let ctrl;
let scope;
let element;
let parentFormCtrl;
let $compile;
let injector;
let $rootScope;
let $q;
beforeEach(() => {
window.angular = new Angular();
window.angular
.module("myModule", ["ng"])
.decorator("$exceptionHandler", function () {
return (exception) => {
throw new Error(exception.message);
};
});
injector = window.angular.bootstrap(document.getElementById("dummy"), [
"myModule",
]);
$compile = injector.get("$compile");
const attrs = { name: "testAlias", ngModel: "value" };
parentFormCtrl = {
$$setPending: jasmine.createSpy("$$setPending"),
$setValidity: jasmine.createSpy("$setValidity"),
$setDirty: jasmine.createSpy("$setDirty"),
$$clearControlValidity: () => {},
};
element = JQLite("");
let $controller = injector.get("$controller");
scope = injector.get("$rootScope");
$rootScope = scope;
ctrl = $controller(NgModelController, {
$scope: scope,
$element: element.find("input"),
$attrs: attrs,
});
// Assign the mocked parentFormCtrl to the model controller
ctrl.$$parentForm = parentFormCtrl;
});
afterEach(() => {
dealoc(element);
});
describe("NgModelController", () => {
it("should init the properties", () => {
expect(ctrl.$untouched).toBe(true);
expect(ctrl.$touched).toBe(false);
expect(ctrl.$dirty).toBe(false);
expect(ctrl.$pristine).toBe(true);
expect(ctrl.$valid).toBe(true);
expect(ctrl.$invalid).toBe(false);
expect(ctrl.$viewValue).toBeDefined();
expect(ctrl.$modelValue).toBeDefined();
expect(ctrl.$formatters).toEqual([]);
expect(ctrl.$parsers).toEqual([]);
expect(ctrl.$name).toBe("testAlias");
});
describe("setValidity", () => {
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", () => {
expect(parentFormCtrl.$setValidity).not.toHaveBeenCalled();
ctrl.$setValidity("ERROR", false);
expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith(
"ERROR",
false,
ctrl,
);
});
it("should transition from states correctly", () => {
expectCleared();
ctrl.$setValidity("someError", false);
expectOneError();
ctrl.$setValidity("someError", undefined);
expectOnePending();
ctrl.$setValidity("someError", true);
expectOneSuccess();
ctrl.$setValidity("someError", null);
expectCleared();
});
it("should set valid/invalid with multiple errors", () => {
ctrl.$setValidity("first", false);
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
ctrl.$setValidity("second", false);
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
ctrl.$setValidity("third", undefined);
expect(ctrl.$valid).toBeUndefined();
expect(ctrl.$invalid).toBeUndefined();
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);
ctrl.$setValidity("first", true);
expect(ctrl.$valid).toBe(true);
expect(ctrl.$invalid).toBe(false);
});
});
describe("setPristine", () => {
it("should set control to its pristine state", () => {
ctrl.$setViewValue("edit");
expect(ctrl.$dirty).toBe(true);
expect(ctrl.$pristine).toBe(false);
ctrl.$setPristine();
expect(ctrl.$dirty).toBe(false);
expect(ctrl.$pristine).toBe(true);
});
});
describe("setDirty", () => {
it("should set control to its dirty state", () => {
expect(ctrl.$pristine).toBe(true);
expect(ctrl.$dirty).toBe(false);
ctrl.$setDirty();
expect(ctrl.$pristine).toBe(false);
expect(ctrl.$dirty).toBe(true);
});
it("should set parent form to its dirty state", () => {
ctrl.$setDirty();
expect(parentFormCtrl.$setDirty).toHaveBeenCalled();
});
});
describe("setUntouched", () => {
it("should set control to its untouched state", () => {
ctrl.$setTouched();
ctrl.$setUntouched();
expect(ctrl.$touched).toBe(false);
expect(ctrl.$untouched).toBe(true);
});
});
describe("setTouched", () => {
it("should set control to its touched state", () => {
ctrl.$setUntouched();
ctrl.$setTouched();
expect(ctrl.$touched).toBe(true);
expect(ctrl.$untouched).toBe(false);
});
});
describe("view -> model", () => {
it("should set the value to $viewValue", () => {
ctrl.$setViewValue("some-val");
expect(ctrl.$viewValue).toBe("some-val");
});
it("should pipeline all registered parsers and set result to $modelValue", () => {
const log = [];
ctrl.$parsers.push((value) => {
log.push(value);
return `${value}-a`;
});
ctrl.$parsers.push((value) => {
log.push(value);
return `${value}-b`;
});
ctrl.$setViewValue("init");
expect(log).toEqual(["init", "init-a"]);
expect(ctrl.$modelValue).toBe("init-a-b");
});
it("should fire viewChangeListeners when the value changes in the view (even if invalid)", () => {
const spy = jasmine.createSpy("viewChangeListener");
ctrl.$viewChangeListeners.push(spy);
ctrl.$setViewValue("val");
expect(spy).toHaveBeenCalled();
spy.calls.reset();
// invalid
ctrl.$parsers.push(() => undefined);
ctrl.$setViewValue("val2");
expect(spy).toHaveBeenCalled();
});
it("should reset the model when the view is invalid", () => {
ctrl.$setViewValue("aaaa");
expect(ctrl.$modelValue).toBe("aaaa");
// add a validator that will make any input invalid
ctrl.$parsers.push(() => undefined);
expect(ctrl.$modelValue).toBe("aaaa");
ctrl.$setViewValue("bbbb");
expect(ctrl.$modelValue).toBeUndefined();
});
it("should not reset the model when the view is invalid due to an external validator", () => {
ctrl.$setViewValue("aaaa");
expect(ctrl.$modelValue).toBe("aaaa");
ctrl.$setValidity("someExternalError", false);
ctrl.$setViewValue("bbbb");
expect(ctrl.$modelValue).toBe("bbbb");
});
it("should not reset the view when the view is invalid", () => {
// this test fails when the view changes the model and
// then the model listener in ngModel picks up the change and
// tries to update the view again.
// add a validator that will make any input invalid
ctrl.$parsers.push(() => undefined);
spyOn(ctrl, "$render");
// first digest
ctrl.$setViewValue("bbbb");
expect(ctrl.$modelValue).toBeUndefined();
expect(ctrl.$viewValue).toBe("bbbb");
expect(ctrl.$render).not.toHaveBeenCalled();
expect(scope.value).toBeUndefined();
// further digests
scope.$apply('value = "aaa"');
expect(ctrl.$viewValue).toBe("aaa");
ctrl.$render.calls.reset();
ctrl.$setViewValue("cccc");
expect(ctrl.$modelValue).toBeUndefined();
expect(ctrl.$viewValue).toBe("cccc");
expect(ctrl.$render).not.toHaveBeenCalled();
expect(scope.value).toBeUndefined();
});
it("should call parentForm.$setDirty only when pristine", () => {
ctrl.$setViewValue("");
expect(ctrl.$pristine).toBe(false);
expect(ctrl.$dirty).toBe(true);
expect(parentFormCtrl.$setDirty).toHaveBeenCalled();
parentFormCtrl.$setDirty.calls.reset();
ctrl.$setViewValue("");
expect(ctrl.$pristine).toBe(false);
expect(ctrl.$dirty).toBe(true);
expect(parentFormCtrl.$setDirty).not.toHaveBeenCalled();
});
it("should remove all other errors when any parser returns undefined", () => {
let a;
let b;
const val = function (val, x) {
return x ? val : x;
};
ctrl.$parsers.push((v) => val(v, a));
ctrl.$parsers.push((v) => val(v, b));
ctrl.$validators.high = function (value) {
return !isDefined(value) || value > 5;
};
ctrl.$validators.even = function (value) {
return !isDefined(value) || value % 2 === 0;
};
a = b = true;
ctrl.$setViewValue("3");
expect(ctrl.$error).toEqual({ high: true, even: true });
ctrl.$setViewValue("10");
expect(ctrl.$error).toEqual({});
a = undefined;
ctrl.$setViewValue("12");
expect(ctrl.$error).toEqual({ parse: true });
a = true;
b = undefined;
ctrl.$setViewValue("14");
expect(ctrl.$error).toEqual({ parse: true });
a = undefined;
b = undefined;
ctrl.$setViewValue("16");
expect(ctrl.$error).toEqual({ parse: true });
a = b = false; // not undefined
ctrl.$setViewValue("2");
expect(ctrl.$error).toEqual({ high: true });
});
it("should not remove external validators when a parser failed", () => {
ctrl.$parsers.push((v) => undefined);
ctrl.$setValidity("externalError", false);
ctrl.$setViewValue("someValue");
expect(ctrl.$error).toEqual({ externalError: true, parse: true });
});
it("should remove all non-parse-related CSS classes from the form when a parser fails", () => {
const element = $compile(
'",
)($rootScope);
const inputElm = element.find("input");
const ctrl = $rootScope.myForm.myControl;
let parserIsFailing = false;
ctrl.$parsers.push((value) => (parserIsFailing ? undefined : value));
ctrl.$validators.alwaysFail = function () {
return false;
};
ctrl.$setViewValue("123");
expect(element[0].classList.contains("ng-valid-parse")).toBeTrue();
expect(element[0].classList.contains("ng-invalid-parse")).toBeFalse();
expect(
element[0].classList.contains("ng-invalid-always-fail"),
).toBeTrue();
parserIsFailing = true;
ctrl.$setViewValue("12345");
expect(element[0].classList.contains("ng-valid-parse")).toBeFalse();
expect(element[0].classList.contains("ng-invalid-parse")).toBeTrue();
expect(
element[0].classList.contains("ng-invalid-always-fail"),
).toBeFalse();
dealoc(element);
});
it("should set the ng-invalid-parse and ng-valid-parse CSS class when parsers fail and pass", () => {
let pass = true;
ctrl.$parsers.push((v) => (pass ? v : undefined));
const input = element.find("input");
ctrl.$setViewValue("1");
expect(input[0].classList.contains("ng-valid-parse")).toBeTrue();
expect(input[0].classList.contains("ng-invalid-parse")).toBeFalse();
pass = undefined;
ctrl.$setViewValue("2");
expect(input[0].classList.contains("ng-valid-parse")).toBeFalse();
expect(input[0].classList.contains("ng-invalid-parse")).toBeTrue();
});
it("should update the model after all async validators resolve", () => {
let defer;
ctrl.$asyncValidators.promiseValidator = function (value) {
defer = $q.defer();
return defer.promise;
};
// set view value on first digest
ctrl.$setViewValue("b");
expect(ctrl.$modelValue).toBeUndefined();
expect(scope.value).toBeUndefined();
defer.resolve();
expect(ctrl.$modelValue).toBe("b");
expect(scope.value).toBe("b");
// set view value on further digests
ctrl.$setViewValue("c");
expect(ctrl.$modelValue).toBe("b");
expect(scope.value).toBe("b");
defer.resolve();
expect(ctrl.$modelValue).toBe("c");
expect(scope.value).toBe("c");
});
it("should not throw an error if the scope has been destroyed", () => {
scope.$destroy();
ctrl.$setViewValue("some-val");
expect(ctrl.$viewValue).toBe("some-val");
});
});
describe("model -> view", () => {
it("should set the value to $modelValue", () => {
scope.$apply("value = 10");
expect(ctrl.$modelValue).toBe(10);
});
it("should pipeline all registered formatters in reversed order and set result to $viewValue", () => {
const log = [];
ctrl.$formatters.unshift((value) => {
log.push(value);
return value + 2;
});
ctrl.$formatters.unshift((value) => {
log.push(value);
return `${value}`;
});
scope.$apply("value = 3");
expect(log).toEqual([3, 5]);
expect(ctrl.$viewValue).toBe("5");
});
it("should $render only if value changed", () => {
spyOn(ctrl, "$render");
scope.$apply("value = 3");
expect(ctrl.$render).toHaveBeenCalled();
ctrl.$render.calls.reset();
ctrl.$formatters.push(() => 3);
scope.$apply("value = 5");
expect(ctrl.$render).not.toHaveBeenCalled();
});
it("should clear the view even if invalid", () => {
spyOn(ctrl, "$render");
ctrl.$formatters.push(() => undefined);
scope.$apply("value = 5");
expect(ctrl.$render).toHaveBeenCalled();
});
it("should render immediately even if there are async validators", () => {
spyOn(ctrl, "$render");
ctrl.$asyncValidators.someValidator = function () {
return $q.defer().promise;
};
scope.$apply("value = 5");
expect(ctrl.$viewValue).toBe(5);
expect(ctrl.$render).toHaveBeenCalled();
});
it("should not rerender nor validate in case view value is not changed", () => {
ctrl.$formatters.push((value) => "nochange");
spyOn(ctrl, "$render");
ctrl.$validators.spyValidator = jasmine.createSpy("spyValidator");
scope.$apply('value = "first"');
scope.$apply('value = "second"');
expect(ctrl.$validators.spyValidator).toHaveBeenCalled();
expect(ctrl.$render).toHaveBeenCalled();
});
it("should always format the viewValue as a string for a blank input type when the value is present", () => {
const form = $compile(
'',
)($rootScope);
$rootScope.val = 123;
expect($rootScope.form.field.$viewValue).toBe("123");
$rootScope.val = null;
expect($rootScope.form.field.$viewValue).toBe(null);
dealoc(form);
});
it("should always format the viewValue as a string for a `text` input type when the value is present", () => {
const form = $compile(
'',
)($rootScope);
$rootScope.val = 123;
expect($rootScope.form.field.$viewValue).toBe("123");
$rootScope.val = null;
expect($rootScope.form.field.$viewValue).toBe(null);
dealoc(form);
});
it("should always format the viewValue as a string for an `email` input type when the value is present", () => {
const form = $compile(
'',
)($rootScope);
$rootScope.val = 123;
expect($rootScope.form.field.$viewValue).toBe("123");
$rootScope.val = null;
expect($rootScope.form.field.$viewValue).toBe(null);
dealoc(form);
});
it("should always format the viewValue as a string for a `url` input type when the value is present", () => {
const form = $compile(
'',
)($rootScope);
$rootScope.val = 123;
expect($rootScope.form.field.$viewValue).toBe("123");
$rootScope.val = null;
expect($rootScope.form.field.$viewValue).toBe(null);
dealoc(form);
});
it("should set NaN as the $modelValue when an asyncValidator is present", () => {
ctrl.$asyncValidators.test = function () {
return $q((resolve, reject) => {
resolve();
});
};
scope.$apply("value = 10");
expect(ctrl.$modelValue).toBe(10);
expect(() => {
scope.$apply(() => {
scope.value = NaN;
});
}).not.toThrow();
expect(ctrl.$modelValue).toBeNaN();
});
describe("$processModelValue", () => {
// Emulate setting the model on the scope
function setModelValue(ctrl, value) {
ctrl.$modelValue = ctrl.$$rawModelValue = value;
ctrl.$$parserValid = undefined;
}
it("should run the model -> view pipeline", () => {
const log = [];
const input = ctrl.$$element;
ctrl.$formatters.unshift((value) => {
log.push(value);
return value + 2;
});
ctrl.$formatters.unshift((value) => {
log.push(value);
return `${value}`;
});
spyOn(ctrl, "$render");
setModelValue(ctrl, 3);
expect(ctrl.$modelValue).toBe(3);
ctrl.$processModelValue();
expect(ctrl.$modelValue).toBe(3);
expect(log).toEqual([3, 5]);
expect(ctrl.$viewValue).toBe("5");
expect(ctrl.$render).toHaveBeenCalled();
});
// TODO
// it("should add the validation and empty-state classes", () => {
// const input = $compile(
// '',
// )($rootScope);
// ;
// spyOn($animate, "addClass");
// spyOn($animate, "removeClass");
// const ctrl = input.controller("ngModel");
// expect(input[0].classList.contains("ng-empty")).toBeTrue();
// expect(input[0].classList.contains("ng-valid")).toBeTrue();
// setModelValue(ctrl, 3);
// ctrl.$processModelValue();
// // $animate adds / removes classes in the $postUpdate, which
// // we cannot trigger with $digest, because that would set the model from the scope,
// // so we simply check if the functions have been called
// expect($animate.removeClass.calls.mostRecent().args[0][0]).toBe(
// input[0],
// );
// expect($animate.removeClass.calls.mostRecent().args[1]).toBe(
// "ng-empty",
// );
// expect($animate.addClass.calls.mostRecent().args[0][0]).toBe(
// input[0],
// );
// expect($animate.addClass.calls.mostRecent().args[1]).toBe(
// "ng-not-empty",
// );
// $animate.removeClass.calls.reset();
// $animate.addClass.calls.reset();
// setModelValue(ctrl, 35);
// ctrl.$processModelValue();
// expect($animate.addClass.calls.argsFor(1)[0][0]).toBe(input[0]);
// expect($animate.addClass.calls.argsFor(1)[1]).toBe("ng-invalid");
// expect($animate.addClass.calls.argsFor(2)[0][0]).toBe(input[0]);
// expect($animate.addClass.calls.argsFor(2)[1]).toBe(
// "ng-invalid-maxlength",
// );
// });
// this is analogue to $setViewValue
it("should run the model -> view pipeline even if the value has not changed", () => {
const log = [];
ctrl.$formatters.unshift((value) => {
log.push(value);
return value + 2;
});
ctrl.$formatters.unshift((value) => {
log.push(value);
return `${value}`;
});
spyOn(ctrl, "$render");
setModelValue(ctrl, 3);
ctrl.$processModelValue();
expect(ctrl.$modelValue).toBe(3);
expect(ctrl.$viewValue).toBe("5");
expect(log).toEqual([3, 5]);
expect(ctrl.$render).toHaveBeenCalled();
ctrl.$processModelValue();
expect(ctrl.$modelValue).toBe(3);
expect(ctrl.$viewValue).toBe("5");
expect(log).toEqual([3, 5, 3, 5]);
// $render() is not called if the viewValue didn't change
expect(ctrl.$render).toHaveBeenCalled();
});
});
});
describe("validation", () => {
describe("$validate", () => {
it("should perform validations when $validate() is called", () => {
scope.$apply('value = ""');
let validatorResult = false;
ctrl.$validators.someValidator = function (value) {
return validatorResult;
};
ctrl.$validate();
expect(ctrl.$valid).toBe(false);
validatorResult = true;
ctrl.$validate();
expect(ctrl.$valid).toBe(true);
});
it("should pass the last parsed modelValue to the validators", () => {
ctrl.$parsers.push((modelValue) => `${modelValue}def`);
ctrl.$setViewValue("abc");
ctrl.$validators.test = function (modelValue, viewValue) {
return true;
};
spyOn(ctrl.$validators, "test");
ctrl.$validate();
expect(ctrl.$validators.test).toHaveBeenCalledWith("abcdef", "abc");
});
it("should set the model to undefined when it becomes invalid", () => {
let valid = true;
ctrl.$validators.test = function (modelValue, viewValue) {
return valid;
};
scope.$apply('value = "abc"');
expect(scope.value).toBe("abc");
valid = false;
ctrl.$validate();
expect(scope.value).toBeUndefined();
});
it("should update the model when it becomes valid", () => {
let valid = true;
ctrl.$validators.test = function (modelValue, viewValue) {
return valid;
};
scope.$apply('value = "abc"');
expect(scope.value).toBe("abc");
valid = false;
ctrl.$validate();
expect(scope.value).toBeUndefined();
valid = true;
ctrl.$validate();
expect(scope.value).toBe("abc");
});
it("should not update the model when it is valid, but there is a parse error", () => {
ctrl.$parsers.push((modelValue) => undefined);
ctrl.$setViewValue("abc");
expect(ctrl.$error.parse).toBe(true);
expect(scope.value).toBeUndefined();
ctrl.$validators.test = function (modelValue, viewValue) {
return true;
};
ctrl.$validate();
expect(ctrl.$error).toEqual({ parse: true });
expect(scope.value).toBeUndefined();
});
it("should not set an invalid model to undefined when validity is the same", () => {
ctrl.$validators.test = function () {
return false;
};
scope.$apply('value = "invalid"');
expect(ctrl.$valid).toBe(false);
expect(scope.value).toBe("invalid");
ctrl.$validate();
expect(ctrl.$valid).toBe(false);
expect(scope.value).toBe("invalid");
});
it("should not change a model that has a formatter", () => {
ctrl.$validators.test = function () {
return true;
};
ctrl.$formatters.push((modelValue) => "xyz");
scope.$apply('value = "abc"');
expect(ctrl.$viewValue).toBe("xyz");
ctrl.$validate();
expect(scope.value).toBe("abc");
});
it("should not change a model that has a parser", () => {
ctrl.$validators.test = function () {
return true;
};
ctrl.$parsers.push((modelValue) => "xyz");
scope.$apply('value = "abc"');
ctrl.$validate();
expect(scope.value).toBe("abc");
});
});
describe("view -> model update", () => {
it("should always perform validations using the parsed model value", () => {
let captures;
ctrl.$validators.raw = function () {
captures = Array.prototype.slice.call(arguments);
return captures[0];
};
ctrl.$parsers.push((value) => value.toUpperCase());
ctrl.$setViewValue("my-value");
expect(captures).toEqual(["MY-VALUE", "my-value"]);
});
it("should always perform validations using the formatted view value", () => {
let captures;
ctrl.$validators.raw = function () {
captures = Array.prototype.slice.call(arguments);
return captures[0];
};
ctrl.$formatters.push((value) => `${value}...`);
scope.$apply('value = "matias"');
expect(captures).toEqual(["matias", "matias..."]);
});
it("should only perform validations if the view value is different", () => {
let count = 0;
ctrl.$validators.countMe = function () {
count++;
};
ctrl.$setViewValue("my-value");
expect(count).toBe(1);
ctrl.$setViewValue("my-value");
expect(count).toBe(1);
ctrl.$setViewValue("your-value");
expect(count).toBe(2);
});
});
it("should perform validations twice each time the model value changes within a digest", () => {
let count = 0;
ctrl.$validators.number = function (value) {
count++;
return /^\d+$/.test(value);
};
scope.$apply('value = ""');
expect(count).toBe(1);
scope.$apply("value = 1");
expect(count).toBe(2);
scope.$apply("value = 1");
expect(count).toBe(2);
scope.$apply('value = ""');
expect(count).toBe(3);
});
it("should only validate to true if all validations are true", () => {
ctrl.$modelValue = undefined;
ctrl.$validators.a = valueFn(true);
ctrl.$validators.b = valueFn(true);
ctrl.$validators.c = valueFn(false);
ctrl.$validate();
expect(ctrl.$valid).toBe(false);
ctrl.$validators.c = valueFn(true);
ctrl.$validate();
expect(ctrl.$valid).toBe(true);
});
it("should treat all responses as boolean for synchronous validators", () => {
const expectValid = function (value, expected) {
ctrl.$modelValue = undefined;
ctrl.$validators.a = valueFn(value);
ctrl.$validate();
expect(ctrl.$valid).toBe(expected);
};
// False tests
expectValid(false, false);
expectValid(undefined, false);
expectValid(null, false);
expectValid(0, false);
expectValid(NaN, false);
expectValid("", false);
// True tests
expectValid(true, true);
expectValid(1, true);
expectValid("0", true);
expectValid("false", true);
expectValid([], true);
expectValid({}, true);
});
it("should register invalid validations on the $error object", () => {
ctrl.$modelValue = undefined;
ctrl.$validators.unique = valueFn(false);
ctrl.$validators.tooLong = valueFn(false);
ctrl.$validators.notNumeric = valueFn(true);
ctrl.$validate();
expect(ctrl.$error.unique).toBe(true);
expect(ctrl.$error.tooLong).toBe(true);
expect(ctrl.$error.notNumeric).not.toBe(true);
});
it("should render a validator asynchronously when a promise is returned", () => {
let defer;
ctrl.$asyncValidators.promiseValidator = function (value) {
defer = $q.defer();
return defer.promise;
};
scope.$apply('value = ""');
expect(ctrl.$valid).toBeUndefined();
expect(ctrl.$invalid).toBeUndefined();
expect(ctrl.$pending.promiseValidator).toBe(true);
defer.resolve();
expect(ctrl.$valid).toBe(true);
expect(ctrl.$invalid).toBe(false);
expect(ctrl.$pending).toBeUndefined();
scope.$apply('value = "123"');
defer.reject();
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
expect(ctrl.$pending).toBeUndefined();
});
it("should throw an error when a promise is not returned for an asynchronous validator", () => {
ctrl.$asyncValidators.async = function (value) {
return true;
};
expect(() => {
scope.$apply('value = "123"');
}).toThrowError(/nopromise/);
});
it("should only run the async validators once all the sync validators have passed", () => {
const stages = {};
stages.sync = { status1: false, status2: false, count: 0 };
ctrl.$validators.syncValidator1 = function (modelValue, viewValue) {
stages.sync.count++;
return stages.sync.status1;
};
ctrl.$validators.syncValidator2 = function (modelValue, viewValue) {
stages.sync.count++;
return stages.sync.status2;
};
stages.async = { defer: null, count: 0 };
ctrl.$asyncValidators.asyncValidator = function (
modelValue,
viewValue,
) {
stages.async.defer = $q.defer();
stages.async.count++;
return stages.async.defer.promise;
};
scope.$apply('value = "123"');
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
expect(stages.sync.count).toBe(2);
expect(stages.async.count).toBe(0);
stages.sync.status1 = true;
scope.$apply('value = "456"');
expect(stages.sync.count).toBe(4);
expect(stages.async.count).toBe(0);
stages.sync.status2 = true;
scope.$apply('value = "789"');
expect(stages.sync.count).toBe(6);
expect(stages.async.count).toBe(1);
stages.async.defer.resolve();
scope.$apply();
expect(ctrl.$valid).toBe(true);
expect(ctrl.$invalid).toBe(false);
});
it("should ignore expired async validation promises once delivered", () => {
let defer;
let oldDefer;
let newDefer;
ctrl.$asyncValidators.async = function (value) {
defer = $q.defer();
return defer.promise;
};
scope.$apply('value = ""');
oldDefer = defer;
scope.$apply('value = "123"');
newDefer = defer;
newDefer.reject();
oldDefer.resolve();
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
expect(ctrl.$pending).toBeUndefined();
});
it("should clear and ignore all pending promises when the model value changes", () => {
ctrl.$validators.sync = function (value) {
return true;
};
const defers = [];
ctrl.$asyncValidators.async = function (value) {
const defer = $q.defer();
defers.push(defer);
return defer.promise;
};
scope.$apply('value = "123"');
expect(ctrl.$pending).toEqual({ async: true });
expect(ctrl.$valid).toBeUndefined();
expect(ctrl.$invalid).toBeUndefined();
expect(defers.length).toBe(1);
expect(isObject(ctrl.$pending)).toBe(true);
scope.$apply('value = "456"');
expect(ctrl.$pending).toEqual({ async: true });
expect(ctrl.$valid).toBeUndefined();
expect(ctrl.$invalid).toBeUndefined();
expect(defers.length).toBe(2);
expect(isObject(ctrl.$pending)).toBe(true);
defers[1].resolve();
expect(ctrl.$valid).toBe(true);
expect(ctrl.$invalid).toBe(false);
expect(isObject(ctrl.$pending)).toBe(false);
});
it("should clear and ignore all pending promises when a parser fails", () => {
let failParser = false;
ctrl.$parsers.push((value) => (failParser ? undefined : value));
let defer;
ctrl.$asyncValidators.async = function (value) {
defer = $q.defer();
return defer.promise;
};
ctrl.$setViewValue("x..y..z");
expect(ctrl.$valid).toBeUndefined();
expect(ctrl.$invalid).toBeUndefined();
failParser = true;
ctrl.$setViewValue("1..2..3");
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
expect(isObject(ctrl.$pending)).toBe(false);
defer.resolve();
expect(ctrl.$valid).toBe(false);
expect(ctrl.$invalid).toBe(true);
expect(isObject(ctrl.$pending)).toBe(false);
});
it("should clear all errors from async validators if a parser fails", () => {
let failParser = false;
ctrl.$parsers.push((value) => (failParser ? undefined : value));
ctrl.$asyncValidators.async = function (value) {
return $q.reject();
};
ctrl.$setViewValue("x..y..z");
expect(ctrl.$error).toEqual({ async: true });
failParser = true;
ctrl.$setViewValue("1..2..3");
expect(ctrl.$error).toEqual({ parse: true });
});
it("should clear all errors from async validators if a sync validator fails", () => {
let failValidator = false;
ctrl.$validators.sync = function (value) {
return !failValidator;
};
ctrl.$asyncValidators.async = function (value) {
return $q.reject();
};
ctrl.$setViewValue("x..y..z");
expect(ctrl.$error).toEqual({ async: true });
failValidator = true;
ctrl.$setViewValue("1..2..3");
expect(ctrl.$error).toEqual({ sync: true });
});
it("should be possible to extend Object prototype and still be able to do form validation", () => {
Object.prototype.someThing = function () {};
const element = $compile(
'",
)($rootScope);
const inputElm = element.find("input");
const formCtrl = $rootScope.myForm;
const usernameCtrl = formCtrl.username;
expect(usernameCtrl.$invalid).toBe(true);
expect(formCtrl.$invalid).toBe(true);
usernameCtrl.$setViewValue("valid-username");
expect(usernameCtrl.$invalid).toBe(false);
expect(formCtrl.$invalid).toBe(false);
delete Object.prototype.someThing;
dealoc(element);
});
it("should re-evaluate the form validity state once the asynchronous promise has been delivered", () => {
const element = $compile(
'",
)($rootScope);
const inputElm = element.find("input");
const formCtrl = $rootScope.myForm;
const usernameCtrl = formCtrl.username;
const ageCtrl = formCtrl.age;
let usernameDefer;
usernameCtrl.$asyncValidators.usernameAvailability = function () {
usernameDefer = $q.defer();
return usernameDefer.promise;
};
expect(usernameCtrl.$invalid).toBe(true);
expect(formCtrl.$invalid).toBe(true);
usernameCtrl.$setViewValue("valid-username");
expect(formCtrl.$pending.usernameAvailability).toBeTruthy();
expect(usernameCtrl.$invalid).toBeUndefined();
expect(formCtrl.$invalid).toBeUndefined();
usernameDefer.resolve();
expect(usernameCtrl.$invalid).toBe(false);
expect(formCtrl.$invalid).toBe(true);
ageCtrl.$setViewValue(22);
expect(usernameCtrl.$invalid).toBe(false);
expect(ageCtrl.$invalid).toBe(false);
expect(formCtrl.$invalid).toBe(false);
usernameCtrl.$setViewValue("valid");
expect(usernameCtrl.$invalid).toBe(true);
expect(ageCtrl.$invalid).toBe(false);
expect(formCtrl.$invalid).toBe(true);
usernameCtrl.$setViewValue("another-valid-username");
usernameDefer.resolve();
expect(usernameCtrl.$invalid).toBe(false);
expect(formCtrl.$invalid).toBe(false);
expect(formCtrl.$pending).toBeFalsy();
expect(ageCtrl.$invalid).toBe(false);
dealoc(element);
});
it("should always use the most recent $viewValue for validation", () => {
ctrl.$parsers.push((value) => {
if (value && value.substr(-1) === "b") {
value = "a";
ctrl.$setViewValue(value);
ctrl.$render();
}
return value;
});
ctrl.$validators.mock = function (modelValue) {
return true;
};
spyOn(ctrl.$validators, "mock").and.callThrough();
ctrl.$setViewValue("ab");
expect(ctrl.$validators.mock).toHaveBeenCalledWith("a", "a");
expect(ctrl.$validators.mock).toHaveBeenCalledTimes(2);
});
it("should validate even if the modelValue did not change", () => {
ctrl.$parsers.push((value) => {
if (value && value.substr(-1) === "b") {
value = "a";
}
return value;
});
ctrl.$validators.mock = function (modelValue) {
return true;
};
spyOn(ctrl.$validators, "mock").and.callThrough();
ctrl.$setViewValue("a");
expect(ctrl.$validators.mock).toHaveBeenCalledWith("a", "a");
expect(ctrl.$validators.mock).toHaveBeenCalledTimes(1);
ctrl.$setViewValue("ab");
expect(ctrl.$validators.mock).toHaveBeenCalledWith("a", "ab");
expect(ctrl.$validators.mock).toHaveBeenCalledTimes(2);
});
it("should validate correctly when $parser name equals $validator key", () => {
ctrl.$validators.parserOrValidator = function (value) {
switch (value) {
case "allInvalid":
case "parseValid-validatorsInvalid":
case "stillParseValid-validatorsInvalid":
return false;
default:
return true;
}
};
ctrl.$validators.validator = function (value) {
switch (value) {
case "allInvalid":
case "parseValid-validatorsInvalid":
case "stillParseValid-validatorsInvalid":
return false;
default:
return true;
}
};
ctrl.$parsers.push((value) => {
switch (value) {
case "allInvalid":
case "stillAllInvalid":
case "parseInvalid-validatorsValid":
case "stillParseInvalid-validatorsValid":
ctrl.$$parserName = "parserOrValidator";
return undefined;
default:
return value;
}
});
// Parser and validators are invalid
scope.$apply('value = "allInvalid"');
expect(scope.value).toBe("allInvalid");
expect(ctrl.$error).toEqual({
parserOrValidator: true,
validator: true,
});
ctrl.$validate();
expect(scope.value).toEqual("allInvalid");
expect(ctrl.$error).toEqual({
parserOrValidator: true,
validator: true,
});
ctrl.$setViewValue("stillAllInvalid");
expect(scope.value).toBeUndefined();
expect(ctrl.$error).toEqual({ parserOrValidator: true });
ctrl.$validate();
expect(scope.value).toBeUndefined();
expect(ctrl.$error).toEqual({ parserOrValidator: true });
// Parser is valid, validators are invalid
scope.$apply('value = "parseValid-validatorsInvalid"');
expect(scope.value).toBe("parseValid-validatorsInvalid");
expect(ctrl.$error).toEqual({
parserOrValidator: true,
validator: true,
});
ctrl.$validate();
expect(scope.value).toBe("parseValid-validatorsInvalid");
expect(ctrl.$error).toEqual({
parserOrValidator: true,
validator: true,
});
ctrl.$setViewValue("stillParseValid-validatorsInvalid");
expect(scope.value).toBeUndefined();
expect(ctrl.$error).toEqual({
parserOrValidator: true,
validator: true,
});
ctrl.$validate();
expect(scope.value).toBeUndefined();
expect(ctrl.$error).toEqual({
parserOrValidator: true,
validator: true,
});
// Parser is invalid, validators are valid
scope.$apply('value = "parseInvalid-validatorsValid"');
expect(scope.value).toBe("parseInvalid-validatorsValid");
expect(ctrl.$error).toEqual({});
ctrl.$validate();
expect(scope.value).toBe("parseInvalid-validatorsValid");
expect(ctrl.$error).toEqual({});
ctrl.$setViewValue("stillParseInvalid-validatorsValid");
expect(scope.value).toBeUndefined();
expect(ctrl.$error).toEqual({ parserOrValidator: true });
ctrl.$validate();
expect(scope.value).toBeUndefined();
expect(ctrl.$error).toEqual({ parserOrValidator: true });
});
});
describe("override ModelOptions", () => {
it("should replace the previous model options", () => {
const { $options } = ctrl;
ctrl.$overrideModelOptions({});
expect(ctrl.$options).not.toBe($options);
});
it("should set the given options", () => {
const { $options } = ctrl;
ctrl.$overrideModelOptions({ debounce: 1000, updateOn: "blur" });
expect(ctrl.$options.getOption("debounce")).toEqual(1000);
expect(ctrl.$options.getOption("updateOn")).toEqual("blur");
expect(ctrl.$options.getOption("updateOnDefault")).toBe(false);
});
it("should inherit from a parent model options if specified", () => {
const element = $compile(
'",
)($rootScope);
const ctrl = $rootScope.form.input;
ctrl.$overrideModelOptions({ debounce: 2000, "*": "$inherit" });
expect(ctrl.$options.getOption("debounce")).toEqual(2000);
expect(ctrl.$options.getOption("updateOn")).toEqual("blur");
expect(ctrl.$options.getOption("updateOnDefault")).toBe(false);
dealoc(element);
});
it("should not inherit from a parent model options if not specified", () => {
const element = $compile(
'",
)($rootScope);
const ctrl = $rootScope.form.input;
ctrl.$overrideModelOptions({ debounce: 2000 });
expect(ctrl.$options.getOption("debounce")).toEqual(2000);
expect(ctrl.$options.getOption("updateOn")).toEqual("");
expect(ctrl.$options.getOption("updateOnDefault")).toBe(true);
dealoc(element);
});
});
});
describe("CSS classes", () => {
const 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 ng-empty or ng-not-empty when the view value changes", () => {
const element = $compile('')($rootScope);
expect(element.val()).toBe("");
$rootScope.value = "XXX";
expect(element.val()).toBe("XXX");
element.val("");
browserTrigger(element, "change");
expect(element.val()).toBe("");
element.val("YYY");
browserTrigger(element, "change");
expect(element.val()).toBe("YYY");
});
it("should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty, ng-untouched, ng-touched)", () => {
const element = $compile('')(
$rootScope,
);
expect(element[0].classList.contains("ng-valid")).toBeTrue();
expect(element[0].classList.contains("ng-pristine")).toBeTrue();
expect(element[0].classList.contains("ng-touched")).toBeFalse();
expect(element[0].classList.contains("ng-valid-email")).toBe(true);
expect(element[0].classList.contains("ng-invalid-email")).toBe(false);
$rootScope.$apply("value = 'invalid-email'");
expect(element[0].classList.contains("ng-invalid")).toBeTrue();
expect(element[0].classList.contains("ng-pristine")).toBeTrue();
expect(element[0].classList.contains("ng-valid-email")).toBe(false);
expect(element[0].classList.contains("ng-invalid-email")).toBe(true);
element.val("invalid-again");
browserTrigger(element, "change");
expect(element[0].classList.contains("ng-invalid")).toBeTrue();
expect(element[0].classList.contains("ng-dirty")).toBeTrue();
expect(element[0].classList.contains("ng-valid-email")).toBe(false);
expect(element[0].classList.contains("ng-invalid-email")).toBe(true);
element.val("[email protected]");
browserTrigger(element, "change");
expect(element[0].classList.contains("ng-valid")).toBeTrue();
expect(element[0].classList.contains("ng-dirty")).toBeTrue();
expect(element[0].classList.contains("ng-valid-email")).toBe(true);
expect(element[0].classList.contains("ng-invalid-email")).toBe(false);
browserTrigger(element, "blur");
expect(element[0].classList.contains("ng-touched")).toBeTrue();
dealoc(element);
});
it("should set invalid classes on init", () => {
const element = $compile(
'',
)($rootScope);
expect(element[0].classList.contains("ng-invalid")).toBeTrue();
expect(element[0].classList.contains("ng-invalid-required")).toBeTrue();
dealoc(element);
});
});
describe("custom formatter and parser that are added by a directive in post linking", () => {
let inputElm;
let scope;
let module;
beforeEach(() => {
window.angular = new Angular();
module = window.angular
.module("myModule", [])
.directive("customFormat", () => ({
require: "ngModel",
link(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$formatters.push((value) => value.part);
ngModelCtrl.$parsers.push((value) => ({ part: value }));
},
}));
});
afterEach(() => {
dealoc(inputElm);
});
function createInput(type) {
inputElm = JQLite(``);
const injector = angular.bootstrap(inputElm, ["myModule"]);
scope = injector.get("$rootScope");
}
it("should use them after the builtin ones for text inputs", () => {
createInput("text");
scope.$apply('val = {part: "a"}');
expect(inputElm.val()).toBe("a");
inputElm.val("b");
browserTrigger(inputElm, "change");
expect(scope.val).toEqual({ part: "b" });
});
it("should use them after the builtin ones for number inputs", () => {
createInput("number");
scope.$apply("val = {part: 1}");
expect(inputElm.val()).toBe("1");
inputElm.val("2");
browserTrigger(inputElm, "change");
expect(scope.val).toEqual({ part: 2 });
});
it("should use them after the builtin ones for date inputs", () => {
createInput("date");
scope.$apply(() => {
scope.val = { part: "2000-11-08" };
});
expect(inputElm.val()).toBe("2000-11-08");
inputElm.val("2001-12-09");
browserTrigger(inputElm, "change");
expect(scope.val).toEqual({ part: "2001-12-09" });
});
});
describe("$touched", () => {
it('should set the control touched state on "blur" event', () => {
const element = $compile(
'",
)($rootScope);
const inputElm = element.find("input");
const control = $rootScope.myForm.myControl;
expect(control.$touched).toBe(false);
expect(control.$untouched).toBe(true);
browserTrigger(inputElm, "blur");
expect(control.$touched).toBe(true);
expect(control.$untouched).toBe(false);
dealoc(element);
});
it('should not cause a digest on "blur" event if control is already touched', () => {
const element = $compile(
'",
)($rootScope);
const inputElm = element.find("input");
const control = $rootScope.myForm.myControl;
control.$setTouched();
spyOn($rootScope, "$apply");
browserTrigger(inputElm, "blur");
expect($rootScope.$apply).not.toHaveBeenCalled();
dealoc(element);
});
it('should digest asynchronously on "blur" event if a apply is already in progress', () => {
const element = $compile(
'",
)($rootScope);
const inputElm = element.find("input");
const control = $rootScope.myForm.myControl;
$rootScope.$apply(() => {
expect(control.$touched).toBe(false);
expect(control.$untouched).toBe(true);
browserTrigger(inputElm, "blur");
expect(control.$touched).toBe(false);
expect(control.$untouched).toBe(true);
});
expect(control.$touched).toBe(true);
expect(control.$untouched).toBe(false);
dealoc(element);
});
});
describe("nested in a form", () => {
it("should register/deregister a nested ngModel with parent form when entering or leaving DOM", () => {
const element = $compile(
'",
)($rootScope);
let isFormValid;
$rootScope.inputPresent = false;
$rootScope.$watch("myForm.$valid", (value) => {
isFormValid = value;
});
$rootScope.$apply();
expect($rootScope.myForm.$valid).toBe(true);
expect(isFormValid).toBe(true);
expect($rootScope.myForm.myControl).toBeUndefined();
$rootScope.inputPresent = true;
$rootScope.$apply();
expect($rootScope.myForm.$valid).toBe(false);
expect(isFormValid).toBe(false);
expect($rootScope.myForm.myControl).toBeDefined();
$rootScope.inputPresent = false;
$rootScope.$apply();
expect($rootScope.myForm.$valid).toBe(true);
expect(isFormValid).toBe(true);
expect($rootScope.myForm.myControl).toBeUndefined();
dealoc(element);
});
it("should register/deregister a nested ngModel with parent form when entering or leaving DOM with animations", () => {
// ngAnimate performs the dom manipulation after digest, and since the form validity can be affected by a form
// control going away we must ensure that the deregistration happens during the digest while we are still doing
// dirty checking.
// module("ngAnimate");
const element = $compile(
'",
)($rootScope);
let isFormValid;
$rootScope.inputPresent = false;
// this watch ensure that the form validity gets updated during digest (so that we can observe it)
$rootScope.$watch("myForm.$valid", (value) => {
isFormValid = value;
});
$rootScope.$apply();
expect($rootScope.myForm.$valid).toBe(true);
expect(isFormValid).toBe(true);
expect($rootScope.myForm.myControl).toBeUndefined();
$rootScope.inputPresent = true;
$rootScope.$apply();
expect($rootScope.myForm.$valid).toBe(false);
expect(isFormValid).toBe(false);
expect($rootScope.myForm.myControl).toBeDefined();
$rootScope.inputPresent = false;
$rootScope.$apply();
expect($rootScope.myForm.$valid).toBe(true);
expect(isFormValid).toBe(true);
expect($rootScope.myForm.myControl).toBeUndefined();
dealoc(element);
});
it("should keep previously defined watches consistent when changes in validity are made", () => {
let isFormValid;
$rootScope.$watch("myForm.$valid", (value) => {
isFormValid = value;
});
const element = $compile(
'",
)($rootScope);
$rootScope.$apply();
expect(isFormValid).toBe(false);
expect($rootScope.myForm.$valid).toBe(false);
$rootScope.value = "value";
$rootScope.$apply();
expect(isFormValid).toBe(true);
expect($rootScope.myForm.$valid).toBe(true);
dealoc(element);
});
});
// TODO:
// describe("animations", () => {
// function findElementAnimations(element, queue) {
// const node = element[0];
// const animations = [];
// for (let i = 0; i < queue.length; i++) {
// const animation = queue[i];
// if (animation.element[0] === node) {
// animations.push(animation);
// }
// }
// return animations;
// }
// function assertValidAnimation(animation, event, classNameA, classNameB) {
// expect(animation.event).toBe(event);
// expect(animation.args[1]).toBe(classNameA);
// if (classNameB) expect(animation.args[2]).toBe(classNameB);
// }
// let doc;
// let input;
// let scope;
// let model;
// //beforeEach(module("ngAnimateMock"));
// beforeEach(() => {
// scope = $rootScope.$new();
// doc = JQLite(
// '",
// );
// $rootElement.append(doc);
// $compile(doc)(scope);
// $animate.queue = [];
// input = doc.find("input");
// model = scope.myForm.myInput;
// });
// afterEach(() => {
// dealoc(input);
// });
// it("should trigger an animation when invalid", () => {
// model.$setValidity("required", false);
// const animations = findElementAnimations(input, $animate.queue);
// assertValidAnimation(animations[0], "removeClass", "ng-valid");
// assertValidAnimation(animations[1], "addClass", "ng-invalid");
// assertValidAnimation(animations[2], "addClass", "ng-invalid-required");
// });
// it("should trigger an animation when valid", () => {
// model.$setValidity("required", false);
// $animate.queue = [];
// model.$setValidity("required", true);
// const animations = findElementAnimations(input, $animate.queue);
// 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", () => {
// model.$setViewValue("some dirty value");
// const animations = findElementAnimations(input, $animate.queue);
// assertValidAnimation(animations[0], "removeClass", "ng-empty");
// assertValidAnimation(animations[1], "addClass", "ng-not-empty");
// assertValidAnimation(animations[2], "removeClass", "ng-pristine");
// assertValidAnimation(animations[3], "addClass", "ng-dirty");
// });
// it("should trigger an animation when pristine", () => {
// model.$setPristine();
// const animations = findElementAnimations(input, $animate.queue);
// assertValidAnimation(animations[0], "removeClass", "ng-dirty");
// assertValidAnimation(animations[1], "addClass", "ng-pristine");
// });
// it("should trigger an animation when untouched", () => {
// model.$setUntouched();
// const animations = findElementAnimations(input, $animate.queue);
// assertValidAnimation(animations[0], "setClass", "ng-untouched");
// expect(animations[0].args[2]).toBe("ng-touched");
// });
// it("should trigger an animation when touched", () => {
// model.$setTouched();
// const animations = findElementAnimations(input, $animate.queue);
// assertValidAnimation(
// animations[0],
// "setClass",
// "ng-touched",
// "ng-untouched",
// );
// expect(animations[0].args[2]).toBe("ng-untouched");
// });
// it("should trigger custom errors as addClass/removeClass when invalid/valid", () => {
// model.$setValidity("custom-error", false);
// let animations = findElementAnimations(input, $animate.queue);
// assertValidAnimation(animations[0], "removeClass", "ng-valid");
// assertValidAnimation(animations[1], "addClass", "ng-invalid");
// assertValidAnimation(
// animations[2],
// "addClass",
// "ng-invalid-custom-error",
// );
// $animate.queue = [];
// model.$setValidity("custom-error", true);
// animations = findElementAnimations(input, $animate.queue);
// 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",
// );
// });
// });
});
© 2015 - 2025 Weber Informatics LLC | Privacy Policy