diff --git a/test/helpers/testabilityPatch.js b/test/helpers/testabilityPatch.js index fd55c58c..b349f123 100644 --- a/test/helpers/testabilityPatch.js +++ b/test/helpers/testabilityPatch.js @@ -339,3 +339,67 @@ window.dump = function() { return angular.mock.dump(arg); })); }; + +function getInputCompileHelper(currentSpec) { + + var helper = {}; + + module(function($compileProvider) { + $compileProvider.directive('attrCapture', function() { + return function(scope, element, $attrs) { + helper.attrs = $attrs; + }; + }); + }); + + inject(function($compile, $rootScope, $sniffer) { + + helper.compileInput = function(inputHtml, mockValidity, scope) { + + scope = helper.scope = scope || $rootScope; + + // Create the input element and dealoc when done + helper.inputElm = jqLite(inputHtml); + + // Set up mock validation if necessary + if (isObject(mockValidity)) { + VALIDITY_STATE_PROPERTY = 'ngMockValidity'; + helper.inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity); + currentSpec.after(function() { + VALIDITY_STATE_PROPERTY = 'validity'; + }); + } + + // Create the form element and dealoc when done + helper.formElm = jqLite('
'); + helper.formElm.append(helper.inputElm); + + // Compile the lot and return the input element + $compile(helper.formElm)(scope); + + spyOn(scope.form, '$addControl').andCallThrough(); + spyOn(scope.form, '$$renameControl').andCallThrough(); + + scope.$digest(); + + return helper.inputElm; + }; + + helper.changeInputValueTo = function(value) { + helper.inputElm.val(value); + browserTrigger(helper.inputElm, $sniffer.hasEvent('input') ? 'input' : 'change'); + }; + + helper.changeGivenInputTo = function(inputElm, value) { + inputElm.val(value); + browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change'); + }; + + helper.dealoc = function() { + dealoc(helper.inputElm); + dealoc(helper.formElm); + }; + }); + + return helper; +} diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index f0341722..6f50fc13 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -1,1513 +1,33 @@ 'use strict'; -describe('NgModelController', function() { - /* global NgModelController: false */ - var ctrl, scope, ngModelAccessor, element, parentFormCtrl; - - beforeEach(inject(function($rootScope, $controller) { - var attrs = {name: 'testAlias', ngModel: 'value'}; - - parentFormCtrl = { - $$setPending: jasmine.createSpy('$$setPending'), - $setValidity: jasmine.createSpy('$setValidity'), - $setDirty: jasmine.createSpy('$setDirty'), - $$clearControlValidity: noop - }; - - element = jqLite(''); - element.data('$formController', parentFormCtrl); - - scope = $rootScope; - ngModelAccessor = jasmine.createSpy('ngModel accessor'); - ctrl = $controller(NgModelController, { - $scope: scope, - $element: element.find('input'), - $attrs: attrs - }); - })); - - - afterEach(function() { - dealoc(element); - }); - - - it('should init the properties', function() { - 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() { - - function expectOneError() { - expect(ctrl.$error).toEqual({someError: true}); - expect(ctrl.$$success).toEqual({}); - expect(ctrl.$pending).toBeUndefined(); - } - - function expectOneSuccess() { - expect(ctrl.$error).toEqual({}); - expect(ctrl.$$success).toEqual({someError: true}); - expect(ctrl.$pending).toBeUndefined(); - } - - function expectOnePending() { - expect(ctrl.$error).toEqual({}); - expect(ctrl.$$success).toEqual({}); - expect(ctrl.$pending).toEqual({someError: true}); - } - - function expectCleared() { - expect(ctrl.$error).toEqual({}); - expect(ctrl.$$success).toEqual({}); - expect(ctrl.$pending).toBeUndefined(); - } - - it('should propagate validity to the parent form', function() { - expect(parentFormCtrl.$setValidity).not.toHaveBeenCalled(); - ctrl.$setValidity('ERROR', false); - expect(parentFormCtrl.$setValidity).toHaveBeenCalledOnceWith('ERROR', false, ctrl); - }); - - it('should transition from states correctly', function() { - 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', function() { - 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).toBe(undefined); - expect(ctrl.$invalid).toBe(undefined); - - ctrl.$setValidity('third', null); - expect(ctrl.$valid).toBe(false); - expect(ctrl.$invalid).toBe(true); - - ctrl.$setValidity('second', true); - expect(ctrl.$valid).toBe(false); - expect(ctrl.$invalid).toBe(true); - - ctrl.$setValidity('first', true); - expect(ctrl.$valid).toBe(true); - expect(ctrl.$invalid).toBe(false); - }); - - }); - - describe('setPristine', function() { - - it('should set control to its pristine state', function() { - 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', function() { - - it('should set control to its dirty state', function() { - 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', function() { - ctrl.$setDirty(); - expect(parentFormCtrl.$setDirty).toHaveBeenCalled(); - }); - }); - - describe('setUntouched', function() { - - it('should set control to its untouched state', function() { - ctrl.$setTouched(); - - ctrl.$setUntouched(); - expect(ctrl.$touched).toBe(false); - expect(ctrl.$untouched).toBe(true); - }); - }); - - describe('setTouched', function() { - - it('should set control to its touched state', function() { - ctrl.$setUntouched(); - - ctrl.$setTouched(); - expect(ctrl.$touched).toBe(true); - expect(ctrl.$untouched).toBe(false); - }); - }); - - describe('view -> model', function() { - - it('should set the value to $viewValue', function() { - ctrl.$setViewValue('some-val'); - expect(ctrl.$viewValue).toBe('some-val'); - }); - - - it('should pipeline all registered parsers and set result to $modelValue', function() { - var log = []; - - ctrl.$parsers.push(function(value) { - log.push(value); - return value + '-a'; - }); - - ctrl.$parsers.push(function(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)', - function() { - var spy = jasmine.createSpy('viewChangeListener'); - ctrl.$viewChangeListeners.push(spy); - ctrl.$setViewValue('val'); - expect(spy).toHaveBeenCalledOnce(); - spy.reset(); - - // invalid - ctrl.$parsers.push(function() {return undefined;}); - ctrl.$setViewValue('val2'); - expect(spy).toHaveBeenCalledOnce(); - }); - - - it('should reset the model when the view is invalid', function() { - ctrl.$setViewValue('aaaa'); - expect(ctrl.$modelValue).toBe('aaaa'); - - // add a validator that will make any input invalid - ctrl.$parsers.push(function() {return 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', function() { - 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', function() { - // 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(function() {return 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.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', function() { - ctrl.$setViewValue(''); - expect(ctrl.$pristine).toBe(false); - expect(ctrl.$dirty).toBe(true); - expect(parentFormCtrl.$setDirty).toHaveBeenCalledOnce(); - - parentFormCtrl.$setDirty.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', function() { - var a, b, val = function(val, x) { - return x ? val : x; - }; - - ctrl.$parsers.push(function(v) { return val(v, a); }); - ctrl.$parsers.push(function(v) { return 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', function() { - ctrl.$parsers.push(function(v) { return 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', - inject(function($compile, $rootScope) { - - var element = $compile('')($rootScope); - var inputElm = element.find('input'); - var ctrl = $rootScope.myForm.myControl; - - var parserIsFailing = false; - ctrl.$parsers.push(function(value) { - return parserIsFailing ? undefined : value; - }); - - ctrl.$validators.alwaysFail = function() { - return false; - }; - - ctrl.$setViewValue('123'); - scope.$digest(); - - expect(element).toHaveClass('ng-valid-parse'); - expect(element).not.toHaveClass('ng-invalid-parse'); - expect(element).toHaveClass('ng-invalid-always-fail'); - - parserIsFailing = true; - ctrl.$setViewValue('12345'); - scope.$digest(); - - expect(element).not.toHaveClass('ng-valid-parse'); - expect(element).toHaveClass('ng-invalid-parse'); - expect(element).not.toHaveClass('ng-invalid-always-fail'); - - dealoc(element); - })); - - it('should set the ng-invalid-parse and ng-valid-parse CSS class when parsers fail and pass', function() { - var pass = true; - ctrl.$parsers.push(function(v) { - return pass ? v : undefined; - }); - - var input = element.find('input'); - - ctrl.$setViewValue('1'); - expect(input).toHaveClass('ng-valid-parse'); - expect(input).not.toHaveClass('ng-invalid-parse'); - - pass = undefined; - - ctrl.$setViewValue('2'); - expect(input).not.toHaveClass('ng-valid-parse'); - expect(input).toHaveClass('ng-invalid-parse'); - }); - - it('should update the model after all async validators resolve', inject(function($q) { - var 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(); - scope.$digest(); - - 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(); - scope.$digest(); - - expect(ctrl.$modelValue).toBe('c'); - expect(scope.value).toBe('c'); - - })); - - }); - - - describe('model -> view', function() { - - it('should set the value to $modelValue', function() { - scope.$apply('value = 10'); - expect(ctrl.$modelValue).toBe(10); - }); - - - it('should pipeline all registered formatters in reversed order and set result to $viewValue', - function() { - var log = []; - - ctrl.$formatters.unshift(function(value) { - log.push(value); - return value + 2; - }); - - ctrl.$formatters.unshift(function(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', function() { - spyOn(ctrl, '$render'); - - scope.$apply('value = 3'); - expect(ctrl.$render).toHaveBeenCalledOnce(); - ctrl.$render.reset(); - - ctrl.$formatters.push(function() {return 3;}); - scope.$apply('value = 5'); - expect(ctrl.$render).not.toHaveBeenCalled(); - }); - - - it('should clear the view even if invalid', function() { - spyOn(ctrl, '$render'); - - ctrl.$formatters.push(function() {return undefined;}); - scope.$apply('value = 5'); - expect(ctrl.$render).toHaveBeenCalledOnce(); - }); - - it('should render immediately even if there are async validators', inject(function($q) { - spyOn(ctrl, '$render'); - ctrl.$asyncValidators.someValidator = function() { - return $q.defer().promise; - }; - - scope.$apply('value = 5'); - expect(ctrl.$viewValue).toBe(5); - expect(ctrl.$render).toHaveBeenCalledOnce(); - })); - - it('should not rerender nor validate in case view value is not changed', function() { - ctrl.$formatters.push(function(value) { - return 'nochange'; - }); - - spyOn(ctrl, '$render'); - ctrl.$validators.spyValidator = jasmine.createSpy('spyValidator'); - scope.$apply('value = "first"'); - scope.$apply('value = "second"'); - expect(ctrl.$validators.spyValidator).toHaveBeenCalledOnce(); - expect(ctrl.$render).toHaveBeenCalledOnce(); - }); - - }); - - describe('validation', function() { - - describe('$validate', function() { - it('should perform validations when $validate() is called', function() { - scope.$apply('value = ""'); - - var 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', function() { - ctrl.$parsers.push(function(modelValue) { - return modelValue + 'def'; - }); - - ctrl.$setViewValue('abc'); - - ctrl.$validators.test = function(modelValue, viewValue) { - return true; - }; - - spyOn(ctrl.$validators, 'test'); - - ctrl.$validate(); - - expect(ctrl.$validators.test).toHaveBeenCalledWith('abcdef', 'abc'); - }); - - it('should set the model to undefined when it becomes invalid', function() { - var valid = true; - ctrl.$validators.test = function(modelValue, viewValue) { - return valid; - }; - - scope.$apply('value = "abc"'); - expect(scope.value).toBe('abc'); - - valid = false; - ctrl.$validate(); - - expect(scope.value).toBeUndefined(); - }); - - it('should update the model when it becomes valid', function() { - var valid = true; - ctrl.$validators.test = function(modelValue, viewValue) { - return valid; - }; - - scope.$apply('value = "abc"'); - expect(scope.value).toBe('abc'); - - valid = false; - ctrl.$validate(); - expect(scope.value).toBeUndefined(); - - valid = true; - ctrl.$validate(); - expect(scope.value).toBe('abc'); - }); - - it('should not update the model when it is valid, but there is a parse error', function() { - ctrl.$parsers.push(function(modelValue) { - return undefined; - }); - - ctrl.$setViewValue('abc'); - expect(ctrl.$error.parse).toBe(true); - expect(scope.value).toBeUndefined(); - - ctrl.$validators.test = function(modelValue, viewValue) { - return true; - }; - - ctrl.$validate(); - expect(ctrl.$error).toEqual({parse: true}); - expect(scope.value).toBeUndefined(); - }); - - it('should not set an invalid model to undefined when validity is the same', function() { - ctrl.$validators.test = function() { - return false; - }; - - scope.$apply('value = "invalid"'); - expect(ctrl.$valid).toBe(false); - expect(scope.value).toBe('invalid'); - - ctrl.$validate(); - expect(ctrl.$valid).toBe(false); - expect(scope.value).toBe('invalid'); - }); - - it('should not change a model that has a formatter', function() { - ctrl.$validators.test = function() { - return true; - }; - - ctrl.$formatters.push(function(modelValue) { - return 'xyz'; - }); - - scope.$apply('value = "abc"'); - expect(ctrl.$viewValue).toBe('xyz'); - - ctrl.$validate(); - expect(scope.value).toBe('abc'); - }); - - it('should not change a model that has a parser', function() { - ctrl.$validators.test = function() { - return true; - }; - - ctrl.$parsers.push(function(modelValue) { - return 'xyz'; - }); - - scope.$apply('value = "abc"'); - - ctrl.$validate(); - expect(scope.value).toBe('abc'); - }); - }); - - describe('view -> model update', function() { - it('should always perform validations using the parsed model value', function() { - var captures; - ctrl.$validators.raw = function() { - captures = arguments; - return captures[0]; - }; - - ctrl.$parsers.push(function(value) { - return value.toUpperCase(); - }); - - ctrl.$setViewValue('my-value'); - - expect(captures).toEqual(['MY-VALUE', 'my-value']); - }); - - it('should always perform validations using the formatted view value', function() { - var captures; - ctrl.$validators.raw = function() { - captures = arguments; - return captures[0]; - }; - - ctrl.$formatters.push(function(value) { - return value + '...'; - }); - - scope.$apply('value = "matias"'); - - expect(captures).toEqual(['matias', 'matias...']); - }); - - it('should only perform validations if the view value is different', function() { - var 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', function() { - var 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', function() { - var curry = function(v) { - return function() { - return v; - }; - }; - - ctrl.$modelValue = undefined; - ctrl.$validators.a = curry(true); - ctrl.$validators.b = curry(true); - ctrl.$validators.c = curry(false); - - ctrl.$validate(); - expect(ctrl.$valid).toBe(false); - - ctrl.$validators.c = curry(true); - - ctrl.$validate(); - expect(ctrl.$valid).toBe(true); - }); - - it('should register invalid validations on the $error object', function() { - var curry = function(v) { - return function() { - return v; - }; - }; - - ctrl.$modelValue = undefined; - ctrl.$validators.unique = curry(false); - ctrl.$validators.tooLong = curry(false); - ctrl.$validators.notNumeric = curry(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', inject(function($q) { - var 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(); - scope.$digest(); - - expect(ctrl.$valid).toBe(true); - expect(ctrl.$invalid).toBe(false); - expect(ctrl.$pending).toBeUndefined(); - - scope.$apply('value = "123"'); - - defer.reject(); - scope.$digest(); - - 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', inject(function($q) { - ctrl.$asyncValidators.async = function(value) { - return true; - }; - - expect(function() { - scope.$apply('value = "123"'); - }).toThrowMinErr("ngModel", "$asyncValidators", - "Expected asynchronous validator to return a promise but got 'true' instead."); - })); - - it('should only run the async validators once all the sync validators have passed', - inject(function($q) { - - var 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', inject(function($q) { - var defer, oldDefer, 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(); - scope.$digest(); - oldDefer.resolve(); - scope.$digest(); - - 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', inject(function($q) { - ctrl.$validators.sync = function(value) { - return true; - }; - - var defers = []; - ctrl.$asyncValidators.async = function(value) { - var defer = $q.defer(); - defers.push(defer); - return defer.promise; - }; - - scope.$apply('value = "123"'); - expect(ctrl.$pending).toEqual({async: true}); - expect(ctrl.$valid).toBe(undefined); - expect(ctrl.$invalid).toBe(undefined); - expect(defers.length).toBe(1); - expect(isObject(ctrl.$pending)).toBe(true); - - scope.$apply('value = "456"'); - expect(ctrl.$pending).toEqual({async: true}); - expect(ctrl.$valid).toBe(undefined); - expect(ctrl.$invalid).toBe(undefined); - expect(defers.length).toBe(2); - expect(isObject(ctrl.$pending)).toBe(true); - - defers[1].resolve(); - scope.$digest(); - 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', inject(function($q) { - var failParser = false; - ctrl.$parsers.push(function(value) { - return failParser ? undefined : value; - }); - - var defer; - ctrl.$asyncValidators.async = function(value) { - defer = $q.defer(); - return defer.promise; - }; - - ctrl.$setViewValue('x..y..z'); - expect(ctrl.$valid).toBe(undefined); - expect(ctrl.$invalid).toBe(undefined); - - 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(); - scope.$digest(); - - 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', inject(function($q) { - var failParser = false; - ctrl.$parsers.push(function(value) { - return 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', inject(function($q) { - var 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 re-evaluate the form validity state once the asynchronous promise has been delivered', - inject(function($compile, $rootScope, $q) { - - var element = $compile('')($rootScope); - var inputElm = element.find('input'); - - var formCtrl = $rootScope.myForm; - var usernameCtrl = formCtrl.username; - var ageCtrl = formCtrl.age; - - var usernameDefer; - usernameCtrl.$asyncValidators.usernameAvailability = function() { - usernameDefer = $q.defer(); - return usernameDefer.promise; - }; - - $rootScope.$digest(); - expect(usernameCtrl.$invalid).toBe(true); - expect(formCtrl.$invalid).toBe(true); - - usernameCtrl.$setViewValue('valid-username'); - $rootScope.$digest(); - - expect(formCtrl.$pending.usernameAvailability).toBeTruthy(); - expect(usernameCtrl.$invalid).toBe(undefined); - expect(formCtrl.$invalid).toBe(undefined); - - usernameDefer.resolve(); - $rootScope.$digest(); - expect(usernameCtrl.$invalid).toBe(false); - expect(formCtrl.$invalid).toBe(true); - - ageCtrl.$setViewValue(22); - $rootScope.$digest(); - - expect(usernameCtrl.$invalid).toBe(false); - expect(ageCtrl.$invalid).toBe(false); - expect(formCtrl.$invalid).toBe(false); - - usernameCtrl.$setViewValue('valid'); - $rootScope.$digest(); - - expect(usernameCtrl.$invalid).toBe(true); - expect(ageCtrl.$invalid).toBe(false); - expect(formCtrl.$invalid).toBe(true); - - usernameCtrl.$setViewValue('another-valid-username'); - $rootScope.$digest(); - - usernameDefer.resolve(); - $rootScope.$digest(); - - expect(usernameCtrl.$invalid).toBe(false); - expect(formCtrl.$invalid).toBe(false); - expect(formCtrl.$pending).toBeFalsy(); - expect(ageCtrl.$invalid).toBe(false); - - dealoc(element); - })); - - - it('should minimize janky setting of classes during $validate() and ngModelWatch', inject(function($animate, $compile, $rootScope) { - var addClass = $animate.$$addClassImmediately; - var removeClass = $animate.$$removeClassImmediately; - var addClassCallCount = 0; - var removeClassCallCount = 0; - var input; - $animate.$$addClassImmediately = function(element, className) { - if (input && element[0] === input[0]) ++addClassCallCount; - return addClass.call($animate, element, className); - }; - - $animate.$$removeClassImmediately = function(element, className) { - if (input && element[0] === input[0]) ++removeClassCallCount; - return removeClass.call($animate, element, className); - }; - - dealoc(element); - - $rootScope.value = "123456789"; - element = $compile( - '' - )($rootScope); - - var form = $rootScope.form; - input = element.children().eq(0); - - $rootScope.$digest(); - - expect(input).toBeValid(); - expect(input).not.toHaveClass('ng-invalid-maxlength'); - expect(input).toHaveClass('ng-valid-maxlength'); - expect(addClassCallCount).toBe(1); - expect(removeClassCallCount).toBe(0); - - dealoc(element); - })); - - it('should always use the most recent $viewValue for validation', function() { - ctrl.$parsers.push(function(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').andCallThrough(); - - ctrl.$setViewValue('ab'); - - expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a'); - expect(ctrl.$validators.mock.calls.length).toEqual(2); - }); - - it('should validate even if the modelValue did not change', function() { - ctrl.$parsers.push(function(value) { - if (value && value.substr(-1) === 'b') { - value = 'a'; - } - - return value; - }); - - ctrl.$validators.mock = function(modelValue) { - return true; - }; - - spyOn(ctrl.$validators, 'mock').andCallThrough(); - - ctrl.$setViewValue('a'); - - expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a'); - expect(ctrl.$validators.mock.calls.length).toEqual(1); - - ctrl.$setViewValue('ab'); - - expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'ab'); - expect(ctrl.$validators.mock.calls.length).toEqual(2); - }); - - }); -}); - - -describe('ngModel', function() { - var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; - - it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty, ng-untouched, ng-touched)', - inject(function($compile, $rootScope, $sniffer) { - var element = $compile('')($rootScope); - - $rootScope.$digest(); - expect(element).toBeValid(); - expect(element).toBePristine(); - expect(element).toBeUntouched(); - expect(element.hasClass('ng-valid-email')).toBe(true); - expect(element.hasClass('ng-invalid-email')).toBe(false); - - $rootScope.$apply("value = 'invalid-email'"); - expect(element).toBeInvalid(); - expect(element).toBePristine(); - expect(element.hasClass('ng-valid-email')).toBe(false); - expect(element.hasClass('ng-invalid-email')).toBe(true); - - element.val('invalid-again'); - browserTrigger(element, ($sniffer.hasEvent('input')) ? 'input' : 'change'); - expect(element).toBeInvalid(); - expect(element).toBeDirty(); - expect(element.hasClass('ng-valid-email')).toBe(false); - expect(element.hasClass('ng-invalid-email')).toBe(true); - - element.val('vojta@google.com'); - browserTrigger(element, $sniffer.hasEvent('input') ? 'input' : 'change'); - expect(element).toBeValid(); - expect(element).toBeDirty(); - expect(element.hasClass('ng-valid-email')).toBe(true); - expect(element.hasClass('ng-invalid-email')).toBe(false); - - browserTrigger(element, 'blur'); - expect(element).toBeTouched(); - - dealoc(element); - })); - - - it('should set invalid classes on init', inject(function($compile, $rootScope) { - var element = $compile('')($rootScope); - $rootScope.$digest(); - - expect(element).toBeInvalid(); - expect(element).toHaveClass('ng-invalid-required'); - - dealoc(element); - })); - - describe('custom formatter and parser that are added by a directive in post linking', function() { - var inputElm, scope; - beforeEach(module(function($compileProvider) { - $compileProvider.directive('customFormat', function() { - return { - require: 'ngModel', - link: function(scope, element, attrs, ngModelCtrl) { - ngModelCtrl.$formatters.push(function(value) { - return value.part; - }); - ngModelCtrl.$parsers.push(function(value) { - return {part: value}; - }); - } - }; - }); - })); - - afterEach(function() { - dealoc(inputElm); - }); - - function createInput(type) { - inject(function($compile, $rootScope) { - scope = $rootScope; - inputElm = $compile('')($rootScope); - }); - } - - it('should use them after the builtin ones for text inputs', function() { - 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', function() { - 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', function() { - createInput('date'); - scope.$apply(function() { - scope.val = {part: new Date(2000, 10, 8)}; - }); - expect(inputElm.val()).toBe('2000-11-08'); - - inputElm.val('2001-12-09'); - browserTrigger(inputElm, 'change'); - expect(scope.val).toEqual({part: new Date(2001, 11, 9)}); - }); - }); - - - it('should always format the viewValue as a string for a blank input type when the value is present', - inject(function($compile, $rootScope, $sniffer) { - - var form = $compile('')($rootScope); - - $rootScope.val = 123; - $rootScope.$digest(); - expect($rootScope.form.field.$viewValue).toBe('123'); - - $rootScope.val = null; - $rootScope.$digest(); - 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', - inject(function($compile, $rootScope, $sniffer) { - - var form = $compile('')($rootScope); - $rootScope.val = 123; - $rootScope.$digest(); - expect($rootScope.form.field.$viewValue).toBe('123'); - - $rootScope.val = null; - $rootScope.$digest(); - 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', - inject(function($compile, $rootScope, $sniffer) { - - var form = $compile('')($rootScope); - $rootScope.val = 123; - $rootScope.$digest(); - expect($rootScope.form.field.$viewValue).toBe('123'); - - $rootScope.val = null; - $rootScope.$digest(); - 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', - inject(function($compile, $rootScope, $sniffer) { - - var form = $compile('')($rootScope); - $rootScope.val = 123; - $rootScope.$digest(); - expect($rootScope.form.field.$viewValue).toBe('123'); - - $rootScope.val = null; - $rootScope.$digest(); - expect($rootScope.form.field.$viewValue).toBe(null); - - dealoc(form); - })); - - it('should set the control touched state on "blur" event', inject(function($compile, $rootScope) { - var element = $compile('')($rootScope); - var inputElm = element.find('input'); - var 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', - inject(function($compile, $rootScope) { - - var element = $compile('')($rootScope); - var inputElm = element.find('input'); - var 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', - inject(function($compile, $rootScope) { - - var element = $compile('')($rootScope); - var inputElm = element.find('input'); - var control = $rootScope.myForm.myControl; - - $rootScope.$apply(function() { - 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); - })); - - - it('should register/deregister a nested ngModel with parent form when entering or leaving DOM', - inject(function($compile, $rootScope) { - - var element = $compile('')($rootScope); - var isFormValid; - - $rootScope.inputPresent = false; - $rootScope.$watch('myForm.$valid', function(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', - function() { - - // 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'); - - inject(function($compile, $rootScope) { - var element = $compile('')($rootScope); - var 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', function(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', - inject(function($compile, $rootScope) { - - var isFormValid; - $rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; }); - - var 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); - })); - -}); - +/* globals getInputCompileHelper: false */ describe('input', function() { - var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo, currentSpec; + var helper, $compile, $rootScope, $browser, $sniffer, $timeout, $q; - function compileInput(inputHtml, mockValidity) { - inputElm = jqLite(inputHtml); - if (isObject(mockValidity)) { - VALIDITY_STATE_PROPERTY = 'ngMockValidity'; - inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity); - currentSpec.after(function() { - VALIDITY_STATE_PROPERTY = 'validity'; - }); - } - formElm = jqLite(''); - formElm.append(inputElm); - $compile(formElm)(scope); - scope.$digest(); - } - - var attrs; - beforeEach(function() { currentSpec = this; }); - afterEach(function() { currentSpec = null; }); - beforeEach(module(function($compileProvider) { - $compileProvider.directive('attrCapture', function() { - return function(scope, element, $attrs) { - attrs = $attrs; - }; - }); - })); - - beforeEach(inject(function($injector, _$sniffer_, _$browser_) { - $sniffer = _$sniffer_; - $browser = _$browser_; - $compile = $injector.get('$compile'); - scope = $injector.get('$rootScope'); - - changeInputValueTo = function(value) { - inputElm.val(value); - browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change'); - }; - })); + beforeEach(function() { + helper = getInputCompileHelper(this); + }); afterEach(function() { - dealoc(formElm); + helper.dealoc(); }); + beforeEach(inject(function(_$compile_, _$rootScope_, _$browser_, _$sniffer_, _$timeout_, _$q_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $browser = _$browser_; + $sniffer = _$sniffer_; + $timeout = _$timeout_; + $q = _$q_; + })); + + it('should bind to a model', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply("name = 'misko'"); + $rootScope.$apply("name = 'misko'"); expect(inputElm.val()).toBe('misko'); }); @@ -1526,7 +46,7 @@ describe('input', function() { } }); - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.prop('readOnly')).toBe(false); expect(inputElm.prop('disabled')).toBe(false); @@ -1537,67 +57,63 @@ describe('input', function() { it('should update the model on "blur" event', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('adam'); - expect(scope.name).toEqual('adam'); + helper.changeInputValueTo('adam'); + expect($rootScope.name).toEqual('adam'); }); it('should not add the property to the scope if name is unspecified', function() { - inputElm = jqLite(''); - formElm = jqLite(''); - formElm.append(inputElm); - $compile(formElm)(scope); + helper.compileInput(''); - spyOn(scope.form, '$addControl').andCallThrough(); - spyOn(scope.form, '$$renameControl').andCallThrough(); - - scope.$digest(); - - expect(scope.form['undefined']).toBeUndefined(); - expect(scope.form.$addControl).not.toHaveBeenCalled(); - expect(scope.form.$$renameControl).not.toHaveBeenCalled(); + expect($rootScope.form['undefined']).toBeUndefined(); + expect($rootScope.form.$addControl).not.toHaveBeenCalled(); + expect($rootScope.form.$$renameControl).not.toHaveBeenCalled(); }); describe('compositionevents', function() { - it('should not update the model between "compositionstart" and "compositionend" on non android', inject(function($sniffer) { + it('should not update the model between "compositionstart" and "compositionend" on non android', function() { + $sniffer.android = false; - compileInput(''); - changeInputValueTo('a'); - expect(scope.name).toEqual('a'); + var inputElm = helper.compileInput(''); + helper.changeInputValueTo('a'); + expect($rootScope.name).toEqual('a'); browserTrigger(inputElm, 'compositionstart'); - changeInputValueTo('adam'); - expect(scope.name).toEqual('a'); + helper.changeInputValueTo('adam'); + expect($rootScope.name).toEqual('a'); browserTrigger(inputElm, 'compositionend'); - changeInputValueTo('adam'); - expect(scope.name).toEqual('adam'); - })); + helper.changeInputValueTo('adam'); + expect($rootScope.name).toEqual('adam'); + }); - it('should update the model between "compositionstart" and "compositionend" on android', inject(function($sniffer) { + + it('should update the model between "compositionstart" and "compositionend" on android', function() { $sniffer.android = true; - compileInput(''); - changeInputValueTo('a'); - expect(scope.name).toEqual('a'); + var inputElm = helper.compileInput(''); + helper.changeInputValueTo('a'); + expect($rootScope.name).toEqual('a'); browserTrigger(inputElm, 'compositionstart'); - changeInputValueTo('adam'); - expect(scope.name).toEqual('adam'); + helper.changeInputValueTo('adam'); + expect($rootScope.name).toEqual('adam'); browserTrigger(inputElm, 'compositionend'); - changeInputValueTo('adam2'); - expect(scope.name).toEqual('adam2'); - })); + helper.changeInputValueTo('adam2'); + expect($rootScope.name).toEqual('adam2'); + }); + + + it('should update the model on "compositionend"', function() { + var inputElm = helper.compileInput(''); + browserTrigger(inputElm, 'compositionstart'); + helper.changeInputValueTo('caitp'); + expect($rootScope.name).toBeUndefined(); + browserTrigger(inputElm, 'compositionend'); + expect($rootScope.name).toEqual('caitp'); + }); }); - it('should update the model on "compositionend"', function() { - compileInput(''); - browserTrigger(inputElm, 'compositionstart'); - changeInputValueTo('caitp'); - expect(scope.name).toBeUndefined(); - browserTrigger(inputElm, 'compositionend'); - expect(scope.name).toEqual('caitp'); - }); describe("IE placeholder input events", function() { //IE fires an input event whenever a placeholder visually changes, essentially treating it as a value @@ -1609,32 +125,33 @@ describe('input', function() { //These tests try simulate various scenerios which do/do-not fire the extra input event it('should not dirty the model on an input event in response to a placeholder change', function() { - compileInput(''); + var inputElm = helper.compileInput(''); msie && browserTrigger(inputElm, 'input'); expect(inputElm.attr('placeholder')).toBe('Test'); expect(inputElm).toBePristine(); - attrs.$set('placeholder', ''); + helper.attrs.$set('placeholder', ''); msie && browserTrigger(inputElm, 'input'); expect(inputElm.attr('placeholder')).toBe(''); expect(inputElm).toBePristine(); - attrs.$set('placeholder', 'Test Again'); + helper.attrs.$set('placeholder', 'Test Again'); msie && browserTrigger(inputElm, 'input'); expect(inputElm.attr('placeholder')).toBe('Test Again'); expect(inputElm).toBePristine(); - attrs.$set('placeholder', undefined); + helper.attrs.$set('placeholder', undefined); msie && browserTrigger(inputElm, 'input'); expect(inputElm.attr('placeholder')).toBe(undefined); expect(inputElm).toBePristine(); - changeInputValueTo('foo'); + helper.changeInputValueTo('foo'); expect(inputElm).toBeDirty(); }); - it('should not dirty the model on an input event in response to a interpolated placeholder change', inject(function($rootScope) { - compileInput(''); + + it('should not dirty the model on an input event in response to a interpolated placeholder change', function() { + var inputElm = helper.compileInput(''); msie && browserTrigger(inputElm, 'input'); expect(inputElm).toBePristine(); @@ -1648,13 +165,14 @@ describe('input', function() { msie && browserTrigger(inputElm, 'input'); expect(inputElm).toBePristine(); - changeInputValueTo('foo'); + helper.changeInputValueTo('foo'); expect(inputElm).toBeDirty(); - })); + }); - it('should dirty the model on an input event while in focus even if the placeholder changes', inject(function($rootScope) { + + it('should dirty the model on an input event while in focus even if the placeholder changes', function() { $rootScope.ph = 'Test'; - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm).toBePristine(); browserTrigger(inputElm, 'focusin'); @@ -1667,12 +185,13 @@ describe('input', function() { $rootScope.$digest(); expect(inputElm).toBePristine(); - changeInputValueTo('foo'); + helper.changeInputValueTo('foo'); expect(inputElm).toBeDirty(); - })); + }); - it('should not dirty the model on an input event in response to a ng-attr-placeholder change', inject(function($rootScope) { - compileInput(''); + + it('should not dirty the model on an input event in response to a ng-attr-placeholder change', function() { + var inputElm = helper.compileInput(''); expect(inputElm).toBePristine(); $rootScope.ph = 1; @@ -1685,12 +204,13 @@ describe('input', function() { msie && browserTrigger(inputElm, 'input'); expect(inputElm).toBePristine(); - changeInputValueTo('foo'); + helper.changeInputValueTo('foo'); expect(inputElm).toBeDirty(); - })); + }); - it('should not dirty the model on an input event in response to a focus', inject(function($sniffer) { - compileInput(''); + + it('should not dirty the model on an input event in response to a focus', function() { + var inputElm = helper.compileInput(''); msie && browserTrigger(inputElm, 'input'); expect(inputElm.attr('placeholder')).toBe('Test'); expect(inputElm).toBePristine(); @@ -1701,12 +221,13 @@ describe('input', function() { expect(inputElm.attr('placeholder')).toBe('Test'); expect(inputElm).toBePristine(); - changeInputValueTo('foo'); + helper.changeInputValueTo('foo'); expect(inputElm).toBeDirty(); - })); + }); - it('should not dirty the model on an input event in response to a blur', inject(function($sniffer) { - compileInput(''); + + it('should not dirty the model on an input event in response to a blur', function() { + var inputElm = helper.compileInput(''); msie && browserTrigger(inputElm, 'input'); expect(inputElm.attr('placeholder')).toBe('Test'); expect(inputElm).toBePristine(); @@ -1721,35 +242,38 @@ describe('input', function() { browserTrigger(inputElm, 'blur'); expect(inputElm).toBePristine(); - changeInputValueTo('foo'); + helper.changeInputValueTo('foo'); expect(inputElm).toBeDirty(); - })); + }); - it('should dirty the model on an input event if there is a placeholder and value', inject(function($rootScope) { + + it('should dirty the model on an input event if there is a placeholder and value', function() { $rootScope.name = 'foo'; - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe($rootScope.name); expect(inputElm).toBePristine(); - changeInputValueTo('bar'); + helper.changeInputValueTo('bar'); expect(inputElm).toBeDirty(); - })); + }); - it('should dirty the model on an input event if there is a placeholder and value after focusing', inject(function($rootScope) { + + it('should dirty the model on an input event if there is a placeholder and value after focusing', function() { $rootScope.name = 'foo'; - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe($rootScope.name); expect(inputElm).toBePristine(); browserTrigger(inputElm, 'focusin'); browserTrigger(inputElm, 'focus'); - changeInputValueTo('bar'); + helper.changeInputValueTo('bar'); expect(inputElm).toBeDirty(); - })); + }); - it('should dirty the model on an input event if there is a placeholder and value after bluring', inject(function($rootScope) { + + it('should dirty the model on an input event if there is a placeholder and value after bluring', function() { $rootScope.name = 'foo'; - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe($rootScope.name); expect(inputElm).toBePristine(); @@ -1759,96 +283,105 @@ describe('input', function() { browserTrigger(inputElm, 'focusout'); browserTrigger(inputElm, 'blur'); - changeInputValueTo('bar'); + helper.changeInputValueTo('bar'); expect(inputElm).toBeDirty(); - })); + }); }); - it('should interpolate input names', function() { - scope.nameID = '47'; - compileInput(''); - expect(scope.form.name47.$pristine).toBeTruthy(); - changeInputValueTo('caitp'); - expect(scope.form.name47.$dirty).toBeTruthy(); + describe('interpolated names', function() { + + it('should interpolate input names', function() { + $rootScope.nameID = '47'; + var inputElm = helper.compileInput(''); + expect($rootScope.form.name47.$pristine).toBeTruthy(); + helper.changeInputValueTo('caitp'); + expect($rootScope.form.name47.$dirty).toBeTruthy(); + }); + + + it('should rename form controls in form when interpolated name changes', function() { + $rootScope.nameID = "A"; + var inputElm = helper.compileInput(''); + expect($rootScope.form.nameA.$name).toBe('nameA'); + var oldModel = $rootScope.form.nameA; + $rootScope.nameID = "B"; + $rootScope.$digest(); + expect($rootScope.form.nameA).toBeUndefined(); + expect($rootScope.form.nameB).toBe(oldModel); + expect($rootScope.form.nameB.$name).toBe('nameB'); + }); + + + it('should rename form controls in null form when interpolated name changes', function() { + $rootScope.nameID = "A"; + var inputElm = helper.compileInput(''); + var model = inputElm.controller('ngModel'); + expect(model.$name).toBe('nameA'); + + $rootScope.nameID = "B"; + $rootScope.$digest(); + expect(model.$name).toBe('nameB'); + }); }); - - it('should rename form controls in form when interpolated name changes', function() { - scope.nameID = "A"; - compileInput(''); - expect(scope.form.nameA.$name).toBe('nameA'); - var oldModel = scope.form.nameA; - scope.nameID = "B"; - scope.$digest(); - expect(scope.form.nameA).toBeUndefined(); - expect(scope.form.nameB).toBe(oldModel); - expect(scope.form.nameB.$name).toBe('nameB'); - }); - - - it('should rename form controls in null form when interpolated name changes', function() { - var element = $compile('')(scope); - scope.nameID = "A"; - scope.$digest(); - var model = element.controller('ngModel'); - expect(model.$name).toBe('nameA'); - - scope.nameID = "B"; - scope.$digest(); - expect(model.$name).toBe('nameB'); - }); - - describe('"change" event', function() { - function assertBrowserSupportsChangeEvent(inputEventSupported) { - // Force browser to report a lack of an 'input' event - $sniffer.hasEvent = function(eventName) { - if (eventName === 'input' && !inputEventSupported) { - return false; - } - return true; - }; - compileInput(''); + var assertBrowserSupportsChangeEvent; + + beforeEach(function() { + assertBrowserSupportsChangeEvent = function(inputEventSupported) { + // Force browser to report a lack of an 'input' event + $sniffer.hasEvent = function(eventName) { + return !(eventName === 'input' && !inputEventSupported); + }; + var inputElm = helper.compileInput(''); + + inputElm.val('mark'); + browserTrigger(inputElm, 'change'); + expect($rootScope.name).toEqual('mark'); + }; + }); - inputElm.val('mark'); - browserTrigger(inputElm, 'change'); - expect(scope.name).toEqual('mark'); - } it('should update the model event if the browser does not support the "input" event',function() { assertBrowserSupportsChangeEvent(false); }); + it('should update the model event if the browser supports the "input" ' + 'event so that form auto complete works',function() { assertBrowserSupportsChangeEvent(true); }); + if (!_jqLiteMode) { describe('double $digest when triggering an event using jQuery', function() { - function run() { - $sniffer.hasEvent = function(eventName) { - return eventName !== 'input'; + var run; + + beforeEach(function() { + run = function(scope) { + + $sniffer.hasEvent = function(eventName) { return eventName !== 'input'; }; + + scope = scope || $rootScope; + + var inputElm = helper.compileInput('', false, scope); + + scope.field = 'fake field'; + scope.$watch('field', function() { + // We need to use _originalTrigger since trigger is modified by Angular Scenario. + inputElm._originalTrigger('change'); + }); + scope.$apply(); }; - - compileInput(''); - - scope.field = 'fake field'; - scope.$watch('field', function() { - // We need to use _originalTrigger since trigger is modified by Angular Scenario. - inputElm._originalTrigger('change'); - }); - scope.$apply(); - } + }); it('should not cause the double $digest with non isolate scopes', function() { run(); }); it('should not cause the double $digest with isolate scopes', function() { - scope = scope.$new(true); - run(); + run($rootScope.$new(true)); }); }); } @@ -1862,8 +395,9 @@ describe('input', function() { }; }); + it('should update the model on "paste" event if the input value changes', function() { - compileInput(''); + var inputElm = helper.compileInput(''); browserTrigger(inputElm, 'keydown'); $browser.defer.flush(); @@ -1872,20 +406,22 @@ describe('input', function() { inputElm.val('mark'); browserTrigger(inputElm, 'paste'); $browser.defer.flush(); - expect(scope.name).toEqual('mark'); + expect($rootScope.name).toEqual('mark'); }); + it('should update the model on "cut" event', function() { - compileInput(''); + var inputElm = helper.compileInput(''); inputElm.val('john'); browserTrigger(inputElm, 'cut'); $browser.defer.flush(); - expect(scope.name).toEqual('john'); + expect($rootScope.name).toEqual('john'); }); + it('should cancel the delayed dirty if a change occurs', function() { - compileInput(''); + var inputElm = helper.compileInput(''); var ctrl = inputElm.controller('ngModel'); browserTrigger(inputElm, 'keydown', {target: inputElm[0]}); @@ -1894,7 +430,7 @@ describe('input', function() { expect(inputElm).toBeDirty(); ctrl.$setPristine(); - scope.$apply(); + $rootScope.$apply(); $browser.defer.flush(); expect(inputElm).toBePristine(); @@ -1902,579 +438,37 @@ describe('input', function() { }); - it('should update the model and trim the value', function() { - compileInput(''); + describe('ngTrim', function() { - changeInputValueTo(' a '); - expect(scope.name).toEqual('a'); - }); + it('should update the model and trim the value', function() { + var inputElm = helper.compileInput(''); - - it('should update the model and not trim the value', function() { - compileInput(''); - - changeInputValueTo(' a '); - expect(scope.name).toEqual(' a '); - }); - - - describe('ngModelOptions attributes', function() { - - it('should allow overriding the model update trigger event on text inputs', function() { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.name).toBeUndefined(); - browserTrigger(inputElm, 'blur'); - expect(scope.name).toEqual('a'); - }); - - it('should not dirty the input if nothing was changed before updateOn trigger', function() { - compileInput( - ''); - - browserTrigger(inputElm, 'blur'); - expect(scope.form.alias.$pristine).toBeTruthy(); - }); - - it('should allow overriding the model update trigger event on text areas', function() { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.name).toBeUndefined(); - browserTrigger(inputElm, 'blur'); - expect(scope.name).toEqual('a'); - }); - - it('should bind the element to a list of events', function() { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.name).toBeUndefined(); - browserTrigger(inputElm, 'blur'); - expect(scope.name).toEqual('a'); - - changeInputValueTo('b'); - expect(scope.name).toEqual('a'); - browserTrigger(inputElm, 'mousemove'); - expect(scope.name).toEqual('b'); + helper.changeInputValueTo(' a '); + expect($rootScope.name).toEqual('a'); }); - it('should allow keeping the default update behavior on text inputs', function() { - compileInput( - ''); + it('should update the model and not trim the value', function() { + var inputElm = helper.compileInput(''); - changeInputValueTo('a'); - expect(scope.name).toEqual('a'); - }); - - - it('should allow overriding the model update trigger event on checkboxes', function() { - compileInput( - ''); - - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(undefined); - - browserTrigger(inputElm, 'blur'); - expect(scope.checkbox).toBe(true); - - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(true); - }); - - - it('should allow keeping the default update behavior on checkboxes', function() { - compileInput( - ''); - - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(true); - - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(false); - }); - - - it('should allow overriding the model update trigger event on radio buttons', function() { - compileInput( - '' + - '' + - ''); - - scope.$apply("color = 'white'"); - browserTrigger(inputElm[2], 'click'); - expect(scope.color).toBe('white'); - - browserTrigger(inputElm[2], 'blur'); - expect(scope.color).toBe('blue'); - - }); - - - it('should allow keeping the default update behavior on radio buttons', function() { - compileInput( - '' + - '' + - ''); - - scope.$apply("color = 'white'"); - browserTrigger(inputElm[2], 'click'); - expect(scope.color).toBe('blue'); - }); - - - it('should trigger only after timeout in text inputs', inject(function($timeout) { - compileInput( - ''); - - changeInputValueTo('a'); - changeInputValueTo('b'); - changeInputValueTo('c'); - expect(scope.name).toEqual(undefined); - $timeout.flush(2000); - expect(scope.name).toEqual(undefined); - $timeout.flush(9000); - expect(scope.name).toEqual('c'); - })); - - - it('should trigger only after timeout in checkboxes', inject(function($timeout) { - compileInput( - ''); - - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(2000); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(9000); - expect(scope.checkbox).toBe(true); - })); - - - it('should trigger only after timeout in radio buttons', inject(function($timeout) { - compileInput( - '' + - '' + - ''); - - browserTrigger(inputElm[0], 'click'); - expect(scope.color).toBe('white'); - browserTrigger(inputElm[1], 'click'); - expect(scope.color).toBe('white'); - $timeout.flush(12000); - expect(scope.color).toBe('white'); - $timeout.flush(10000); - expect(scope.color).toBe('red'); - - })); - - it('should not trigger digest while debouncing', inject(function($timeout) { - compileInput( - ''); - - var watchSpy = jasmine.createSpy('watchSpy'); - scope.$watch(watchSpy); - - changeInputValueTo('a'); - expect(watchSpy).not.toHaveBeenCalled(); - - $timeout.flush(10000); - expect(watchSpy).toHaveBeenCalled(); - })); - - it('should allow selecting different debounce timeouts for each event', - inject(function($timeout) { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(6000); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(4000); - expect(scope.name).toEqual('a'); - changeInputValueTo('b'); - browserTrigger(inputElm, 'blur'); - $timeout.flush(4000); - expect(scope.name).toEqual('a'); - $timeout.flush(2000); - expect(scope.name).toEqual('b'); - })); - - - it('should allow selecting different debounce timeouts for each event on checkboxes', inject(function($timeout) { - compileInput(''); - - inputElm[0].checked = false; - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(8000); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(3000); - expect(scope.checkbox).toBe(true); - inputElm[0].checked = true; - browserTrigger(inputElm, 'click'); - browserTrigger(inputElm, 'blur'); - $timeout.flush(3000); - expect(scope.checkbox).toBe(true); - $timeout.flush(3000); - expect(scope.checkbox).toBe(false); - })); - - it('should allow selecting 0 for non-default debounce timeouts for each event on checkboxes', inject(function($timeout) { - compileInput(''); - - inputElm[0].checked = false; - browserTrigger(inputElm, 'click'); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(8000); - expect(scope.checkbox).toBe(undefined); - $timeout.flush(3000); - expect(scope.checkbox).toBe(true); - inputElm[0].checked = true; - browserTrigger(inputElm, 'click'); - browserTrigger(inputElm, 'blur'); - $timeout.flush(0); - expect(scope.checkbox).toBe(false); - })); - - it('should inherit model update settings from ancestor elements', inject(function($timeout) { - var doc = $compile( - '')(scope); - scope.$digest(); - - inputElm = doc.find('input').eq(0); - changeInputValueTo('a'); - expect(scope.name).toEqual(undefined); - browserTrigger(inputElm, 'blur'); - expect(scope.name).toBe(undefined); - $timeout.flush(2000); - expect(scope.name).toBe(undefined); - $timeout.flush(9000); - expect(scope.name).toEqual('a'); - dealoc(doc); - })); - - it('should flush debounced events when calling $commitViewValue directly', function() { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.name).toEqual(undefined); - scope.form.alias.$commitViewValue(); - expect(scope.name).toEqual('a'); - }); - - it('should cancel debounced events when calling $commitViewValue', inject(function($timeout) { - compileInput( - ''); - - changeInputValueTo('a'); - scope.form.alias.$commitViewValue(); - expect(scope.name).toEqual('a'); - - scope.form.alias.$setPristine(); - $timeout.flush(1000); - expect(scope.form.alias.$pristine).toBeTruthy(); - })); - - it('should reset input val if rollbackViewValue called during pending update', function() { - compileInput( - ''); - - changeInputValueTo('a'); - expect(inputElm.val()).toBe('a'); - scope.form.alias.$rollbackViewValue(); - expect(inputElm.val()).toBe(''); - browserTrigger(inputElm, 'blur'); - expect(inputElm.val()).toBe(''); - }); - - it('should allow canceling pending updates', inject(function($timeout) { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.name).toEqual(undefined); - scope.form.alias.$rollbackViewValue(); - expect(scope.name).toEqual(undefined); - browserTrigger(inputElm, 'blur'); - expect(scope.name).toEqual(undefined); - })); - - it('should allow canceling debounced updates', inject(function($timeout) { - compileInput( - ''); - - changeInputValueTo('a'); - expect(scope.name).toEqual(undefined); - $timeout.flush(2000); - scope.form.alias.$rollbackViewValue(); - expect(scope.name).toEqual(undefined); - $timeout.flush(10000); - expect(scope.name).toEqual(undefined); - })); - - it('should handle model updates correctly even if rollbackViewValue is not invoked', function() { - compileInput( - ''); - - changeInputValueTo('a'); - scope.$apply("name = 'b'"); - browserTrigger(inputElm, 'blur'); - expect(scope.name).toBe('b'); - }); - - it('should reset input val if rollbackViewValue called during debounce', inject(function($timeout) { - compileInput( - ''); - - changeInputValueTo('a'); - expect(inputElm.val()).toBe('a'); - scope.form.alias.$rollbackViewValue(); - expect(inputElm.val()).toBe(''); - $timeout.flush(3000); - expect(inputElm.val()).toBe(''); - })); - - it('should not try to invoke a model if getterSetter is false', function() { - compileInput( - ''); - - var spy = scope.name = jasmine.createSpy('setterSpy'); - changeInputValueTo('a'); - expect(spy).not.toHaveBeenCalled(); - expect(inputElm.val()).toBe('a'); - }); - - it('should not try to invoke a model if getterSetter is not set', function() { - compileInput(''); - - var spy = scope.name = jasmine.createSpy('setterSpy'); - changeInputValueTo('a'); - expect(spy).not.toHaveBeenCalled(); - expect(inputElm.val()).toBe('a'); - }); - - it('should try to invoke a function model if getterSetter is true', function() { - compileInput( - ''); - - var spy = scope.name = jasmine.createSpy('setterSpy').andCallFake(function() { - return 'b'; - }); - scope.$apply(); - expect(inputElm.val()).toBe('b'); - - changeInputValueTo('a'); - expect(inputElm.val()).toBe('b'); - expect(spy).toHaveBeenCalledWith('a'); - expect(scope.name).toBe(spy); - }); - - it('should assign to non-function models if getterSetter is true', function() { - compileInput( - ''); - - scope.name = 'c'; - changeInputValueTo('d'); - expect(inputElm.val()).toBe('d'); - expect(scope.name).toBe('d'); - }); - - it('should fail on non-assignable model binding if getterSetter is false', function() { - expect(function() { - compileInput(''); - }).toThrowMinErr('ngModel', 'nonassign', 'Expression \'accessor(user, \'name\')\' is non-assignable.'); - }); - - it('should not fail on non-assignable model binding if getterSetter is true', function() { - compileInput( - ''); - }); - - it('should invoke a model in the correct context if getterSetter is true', function() { - compileInput( - ''); - - scope.someService = { - value: 'a', - getterSetter: function(newValue) { - this.value = newValue || this.value; - return this.value; - } - }; - spyOn(scope.someService, 'getterSetter').andCallThrough(); - scope.$apply(); - - expect(inputElm.val()).toBe('a'); - expect(scope.someService.getterSetter).toHaveBeenCalledWith(); - expect(scope.someService.value).toBe('a'); - - changeInputValueTo('b'); - expect(scope.someService.getterSetter).toHaveBeenCalledWith('b'); - expect(scope.someService.value).toBe('b'); - - scope.someService.value = 'c'; - scope.$apply(); - expect(inputElm.val()).toBe('c'); - expect(scope.someService.getterSetter).toHaveBeenCalledWith(); - }); - - it('should assign invalid values to the scope if allowInvalid is true', function() { - compileInput(''); - changeInputValueTo('12345'); - - expect(scope.value).toBe('12345'); - expect(inputElm).toBeInvalid(); - }); - - it('should not assign not parsable values to the scope if allowInvalid is true', function() { - compileInput('', { - valid: false, - badInput: true - }); - changeInputValueTo('abcd'); - - expect(scope.value).toBeUndefined(); - expect(inputElm).toBeInvalid(); - }); - - it('should update the scope before async validators execute if allowInvalid is true', inject(function($q) { - compileInput(''); - var defer; - scope.form.input.$asyncValidators.promiseValidator = function(value) { - defer = $q.defer(); - return defer.promise; - }; - changeInputValueTo('12345'); - - expect(scope.value).toBe('12345'); - expect(scope.form.input.$pending.promiseValidator).toBe(true); - defer.reject(); - scope.$digest(); - expect(scope.value).toBe('12345'); - expect(inputElm).toBeInvalid(); - })); - - it('should update the view before async validators execute if allowInvalid is true', inject(function($q) { - compileInput(''); - var defer; - scope.form.input.$asyncValidators.promiseValidator = function(value) { - defer = $q.defer(); - return defer.promise; - }; - scope.$apply('value = \'12345\''); - - expect(inputElm.val()).toBe('12345'); - expect(scope.form.input.$pending.promiseValidator).toBe(true); - defer.reject(); - scope.$digest(); - expect(inputElm.val()).toBe('12345'); - expect(inputElm).toBeInvalid(); - })); - - it('should not call ng-change listeners twice if the model did not change with allowInvalid', function() { - compileInput(''); - scope.changed = jasmine.createSpy('changed'); - scope.form.input.$parsers.push(function(value) { - return 'modelValue'; - }); - - changeInputValueTo('input1'); - expect(scope.value).toBe('modelValue'); - expect(scope.changed).toHaveBeenCalledOnce(); - - changeInputValueTo('input2'); - expect(scope.value).toBe('modelValue'); - expect(scope.changed).toHaveBeenCalledOnce(); + helper.changeInputValueTo(' a '); + expect($rootScope.name).toEqual(' a '); }); }); + it('should allow complex reference binding', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply("obj = { abc: { name: 'Misko'} }"); + $rootScope.$apply("obj = { abc: { name: 'Misko'} }"); expect(inputElm.val()).toEqual('Misko'); }); it('should ignore input without ngModel directive', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo(''); + helper.changeInputValueTo(''); expect(inputElm.hasClass('ng-valid')).toBe(false); expect(inputElm.hasClass('ng-invalid')).toBe(false); expect(inputElm.hasClass('ng-pristine')).toBe(false); @@ -2484,31 +478,31 @@ describe('input', function() { it('should report error on assignment error', function() { expect(function() { - compileInput(''); + var inputElm = helper.compileInput(''); }).toThrowMinErr("$parse", "syntax", "Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at ['']."); }); it("should render as blank if null", function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('age = null'); + $rootScope.$apply('age = null'); - expect(scope.age).toBeNull(); + expect($rootScope.age).toBeNull(); expect(inputElm.val()).toEqual(''); }); it('should render 0 even if it is a number', function() { - compileInput(''); - scope.$apply('value = 0'); + var inputElm = helper.compileInput(''); + $rootScope.$apply('value = 0'); expect(inputElm.val()).toBe('0'); }); it('should render the $viewValue when $modelValue is empty', function() { - compileInput(''); + var inputElm = helper.compileInput(''); var ctrl = inputElm.controller('ngModel'); @@ -2523,407 +517,35 @@ describe('input', function() { }); - describe('pattern', function() { - - it('should validate in-lined pattern', function() { - compileInput(''); - - changeInputValueTo('x000-00-0000x'); - expect(inputElm).toBeInvalid(); - - changeInputValueTo('000-00-0000'); - expect(inputElm).toBeValid(); - - changeInputValueTo('000-00-0000x'); - expect(inputElm).toBeInvalid(); - - changeInputValueTo('123-45-6789'); - expect(inputElm).toBeValid(); - - changeInputValueTo('x'); - expect(inputElm).toBeInvalid(); - }); - - it('should listen on ng-pattern when pattern is observed', function() { - var value, patternVal = /^\w+$/; - compileInput(''); - attrs.$observe('pattern', function(v) { - value = attrs.pattern; - }); - - scope.$apply(function() { - scope.pat = patternVal; - }); - - expect(value).toBe(patternVal); - }); - - it('should validate in-lined pattern with modifiers', function() { - compileInput(''); - - changeInputValueTo('aB'); - expect(inputElm).toBeValid(); - - changeInputValueTo('xx'); - expect(inputElm).toBeInvalid(); - }); - - - it('should validate pattern from scope', function() { - scope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/; - compileInput(''); - - changeInputValueTo('x000-00-0000x'); - expect(inputElm).toBeInvalid(); - - changeInputValueTo('000-00-0000'); - expect(inputElm).toBeValid(); - - changeInputValueTo('000-00-0000x'); - expect(inputElm).toBeInvalid(); - - changeInputValueTo('123-45-6789'); - expect(inputElm).toBeValid(); - - changeInputValueTo('x'); - expect(inputElm).toBeInvalid(); - - scope.$apply(function() { - scope.regexp = /abc?/; - }); - - changeInputValueTo('ab'); - expect(inputElm).toBeValid(); - - changeInputValueTo('xx'); - expect(inputElm).toBeInvalid(); - }); - - it('should perform validations when the ngPattern scope value changes', function() { - scope.regexp = /^[a-z]+$/; - compileInput(''); - - changeInputValueTo('abcdef'); - expect(inputElm).toBeValid(); - - changeInputValueTo('123'); - expect(inputElm).toBeInvalid(); - - scope.$apply(function() { - scope.regexp = /^\d+$/; - }); - - expect(inputElm).toBeValid(); - - changeInputValueTo('abcdef'); - expect(inputElm).toBeInvalid(); - - scope.$apply(function() { - scope.regexp = ''; - }); - - expect(inputElm).toBeValid(); - }); - - it('should register "pattern" with the model validations when the pattern attribute is used', function() { - compileInput(''); - - changeInputValueTo('abcd'); - expect(inputElm).toBeInvalid(); - expect(scope.form.input.$error.pattern).toBe(true); - - changeInputValueTo('12345'); - expect(inputElm).toBeValid(); - expect(scope.form.input.$error.pattern).not.toBe(true); - }); - - it('should not throw an error when scope pattern can\'t be found', function() { - expect(function() { - compileInput(''); - scope.$apply("foo = 'bar'"); - }).not.toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/); - }); - - it('should throw an error when the scope pattern is not a regular expression', function() { - expect(function() { - compileInput(''); - scope.$apply(function() { - scope.fooRegexp = {}; - scope.foo = 'bar'; - }); - }).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/); - }); - - it('should be invalid if entire string does not match pattern', function() { - compileInput(''); - changeInputValueTo('1234'); - expect(scope.form.test.$error.pattern).not.toBe(true); - expect(inputElm).toBeValid(); - - changeInputValueTo('123'); - expect(scope.form.test.$error.pattern).toBe(true); - expect(inputElm).not.toBeValid(); - - changeInputValueTo('12345'); - expect(scope.form.test.$error.pattern).toBe(true); - expect(inputElm).not.toBeValid(); - }); - - - it('should be cope with patterns that start with ^', function() { - compileInput(''); - changeInputValueTo('1234'); - expect(scope.form.test.$error.pattern).not.toBe(true); - expect(inputElm).toBeValid(); - - changeInputValueTo('123'); - expect(scope.form.test.$error.pattern).toBe(true); - expect(inputElm).not.toBeValid(); - - changeInputValueTo('12345'); - expect(scope.form.test.$error.pattern).toBe(true); - expect(inputElm).not.toBeValid(); - }); - - - it('should be cope with patterns that end with $', function() { - compileInput(''); - changeInputValueTo('1234'); - expect(scope.form.test.$error.pattern).not.toBe(true); - expect(inputElm).toBeValid(); - - changeInputValueTo('123'); - expect(scope.form.test.$error.pattern).toBe(true); - expect(inputElm).not.toBeValid(); - - changeInputValueTo('12345'); - expect(scope.form.test.$error.pattern).toBe(true); - expect(inputElm).not.toBeValid(); - }); - }); - - - describe('minlength', function() { - - it('should invalidate values that are shorter than the given minlength', function() { - compileInput(''); - - changeInputValueTo('aa'); - expect(inputElm).toBeInvalid(); - - changeInputValueTo('aaa'); - expect(inputElm).toBeValid(); - }); - - it('should listen on ng-minlength when minlength is observed', function() { - var value = 0; - compileInput(''); - attrs.$observe('minlength', function(v) { - value = int(attrs.minlength); - }); - - scope.$apply('min = 5'); - - expect(value).toBe(5); - }); - - it('should observe the standard minlength attribute and register it as a validator on the model', function() { - compileInput(''); - scope.$apply('min = 10'); - - changeInputValueTo('12345'); - expect(inputElm).toBeInvalid(); - expect(scope.form.input.$error.minlength).toBe(true); - - scope.$apply('min = 5'); - - expect(inputElm).toBeValid(); - expect(scope.form.input.$error.minlength).not.toBe(true); - }); - - it('should validate when the model is initalized as a number', function() { - scope.value = 12345; - compileInput(''); - expect(scope.value).toBe(12345); - expect(scope.form.input.$error.minlength).toBeUndefined(); - }); - - }); - - - describe('maxlength', function() { - - it('should invalidate values that are longer than the given maxlength', function() { - compileInput(''); - - changeInputValueTo('aaaaaaaa'); - expect(inputElm).toBeInvalid(); - - changeInputValueTo('aaa'); - expect(inputElm).toBeValid(); - }); - - it('should only accept empty values when maxlength is 0', function() { - compileInput(''); - - changeInputValueTo(''); - expect(inputElm).toBeValid(); - - changeInputValueTo('a'); - expect(inputElm).toBeInvalid(); - }); - - it('should accept values of any length when maxlength is negative', function() { - compileInput(''); - - changeInputValueTo(''); - expect(inputElm).toBeValid(); - - changeInputValueTo('aaaaaaaaaa'); - expect(inputElm).toBeValid(); - }); - - it('should accept values of any length when maxlength is non-numeric', function() { - compileInput(''); - changeInputValueTo('aaaaaaaaaa'); - - scope.$apply('maxlength = "5"'); - expect(inputElm).toBeInvalid(); - - scope.$apply('maxlength = "abc"'); - expect(inputElm).toBeValid(); - - scope.$apply('maxlength = ""'); - expect(inputElm).toBeValid(); - - scope.$apply('maxlength = null'); - expect(inputElm).toBeValid(); - - scope.someObj = {}; - scope.$apply('maxlength = someObj'); - expect(inputElm).toBeValid(); - }); - - it('should listen on ng-maxlength when maxlength is observed', function() { - var value = 0; - compileInput(''); - attrs.$observe('maxlength', function(v) { - value = int(attrs.maxlength); - }); - - scope.$apply('max = 10'); - - expect(value).toBe(10); - }); - - it('should observe the standard maxlength attribute and register it as a validator on the model', function() { - compileInput(''); - scope.$apply('max = 1'); - - changeInputValueTo('12345'); - expect(inputElm).toBeInvalid(); - expect(scope.form.input.$error.maxlength).toBe(true); - - scope.$apply('max = 6'); - - expect(inputElm).toBeValid(); - expect(scope.form.input.$error.maxlength).not.toBe(true); - }); - - it('should assign the correct model after an observed validator became valid', function() { - compileInput(''); - - scope.$apply('max = 1'); - changeInputValueTo('12345'); - expect(scope.value).toBeUndefined(); - - scope.$apply('max = 6'); - expect(scope.value).toBe('12345'); - }); - - it('should assign the correct model after an observed validator became invalid', function() { - compileInput(''); - - scope.$apply('max = 6'); - changeInputValueTo('12345'); - expect(scope.value).toBe('12345'); - - scope.$apply('max = 1'); - expect(scope.value).toBeUndefined(); - }); - - it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() { - compileInput(''); - scope.$apply('max = 1'); - - changeInputValueTo('12345'); - expect(inputElm).toBeInvalid(); - expect(scope.form.input.$error.maxlength).toBe(true); - expect(scope.value).toBeUndefined(); - - scope.$apply('max = 3'); - - expect(inputElm).toBeInvalid(); - expect(scope.form.input.$error.maxlength).toBe(true); - expect(scope.value).toBeUndefined(); - }); - - it('should not notify if observed maxlength changed, but is still invalid', function() { - compileInput(''); - - scope.$apply('max = 1'); - changeInputValueTo('12345'); - - scope.ngChangeSpy = jasmine.createSpy(); - scope.$apply('max = 3'); - - expect(scope.ngChangeSpy).not.toHaveBeenCalled(); - }); - - it('should leave the model untouched when validating before model initialization', function() { - scope.value = '12345'; - compileInput(''); - expect(scope.value).toBe('12345'); - }); - - it('should validate when the model is initalized as a number', function() { - scope.value = 12345; - compileInput(''); - expect(scope.value).toBe(12345); - expect(scope.form.input.$error.maxlength).toBeUndefined(); - }); - - }); - - // INPUT TYPES describe('month', function() { it('should throw if model is not a Date object', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(function() { - scope.$apply(function() { - scope.january = '2013-01'; + $rootScope.$apply(function() { + $rootScope.january = '2013-01'; }); }).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-01` to be a date'); }); - it('should set the view if the model is a valid Date object', function() { - compileInput(''); - scope.$apply(function() { - scope.march = new Date(2013, 2, 1); + it('should set the view if the model is a valid Date object', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.march = new Date(2013, 2, 1); }); expect(inputElm.val()).toBe('2013-03'); }); - it('should set the model undefined if the input is an invalid month string', function() { - compileInput(''); - scope.$apply(function() { - scope.value = new Date(2013, 0, 1); + it('should set the model undefined if the input is an invalid month string', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.value = new Date(2013, 0, 1); }); @@ -2936,188 +558,195 @@ describe('input', function() { //for IE8 } - changeInputValueTo('stuff'); + helper.changeInputValueTo('stuff'); expect(inputElm.val()).toBe('stuff'); - expect(scope.value).toBeUndefined(); + expect($rootScope.value).toBeUndefined(); expect(inputElm).toBeInvalid(); }); + it('should render as blank if null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toEqual(''); }); + it('should come up blank when no value specified', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply(function() { - scope.test = new Date(2011, 0, 1); + $rootScope.$apply(function() { + $rootScope.test = new Date(2011, 0, 1); }); - changeInputValueTo(''); - expect(scope.test).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.test).toBeNull(); expect(inputElm).toBeValid(); }); + it('should use UTC if specified in the options', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('2013-07'); - expect(+scope.value).toBe(Date.UTC(2013, 6, 1)); + helper.changeInputValueTo('2013-07'); + expect(+$rootScope.value).toBe(Date.UTC(2013, 6, 1)); - scope.$apply(function() { - scope.value = new Date(Date.UTC(2014, 6, 1)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(2014, 6, 1)); }); expect(inputElm.val()).toBe('2014-07'); }); + it('should label parse errors as `month`', function() { - compileInput('', { + var inputElm = helper.compileInput('', { valid: false, badInput: true }); - changeInputValueTo('xxx'); + helper.changeInputValueTo('xxx'); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.month).toBeTruthy(); + expect($rootScope.form.alias.$error.month).toBeTruthy(); }); - it('should only change the month of a bound date', function() { - compileInput(''); - scope.$apply(function() { - scope.value = new Date(Date.UTC(2013, 7, 1, 1, 0, 0, 0)); + it('should only change the month of a bound date', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(2013, 7, 1, 1, 0, 0, 0)); }); - changeInputValueTo('2013-12'); - expect(+scope.value).toBe(Date.UTC(2013, 11, 1, 1, 0, 0, 0)); + helper.changeInputValueTo('2013-12'); + expect(+$rootScope.value).toBe(Date.UTC(2013, 11, 1, 1, 0, 0, 0)); expect(inputElm.val()).toBe('2013-12'); }); describe('min', function() { - var scope; - beforeEach(inject(function($rootScope) { - scope = $rootScope; + var inputElm; + beforeEach(function() { $rootScope.minVal = '2013-01'; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should invalidate', function() { - changeInputValueTo('2012-12'); + helper.changeInputValueTo('2012-12'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2013-07'); + helper.changeInputValueTo('2013-07'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2013, 6, 1)); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2013, 6, 1)); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); it('should revalidate when the min value changes', function() { - changeInputValueTo('2013-07'); + helper.changeInputValueTo('2013-07'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeFalsy(); - scope.minVal = '2014-01'; - scope.$digest(); + $rootScope.minVal = '2014-01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); }); describe('max', function() { - var scope; - beforeEach(inject(function($rootScope) { - scope = $rootScope; + var inputElm; + beforeEach(function() { $rootScope.maxVal = '2013-01'; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should validate', function() { - changeInputValueTo('2012-03'); + helper.changeInputValueTo('2012-03'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2012, 2, 1)); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2012, 2, 1)); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); it('should invalidate', function() { - changeInputValueTo('2013-05'); + helper.changeInputValueTo('2013-05'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeUndefined(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeUndefined(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); it('should revalidate when the max value changes', function() { - changeInputValueTo('2012-07'); + helper.changeInputValueTo('2012-07'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect($rootScope.form.alias.$error.max).toBeFalsy(); - scope.maxVal = '2012-01'; - scope.$digest(); + $rootScope.maxVal = '2012-01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); }); }); + describe('week', function() { it('should throw if model is not a Date object', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(function() { - scope.$apply(function() { - scope.secondWeek = '2013-W02'; + $rootScope.$apply(function() { + $rootScope.secondWeek = '2013-W02'; }); }).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-W02` to be a date'); }); - it('should set the view if the model is a valid Date object', function() { - compileInput(''); - scope.$apply(function() { - scope.secondWeek = new Date(2013, 0, 11); + it('should set the view if the model is a valid Date object', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.secondWeek = new Date(2013, 0, 11); }); expect(inputElm.val()).toBe('2013-W02'); }); - it('should not affect the hours or minutes of a bound date', function() { - compileInput(''); - scope.$apply(function() { - scope.secondWeek = new Date(2013, 0, 11, 1, 0, 0, 0); + it('should not affect the hours or minutes of a bound date', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.secondWeek = new Date(2013, 0, 11, 1, 0, 0, 0); }); - changeInputValueTo('2013-W03'); + helper.changeInputValueTo('2013-W03'); - expect(+scope.secondWeek).toBe(+new Date(2013, 0, 17, 1, 0, 0, 0)); + expect(+$rootScope.secondWeek).toBe(+new Date(2013, 0, 17, 1, 0, 0, 0)); }); - it('should set the model undefined if the input is an invalid week string', function() { - compileInput(''); - scope.$apply(function() { - scope.value = new Date(2013, 0, 11); + it('should set the model undefined if the input is an invalid week string', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.value = new Date(2013, 0, 11); }); @@ -3130,164 +759,171 @@ describe('input', function() { //for IE8 } - changeInputValueTo('stuff'); + helper.changeInputValueTo('stuff'); expect(inputElm.val()).toBe('stuff'); - expect(scope.value).toBeUndefined(); + expect($rootScope.value).toBeUndefined(); expect(inputElm).toBeInvalid(); }); + it('should render as blank if null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toEqual(''); }); + it('should come up blank when no value specified', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply(function() { - scope.test = new Date(2011, 0, 1); + $rootScope.$apply(function() { + $rootScope.test = new Date(2011, 0, 1); }); - changeInputValueTo(''); - expect(scope.test).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.test).toBeNull(); expect(inputElm).toBeValid(); }); + it('should use UTC if specified in the options', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('2013-W03'); - expect(+scope.value).toBe(Date.UTC(2013, 0, 17)); + helper.changeInputValueTo('2013-W03'); + expect(+$rootScope.value).toBe(Date.UTC(2013, 0, 17)); - scope.$apply(function() { - scope.value = new Date(Date.UTC(2014, 0, 17)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(2014, 0, 17)); }); expect(inputElm.val()).toBe('2014-W03'); }); + it('should label parse errors as `week`', function() { - compileInput('', { + var inputElm = helper.compileInput('', { valid: false, badInput: true }); - changeInputValueTo('yyy'); + helper.changeInputValueTo('yyy'); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.week).toBeTruthy(); + expect($rootScope.form.alias.$error.week).toBeTruthy(); }); describe('min', function() { - var scope; - beforeEach(inject(function($rootScope) { - scope = $rootScope; + var inputElm; + beforeEach(function() { $rootScope.minVal = '2013-W01'; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should invalidate', function() { - changeInputValueTo('2012-W12'); + helper.changeInputValueTo('2012-W12'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2013-W03'); + helper.changeInputValueTo('2013-W03'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2013, 0, 17)); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2013, 0, 17)); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); it('should revalidate when the min value changes', function() { - changeInputValueTo('2013-W03'); + helper.changeInputValueTo('2013-W03'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeFalsy(); - scope.minVal = '2014-W01'; - scope.$digest(); + $rootScope.minVal = '2014-W01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); }); describe('max', function() { - beforeEach(inject(function($rootScope) { + var inputElm; + + beforeEach(function() { $rootScope.maxVal = '2013-W01'; - scope = $rootScope; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should validate', function() { - changeInputValueTo('2012-W01'); + helper.changeInputValueTo('2012-W01'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2012, 0, 5)); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2012, 0, 5)); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); it('should invalidate', function() { - changeInputValueTo('2013-W03'); + helper.changeInputValueTo('2013-W03'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeUndefined(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeUndefined(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); it('should revalidate when the max value changes', function() { - changeInputValueTo('2012-W03'); + helper.changeInputValueTo('2012-W03'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect($rootScope.form.alias.$error.max).toBeFalsy(); - scope.maxVal = '2012-W01'; - scope.$digest(); + $rootScope.maxVal = '2012-W01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); }); }); + describe('datetime-local', function() { it('should throw if model is not a Date object', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(function() { - scope.$apply(function() { - scope.lunchtime = '2013-12-16T11:30:00'; + $rootScope.$apply(function() { + $rootScope.lunchtime = '2013-12-16T11:30:00'; }); }).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-12-16T11:30:00` to be a date'); }); - it('should set the view if the model if a valid Date object.', function() { - compileInput(''); - scope.$apply(function() { - scope.halfSecondToNextYear = new Date(2013, 11, 31, 23, 59, 59, 500); + it('should set the view if the model if a valid Date object.', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.halfSecondToNextYear = new Date(2013, 11, 31, 23, 59, 59, 500); }); expect(inputElm.val()).toBe('2013-12-31T23:59:59.500'); }); - it('should set the model undefined if the view is invalid', function() { - compileInput(''); - scope.$apply(function() { - scope.breakMe = new Date(2009, 0, 6, 16, 25, 0); + it('should set the model undefined if the view is invalid', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.breakMe = new Date(2009, 0, 6, 16, 25, 0); }); expect(inputElm.val()).toBe('2009-01-06T16:25:00.000'); @@ -3299,270 +935,283 @@ describe('input', function() { //for IE8 } - changeInputValueTo('stuff'); + helper.changeInputValueTo('stuff'); expect(inputElm.val()).toBe('stuff'); - expect(scope.breakMe).toBeUndefined(); + expect($rootScope.breakMe).toBeUndefined(); expect(inputElm).toBeInvalid(); }); + it('should render as blank if null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toEqual(''); }); + it('should come up blank when no value specified', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply(function() { - scope.test = new Date(2011, 0, 1); + $rootScope.$apply(function() { + $rootScope.test = new Date(2011, 0, 1); }); - changeInputValueTo(''); - expect(scope.test).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.test).toBeNull(); expect(inputElm).toBeValid(); }); + it('should use UTC if specified in the options', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('2000-01-01T01:02'); - expect(+scope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0)); + helper.changeInputValueTo('2000-01-01T01:02'); + expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1, 1, 2, 0)); - scope.$apply(function() { - scope.value = new Date(Date.UTC(2001, 0, 1, 1, 2, 0)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(2001, 0, 1, 1, 2, 0)); }); expect(inputElm.val()).toBe('2001-01-01T01:02:00.000'); }); - it('should allow to specify the milliseconds', function() { - compileInput(''); - changeInputValueTo('2000-01-01T01:02:03.500'); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3, 500)); + it('should allow to specify the milliseconds', function() { + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('2000-01-01T01:02:03.500'); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3, 500)); }); + it('should allow to specify single digit milliseconds', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('2000-01-01T01:02:03.4'); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3, 400)); + helper.changeInputValueTo('2000-01-01T01:02:03.4'); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3, 400)); }); + it('should allow to specify the seconds', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('2000-01-01T01:02:03'); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3)); + helper.changeInputValueTo('2000-01-01T01:02:03'); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3)); - scope.$apply(function() { - scope.value = new Date(2001, 0, 1, 1, 2, 3); + $rootScope.$apply(function() { + $rootScope.value = new Date(2001, 0, 1, 1, 2, 3); }); expect(inputElm.val()).toBe('2001-01-01T01:02:03.000'); }); - it('should allow to skip the seconds', function() { - compileInput(''); - changeInputValueTo('2000-01-01T01:02'); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 0)); + it('should allow to skip the seconds', function() { + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('2000-01-01T01:02'); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 1, 2, 0)); }); + it('should label parse errors as `datetimelocal`', function() { - compileInput('', { + var inputElm = helper.compileInput('', { valid: false, badInput: true }); - changeInputValueTo('zzz'); + helper.changeInputValueTo('zzz'); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.datetimelocal).toBeTruthy(); + expect($rootScope.form.alias.$error.datetimelocal).toBeTruthy(); }); describe('min', function() { - var scope; - beforeEach(inject(function($rootScope) { + var inputElm; + beforeEach(function() { $rootScope.minVal = '2000-01-01T12:30:00'; - scope = $rootScope; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should invalidate', function() { - changeInputValueTo('1999-12-31T01:02:00'); + helper.changeInputValueTo('1999-12-31T01:02:00'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2000-01-01T23:02:00'); + helper.changeInputValueTo('2000-01-01T23:02:00'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2, 0)); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 23, 2, 0)); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); it('should revalidate when the min value changes', function() { - changeInputValueTo('2000-02-01T01:02:00'); + helper.changeInputValueTo('2000-02-01T01:02:00'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeFalsy(); - scope.minVal = '2010-01-01T01:02:00'; - scope.$digest(); + $rootScope.minVal = '2010-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); }); describe('max', function() { - var scope; - beforeEach(inject(function($rootScope) { + var inputElm; + beforeEach(function() { $rootScope.maxVal = '2019-01-01T01:02:00'; - scope = $rootScope; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should invalidate', function() { - changeInputValueTo('2019-12-31T01:02:00'); + helper.changeInputValueTo('2019-12-31T01:02:00'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2000-01-01T01:02:00'); + helper.changeInputValueTo('2000-01-01T01:02:00'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 0)); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 1, 2, 0)); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); it('should revalidate when the max value changes', function() { - changeInputValueTo('2000-02-01T01:02:00'); + helper.changeInputValueTo('2000-02-01T01:02:00'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect($rootScope.form.alias.$error.max).toBeFalsy(); - scope.maxVal = '2000-01-01T01:02:00'; - scope.$digest(); + $rootScope.maxVal = '2000-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); }); + it('should validate even if max value changes on-the-fly', function() { - scope.max = '2013-01-01T01:02:00'; - compileInput(''); + $rootScope.max = '2013-01-01T01:02:00'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2014-01-01T12:34:00'); + helper.changeInputValueTo('2014-01-01T12:34:00'); expect(inputElm).toBeInvalid(); - scope.max = '2001-01-01T01:02:00'; - scope.$digest(); + $rootScope.max = '2001-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = '2024-01-01T01:02:00'; - scope.$digest(); + $rootScope.max = '2024-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if min value changes on-the-fly', function() { - scope.min = '2013-01-01T01:02:00'; - compileInput(''); + $rootScope.min = '2013-01-01T01:02:00'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2010-01-01T12:34:00'); + helper.changeInputValueTo('2010-01-01T12:34:00'); expect(inputElm).toBeInvalid(); - scope.min = '2014-01-01T01:02:00'; - scope.$digest(); + $rootScope.min = '2014-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = '2009-01-01T01:02:00'; - scope.$digest(); + $rootScope.min = '2009-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if ng-max value changes on-the-fly', function() { - scope.max = '2013-01-01T01:02:00'; - compileInput(''); + $rootScope.max = '2013-01-01T01:02:00'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2014-01-01T12:34:00'); + helper.changeInputValueTo('2014-01-01T12:34:00'); expect(inputElm).toBeInvalid(); - scope.max = '2001-01-01T01:02:00'; - scope.$digest(); + $rootScope.max = '2001-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = '2024-01-01T01:02:00'; - scope.$digest(); + $rootScope.max = '2024-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if ng-min value changes on-the-fly', function() { - scope.min = '2013-01-01T01:02:00'; - compileInput(''); + $rootScope.min = '2013-01-01T01:02:00'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2010-01-01T12:34:00'); + helper.changeInputValueTo('2010-01-01T12:34:00'); expect(inputElm).toBeInvalid(); - scope.min = '2014-01-01T01:02:00'; - scope.$digest(); + $rootScope.min = '2014-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = '2009-01-01T01:02:00'; - scope.$digest(); + $rootScope.min = '2009-01-01T01:02:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); + describe('time', function() { it('should throw if model is not a Date object', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(function() { - scope.$apply(function() { - scope.lunchtime = '11:30:00'; + $rootScope.$apply(function() { + $rootScope.lunchtime = '11:30:00'; }); }).toThrowMinErr('ngModel', 'datefmt', 'Expected `11:30:00` to be a date'); }); - it('should set the view if the model if a valid Date object.', function() { - compileInput(''); - scope.$apply(function() { - scope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 500); + it('should set the view if the model if a valid Date object.', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 500); }); expect(inputElm.val()).toBe('15:41:00.500'); }); - it('should set the model undefined if the view is invalid', function() { - compileInput(''); - scope.$apply(function() { - scope.breakMe = new Date(1970, 0, 1, 16, 25, 0); + it('should set the model undefined if the view is invalid', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.breakMe = new Date(1970, 0, 1, 16, 25, 0); }); expect(inputElm.val()).toBe('16:25:00.000'); @@ -3574,259 +1223,276 @@ describe('input', function() { //for IE8 } - changeInputValueTo('stuff'); + helper.changeInputValueTo('stuff'); expect(inputElm.val()).toBe('stuff'); - expect(scope.breakMe).toBeUndefined(); + expect($rootScope.breakMe).toBeUndefined(); expect(inputElm).toBeInvalid(); }); + it('should render as blank if null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toEqual(''); }); + it('should come up blank when no value specified', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply(function() { - scope.test = new Date(2011, 0, 1); + $rootScope.$apply(function() { + $rootScope.test = new Date(2011, 0, 1); }); - changeInputValueTo(''); - expect(scope.test).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.test).toBeNull(); expect(inputElm).toBeValid(); }); + it('should use UTC if specified in the options', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('23:02:00'); - expect(+scope.value).toBe(Date.UTC(1970, 0, 1, 23, 2, 0)); + helper.changeInputValueTo('23:02:00'); + expect(+$rootScope.value).toBe(Date.UTC(1970, 0, 1, 23, 2, 0)); - scope.$apply(function() { - scope.value = new Date(Date.UTC(1971, 0, 1, 23, 2, 0)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(1971, 0, 1, 23, 2, 0)); }); expect(inputElm.val()).toBe('23:02:00.000'); }); - it('should allow to specify the milliseconds', function() { - compileInput(''); - changeInputValueTo('01:02:03.500'); - expect(+scope.value).toBe(+new Date(1970, 0, 1, 1, 2, 3, 500)); + it('should allow to specify the milliseconds', function() { + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('01:02:03.500'); + expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 1, 2, 3, 500)); }); + it('should allow to specify single digit milliseconds', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('01:02:03.4'); - expect(+scope.value).toBe(+new Date(1970, 0, 1, 1, 2, 3, 400)); + helper.changeInputValueTo('01:02:03.4'); + expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 1, 2, 3, 400)); }); + it('should allow to specify the seconds', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('01:02:03'); - expect(+scope.value).toBe(+new Date(1970, 0, 1, 1, 2, 3)); + helper.changeInputValueTo('01:02:03'); + expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 1, 2, 3)); - scope.$apply(function() { - scope.value = new Date(1970, 0, 1, 1, 2, 3); + $rootScope.$apply(function() { + $rootScope.value = new Date(1970, 0, 1, 1, 2, 3); }); expect(inputElm.val()).toBe('01:02:03.000'); }); - it('should allow to skip the seconds', function() { - compileInput(''); - changeInputValueTo('01:02'); - expect(+scope.value).toBe(+new Date(1970, 0, 1, 1, 2, 0)); + it('should allow to skip the seconds', function() { + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('01:02'); + expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 1, 2, 0)); }); + it('should label parse errors as `time`', function() { - compileInput('', { + var inputElm = helper.compileInput('', { valid: false, badInput: true }); - changeInputValueTo('mmm'); + helper.changeInputValueTo('mmm'); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.time).toBeTruthy(); + expect($rootScope.form.alias.$error.time).toBeTruthy(); }); - it('should only change hours and minute of a bound date', function() { - compileInput(''); - scope.$apply(function() { - scope.value = new Date(2013, 2, 3, 1, 0, 0); + it('should only change hours and minute of a bound date', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.value = new Date(2013, 2, 3, 1, 0, 0); }); - changeInputValueTo('01:02'); - expect(+scope.value).toBe(+new Date(2013, 2, 3, 1, 2, 0)); + helper.changeInputValueTo('01:02'); + expect(+$rootScope.value).toBe(+new Date(2013, 2, 3, 1, 2, 0)); }); describe('min', function() { - var scope; - beforeEach(inject(function($rootScope) { + var inputElm; + beforeEach(function() { $rootScope.minVal = '09:30:00'; - scope = $rootScope; - compileInput(''); - })); + inputElm = helper.compileInput(''); + }); it('should invalidate', function() { - changeInputValueTo('01:02:00'); + helper.changeInputValueTo('01:02:00'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('23:02:00'); + helper.changeInputValueTo('23:02:00'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(1970, 0, 1, 23, 2, 0)); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 23, 2, 0)); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); it('should revalidate when the min value changes', function() { - changeInputValueTo('23:02:00'); + helper.changeInputValueTo('23:02:00'); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeFalsy(); - scope.minVal = '23:55:00'; - scope.$digest(); + $rootScope.minVal = '23:55:00'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); }); describe('max', function() { + var inputElm; beforeEach(function() { - compileInput(''); + inputElm = helper.compileInput(''); }); it('should invalidate', function() { - changeInputValueTo('23:00:00'); + helper.changeInputValueTo('23:00:00'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('05:30:00'); + helper.changeInputValueTo('05:30:00'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(1970, 0, 1, 5, 30, 0)); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 5, 30, 0)); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); }); - it('should validate even if max value changes on-the-fly', function() { - scope.max = '4:02:00'; - compileInput(''); - changeInputValueTo('05:34:00'); + it('should validate even if max value changes on-the-fly', function() { + $rootScope.max = '4:02:00'; + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('05:34:00'); expect(inputElm).toBeInvalid(); - scope.max = '06:34:00'; - scope.$digest(); + $rootScope.max = '06:34:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if min value changes on-the-fly', function() { - scope.min = '08:45:00'; - compileInput(''); + $rootScope.min = '08:45:00'; + var inputElm = helper.compileInput(''); - changeInputValueTo('06:15:00'); + helper.changeInputValueTo('06:15:00'); expect(inputElm).toBeInvalid(); - scope.min = '05:50:00'; - scope.$digest(); + $rootScope.min = '05:50:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if ng-max value changes on-the-fly', function() { - scope.max = '4:02:00'; - compileInput(''); + $rootScope.max = '4:02:00'; + var inputElm = helper.compileInput(''); - changeInputValueTo('05:34:00'); + helper.changeInputValueTo('05:34:00'); expect(inputElm).toBeInvalid(); - scope.max = '06:34:00'; - scope.$digest(); + $rootScope.max = '06:34:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); - it('should validate even if ng-min value changes on-the-fly', function() { - scope.min = '08:45:00'; - compileInput(''); - changeInputValueTo('06:15:00'); + it('should validate even if ng-min value changes on-the-fly', function() { + $rootScope.min = '08:45:00'; + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('06:15:00'); expect(inputElm).toBeInvalid(); - scope.min = '05:50:00'; - scope.$digest(); + $rootScope.min = '05:50:00'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); + describe('date', function() { it('should throw if model is not a Date object.', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(function() { - scope.$apply(function() { - scope.birthday = '1977-10-22'; + $rootScope.$apply(function() { + $rootScope.birthday = '1977-10-22'; }); }).toThrowMinErr('ngModel', 'datefmt', 'Expected `1977-10-22` to be a date'); }); + it('should set the view to empty when the model is an InvalidDate', function() { - compileInput(''); + var inputElm = helper.compileInput(''); // reset the element type to text otherwise newer browsers // would always set the input.value to empty for invalid dates... inputElm.attr('type', 'text'); - scope.$apply(function() { - scope.val = new Date('a'); + $rootScope.$apply(function() { + $rootScope.val = new Date('a'); }); expect(inputElm.val()).toBe(''); }); - it('should set the view if the model if a valid Date object.', function() { - compileInput(''); - scope.$apply(function() { - scope.christmas = new Date(2013, 11, 25); + it('should set the view if the model if a valid Date object.', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.christmas = new Date(2013, 11, 25); }); expect(inputElm.val()).toBe('2013-12-25'); }); - it('should set the model undefined if the view is invalid', function() { - compileInput(''); - scope.$apply(function() { - scope.arrMatey = new Date(2014, 8, 14); + it('should set the model undefined if the view is invalid', function() { + var inputElm = helper.compileInput(''); + + $rootScope.$apply(function() { + $rootScope.arrMatey = new Date(2014, 8, 14); }); expect(inputElm.val()).toBe('2014-09-14'); @@ -3838,70 +1504,75 @@ describe('input', function() { //for IE8 } - changeInputValueTo('1-2-3'); + helper.changeInputValueTo('1-2-3'); expect(inputElm.val()).toBe('1-2-3'); - expect(scope.arrMatey).toBeUndefined(); + expect($rootScope.arrMatey).toBeUndefined(); expect(inputElm).toBeInvalid(); }); + it('should render as blank if null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toEqual(''); }); + it('should come up blank when no value specified', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe(''); - scope.$apply('test = null'); + $rootScope.$apply('test = null'); - expect(scope.test).toBeNull(); + expect($rootScope.test).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply(function() { - scope.test = new Date(2011, 0, 1); + $rootScope.$apply(function() { + $rootScope.test = new Date(2011, 0, 1); }); - changeInputValueTo(''); - expect(scope.test).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.test).toBeNull(); expect(inputElm).toBeValid(); }); + it('should use UTC if specified in the options', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('2000-01-01'); - expect(+scope.value).toBe(Date.UTC(2000, 0, 1)); + helper.changeInputValueTo('2000-01-01'); + expect(+$rootScope.value).toBe(Date.UTC(2000, 0, 1)); - scope.$apply(function() { - scope.value = new Date(Date.UTC(2001, 0, 1)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(2001, 0, 1)); }); expect(inputElm.val()).toBe('2001-01-01'); }); + it('should label parse errors as `date`', function() { - compileInput('', { + var inputElm = helper.compileInput('', { valid: false, badInput: true }); - changeInputValueTo('nnn'); + helper.changeInputValueTo('nnn'); expect(inputElm).toBeInvalid(); - expect(scope.form.alias.$error.date).toBeTruthy(); + expect($rootScope.form.alias.$error.date).toBeTruthy(); }); + it('should work with multiple date types bound to the same model', function() { - formElm = jqLite(''); + var formElm = jqLite(''); var timeElm = jqLite(''), monthElm = jqLite(''), @@ -3911,192 +1582,178 @@ describe('input', function() { formElm.append(monthElm); formElm.append(weekElm); - $compile(formElm)(scope); + $compile(formElm)($rootScope); - scope.$apply(function() { - scope.val = new Date(2013, 1, 2, 3, 4, 5, 6); + $rootScope.$apply(function() { + $rootScope.val = new Date(2013, 1, 2, 3, 4, 5, 6); }); expect(timeElm.val()).toBe('03:04:05.006'); expect(monthElm.val()).toBe('2013-02'); expect(weekElm.val()).toBe('2013-W05'); - changeGivenInputTo(monthElm, '2012-02'); + helper.changeGivenInputTo(monthElm, '2012-02'); expect(monthElm.val()).toBe('2012-02'); expect(timeElm.val()).toBe('03:04:05.006'); expect(weekElm.val()).toBe('2012-W05'); - changeGivenInputTo(timeElm, '04:05:06'); + helper.changeGivenInputTo(timeElm, '04:05:06'); expect(monthElm.val()).toBe('2012-02'); expect(timeElm.val()).toBe('04:05:06'); expect(weekElm.val()).toBe('2012-W05'); - changeGivenInputTo(weekElm, '2014-W01'); + helper.changeGivenInputTo(weekElm, '2014-W01'); expect(monthElm.val()).toBe('2014-01'); expect(timeElm.val()).toBe('04:05:06.000'); expect(weekElm.val()).toBe('2014-W01'); - expect(+scope.val).toBe(+new Date(2014, 0, 2, 4, 5, 6, 0)); + expect(+$rootScope.val).toBe(+new Date(2014, 0, 2, 4, 5, 6, 0)); - function changeGivenInputTo(inputElm, value) { - inputElm.val(value); - browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change'); - } + dealoc(formElm); }); describe('min', function() { - beforeEach(function() { - compileInput(''); - }); it('should invalidate', function() { - changeInputValueTo('1999-12-31'); + var inputElm = helper.compileInput(''); + helper.changeInputValueTo('1999-12-31'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2000-01-01'); + var inputElm = helper.compileInput(''); + helper.changeInputValueTo('2000-01-01'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2000, 0, 1)); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1)); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); - it('should parse ISO-based date strings as a valid min date value', inject(function($rootScope) { - var scope = $rootScope.$new(); - var element = $compile('')(scope); + it('should parse ISO-based date strings as a valid min date value', function() { + var inputElm = helper.compileInput(''); - var inputElm = element.find('input'); + $rootScope.value = new Date(2010, 1, 1, 0, 0, 0); + $rootScope.min = new Date(2014, 10, 10, 0, 0, 0); + $rootScope.$digest(); - scope.value = new Date(2010, 1, 1, 0, 0, 0); - scope.min = new Date(2014, 10, 10, 0, 0, 0); - scope.$digest(); - - expect(scope.myForm.myControl.$error.min).toBeTruthy(); - - dealoc(element); - })); + expect($rootScope.form.myControl.$error.min).toBeTruthy(); + }); }); describe('max', function() { - beforeEach(function() { - compileInput(''); - }); it('should invalidate', function() { - changeInputValueTo('2019-12-31'); + var inputElm = helper.compileInput(''); + helper.changeInputValueTo('2019-12-31'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2000-01-01'); + var inputElm = helper.compileInput(''); + helper.changeInputValueTo('2000-01-01'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2000, 0, 1)); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect(+$rootScope.value).toBe(+new Date(2000, 0, 1)); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); - it('should parse ISO-based date strings as a valid max date value', inject(function($rootScope) { - var scope = $rootScope.$new(); - var element = $compile('')(scope); + it('should parse ISO-based date strings as a valid max date value', function() { + var inputElm = helper.compileInput(''); - var inputElm = element.find('input'); + $rootScope.value = new Date(2020, 1, 1, 0, 0, 0); + $rootScope.max = new Date(2014, 10, 10, 0, 0, 0); + $rootScope.$digest(); - scope.value = new Date(2020, 1, 1, 0, 0, 0); - scope.max = new Date(2014, 10, 10, 0, 0, 0); - scope.$digest(); - - expect(scope.myForm.myControl.$error.max).toBeTruthy(); - - dealoc(element); - })); + expect($rootScope.form.myControl.$error.max).toBeTruthy(); + }); }); + it('should validate even if max value changes on-the-fly', function() { - scope.max = '2013-01-01'; - compileInput(''); + $rootScope.max = '2013-01-01'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2014-01-01'); + helper.changeInputValueTo('2014-01-01'); expect(inputElm).toBeInvalid(); - scope.max = '2001-01-01'; - scope.$digest(); + $rootScope.max = '2001-01-01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = '2021-01-01'; - scope.$digest(); + $rootScope.max = '2021-01-01'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if min value changes on-the-fly', function() { - scope.min = '2013-01-01'; - compileInput(''); + $rootScope.min = '2013-01-01'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2010-01-01'); + helper.changeInputValueTo('2010-01-01'); expect(inputElm).toBeInvalid(); - scope.min = '2014-01-01'; - scope.$digest(); + $rootScope.min = '2014-01-01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = '2009-01-01'; - scope.$digest(); + $rootScope.min = '2009-01-01'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if ng-max value changes on-the-fly', function() { - scope.max = '2013-01-01'; - compileInput(''); + $rootScope.max = '2013-01-01'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2014-01-01'); + helper.changeInputValueTo('2014-01-01'); expect(inputElm).toBeInvalid(); - scope.max = '2001-01-01'; - scope.$digest(); + $rootScope.max = '2001-01-01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = '2021-01-01'; - scope.$digest(); + $rootScope.max = '2021-01-01'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); + it('should validate even if ng-min value changes on-the-fly', function() { - scope.min = '2013-01-01'; - compileInput(''); + $rootScope.min = '2013-01-01'; + var inputElm = helper.compileInput(''); - changeInputValueTo('2010-01-01'); + helper.changeInputValueTo('2010-01-01'); expect(inputElm).toBeInvalid(); - scope.min = '2014-01-01'; - scope.$digest(); + $rootScope.min = '2014-01-01'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = '2009-01-01'; - scope.$digest(); + $rootScope.min = '2009-01-01'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); + describe('number', function() { it('should reset the model if view is invalid', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('age = 123'); + $rootScope.$apply('age = 123'); expect(inputElm.val()).toBe('123'); try { @@ -4106,57 +1763,58 @@ describe('input', function() { inputElm[0].setAttribute('type', 'text'); } catch (e) {} - changeInputValueTo('123X'); + helper.changeInputValueTo('123X'); expect(inputElm.val()).toBe('123X'); - expect(scope.age).toBeUndefined(); + expect($rootScope.age).toBeUndefined(); expect(inputElm).toBeInvalid(); }); it('should render as blank if null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('age = null'); + $rootScope.$apply('age = null'); - expect(scope.age).toBeNull(); + expect($rootScope.age).toBeNull(); expect(inputElm.val()).toEqual(''); }); + it('should come up blank when no value specified', function() { - compileInput(''); + var inputElm = helper.compileInput(''); expect(inputElm.val()).toBe(''); - scope.$apply('age = null'); + $rootScope.$apply('age = null'); - expect(scope.age).toBeNull(); + expect($rootScope.age).toBeNull(); expect(inputElm.val()).toBe(''); }); it('should parse empty string to null', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('age = 10'); + $rootScope.$apply('age = 10'); - changeInputValueTo(''); - expect(scope.age).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.age).toBeNull(); expect(inputElm).toBeValid(); }); it('should only invalidate the model if suffering from bad input when the data is parsed', function() { - compileInput('', { + var inputElm = helper.compileInput('', { valid: false, badInput: true }); - expect(scope.age).toBeUndefined(); + expect($rootScope.age).toBeUndefined(); expect(inputElm).toBeValid(); - changeInputValueTo('this-will-fail-because-of-the-badInput-flag'); + helper.changeInputValueTo('this-will-fail-because-of-the-badInput-flag'); - expect(scope.age).toBeUndefined(); + expect($rootScope.age).toBeUndefined(); expect(inputElm).toBeInvalid(); }); @@ -4166,30 +1824,30 @@ describe('input', function() { valid: false, badInput: true }; - compileInput('', validity); - changeInputValueTo('10a'); + var inputElm = helper.compileInput('', validity); + helper.changeInputValueTo('10a'); validity.badInput = false; validity.valid = true; - changeInputValueTo(''); - expect(scope.age).toBeNull(); + helper.changeInputValueTo(''); + expect($rootScope.age).toBeNull(); expect(inputElm).toBeValid(); }); it('should validate with undefined viewValue when $validate() called', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.form.alias.$validate(); + $rootScope.form.alias.$validate(); expect(inputElm).toBeValid(); - expect(scope.form.alias.$error.number).toBeUndefined(); + expect($rootScope.form.alias.$error.number).toBeUndefined(); }); it('should throw if the model value is not a number', function() { expect(function() { - scope.value = 'one'; - compileInput(''); + $rootScope.value = 'one'; + var inputElm = helper.compileInput(''); }).toThrowMinErr('ngModel', 'numfmt', "Expected `one` to be a number"); }); @@ -4197,40 +1855,40 @@ describe('input', function() { describe('min', function() { it('should validate', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('1'); + helper.changeInputValueTo('1'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); - changeInputValueTo('100'); + helper.changeInputValueTo('100'); expect(inputElm).toBeValid(); - expect(scope.value).toBe(100); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect($rootScope.value).toBe(100); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); it('should validate even if min value changes on-the-fly', function() { - scope.min = 10; - compileInput(''); + $rootScope.min = 10; + var inputElm = helper.compileInput(''); - changeInputValueTo('15'); + helper.changeInputValueTo('15'); expect(inputElm).toBeValid(); - scope.min = 20; - scope.$digest(); + $rootScope.min = 20; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = null; - scope.$digest(); + $rootScope.min = null; + $rootScope.$digest(); expect(inputElm).toBeValid(); - scope.min = '20'; - scope.$digest(); + $rootScope.min = '20'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = 'abc'; - scope.$digest(); + $rootScope.min = 'abc'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); @@ -4238,40 +1896,40 @@ describe('input', function() { describe('ngMin', function() { it('should validate', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('1'); + helper.changeInputValueTo('1'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeFalsy(); - expect(scope.form.alias.$error.min).toBeTruthy(); + expect($rootScope.value).toBeFalsy(); + expect($rootScope.form.alias.$error.min).toBeTruthy(); - changeInputValueTo('100'); + helper.changeInputValueTo('100'); expect(inputElm).toBeValid(); - expect(scope.value).toBe(100); - expect(scope.form.alias.$error.min).toBeFalsy(); + expect($rootScope.value).toBe(100); + expect($rootScope.form.alias.$error.min).toBeFalsy(); }); it('should validate even if the ngMin value changes on-the-fly', function() { - scope.min = 10; - compileInput(''); + $rootScope.min = 10; + var inputElm = helper.compileInput(''); - changeInputValueTo('15'); + helper.changeInputValueTo('15'); expect(inputElm).toBeValid(); - scope.min = 20; - scope.$digest(); + $rootScope.min = 20; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = null; - scope.$digest(); + $rootScope.min = null; + $rootScope.$digest(); expect(inputElm).toBeValid(); - scope.min = '20'; - scope.$digest(); + $rootScope.min = '20'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.min = 'abc'; - scope.$digest(); + $rootScope.min = 'abc'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); @@ -4280,40 +1938,40 @@ describe('input', function() { describe('max', function() { it('should validate', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('20'); + helper.changeInputValueTo('20'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeUndefined(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeUndefined(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); - changeInputValueTo('0'); + helper.changeInputValueTo('0'); expect(inputElm).toBeValid(); - expect(scope.value).toBe(0); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect($rootScope.value).toBe(0); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); it('should validate even if max value changes on-the-fly', function() { - scope.max = 10; - compileInput(''); + $rootScope.max = 10; + var inputElm = helper.compileInput(''); - changeInputValueTo('5'); + helper.changeInputValueTo('5'); expect(inputElm).toBeValid(); - scope.max = 0; - scope.$digest(); + $rootScope.max = 0; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = null; - scope.$digest(); + $rootScope.max = null; + $rootScope.$digest(); expect(inputElm).toBeValid(); - scope.max = '4'; - scope.$digest(); + $rootScope.max = '4'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = 'abc'; - scope.$digest(); + $rootScope.max = 'abc'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); @@ -4321,40 +1979,40 @@ describe('input', function() { describe('ngMax', function() { it('should validate', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('20'); + helper.changeInputValueTo('20'); expect(inputElm).toBeInvalid(); - expect(scope.value).toBeUndefined(); - expect(scope.form.alias.$error.max).toBeTruthy(); + expect($rootScope.value).toBeUndefined(); + expect($rootScope.form.alias.$error.max).toBeTruthy(); - changeInputValueTo('0'); + helper.changeInputValueTo('0'); expect(inputElm).toBeValid(); - expect(scope.value).toBe(0); - expect(scope.form.alias.$error.max).toBeFalsy(); + expect($rootScope.value).toBe(0); + expect($rootScope.form.alias.$error.max).toBeFalsy(); }); it('should validate even if the ngMax value changes on-the-fly', function() { - scope.max = 10; - compileInput(''); + $rootScope.max = 10; + var inputElm = helper.compileInput(''); - changeInputValueTo('5'); + helper.changeInputValueTo('5'); expect(inputElm).toBeValid(); - scope.max = 0; - scope.$digest(); + $rootScope.max = 0; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = null; - scope.$digest(); + $rootScope.max = null; + $rootScope.$digest(); expect(inputElm).toBeValid(); - scope.max = '4'; - scope.$digest(); + $rootScope.max = '4'; + $rootScope.$digest(); expect(inputElm).toBeInvalid(); - scope.max = 'abc'; - scope.$digest(); + $rootScope.max = 'abc'; + $rootScope.$digest(); expect(inputElm).toBeValid(); }); }); @@ -4363,37 +2021,37 @@ describe('input', function() { describe('required', function() { it('should be valid even if value is 0', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - changeInputValueTo('0'); + helper.changeInputValueTo('0'); expect(inputElm).toBeValid(); - expect(scope.value).toBe(0); - expect(scope.form.alias.$error.required).toBeFalsy(); + expect($rootScope.value).toBe(0); + expect($rootScope.form.alias.$error.required).toBeFalsy(); }); it('should be valid even if value 0 is set from model', function() { - compileInput(''); + var inputElm = helper.compileInput(''); - scope.$apply('value = 0'); + $rootScope.$apply('value = 0'); expect(inputElm).toBeValid(); expect(inputElm.val()).toBe('0'); - expect(scope.form.alias.$error.required).toBeFalsy(); + expect($rootScope.form.alias.$error.required).toBeFalsy(); }); it('should register required on non boolean elements', function() { - compileInput('