From 0a77f06d34856a30df94b994383d61c84287d786 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Tue, 3 Dec 2013 23:27:01 -0500 Subject: [PATCH 1/5] feat(input): add handling for date input partially closes #757 --- src/ng/directive/input.js | 140 +++++++++++++++++++++++++++++++++ test/ng/directive/inputSpec.js | 116 +++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 4e77397878dd..a9db67547d4b 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -11,6 +11,7 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; +var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; var inputType = { @@ -89,6 +90,71 @@ var inputType = { */ 'text': textInputType, + /** + * @ngdoc inputType + * @name ng.directive:input.date + * + * @description + * HTML5 or text input with date validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601 + * date format (yyyy-MM-dd), for example: `2009-01-06`. Will also accept a valid ISO date or Date object + * as model input, but will always output a Date object to the model. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + it('should initialize to model', function() { + expect(binding('value')).toEqual('2013-10-22'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('2015-01-01'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
+ */ + 'date': dateInputType, /** * @ngdoc inputType @@ -530,6 +596,80 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } } +function dateInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + if(ctrl.$isEmpty(value)) { + ctrl.$setValidity('date', true); + return value; + } + + if(DATE_REGEXP.test(value)) { + ctrl.$setValidity('date', true); + return new Date(getTime(value)); + } + + ctrl.$setValidity('date', false); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if(isDate(value)) { + var year = value.getFullYear(), + month = value.getMonth() + 1, + day = value.getDate(); + + month = (month < 10 ? '0' : '') + month; + day = (day < 10 ? '0' : '') + day; + return year + '-' + month + '-' + day; + } + return ctrl.$isEmpty(value) ? '' : '' + value; + }); + + if(attr.min) { + var minValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (getTime(value) >= getTime(attr.min)); + ctrl.$setValidity('min', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if(attr.max) { + var maxValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (getTime(value) <= getTime(attr.max)); + ctrl.$setValidity('max', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + function getTime(iso) { + if(isDate(iso)) { + return +iso; + } + + if(isString(iso)) { + DATE_REGEXP.lastIndex = 0; + var parts = DATE_REGEXP.exec(iso), + yyyy = +parts[1], + mm = +parts[2] - 1, + dd = +parts[3], + time = new Date(yyyy, mm, dd); + return +time; + } + + return NaN; + } +} + function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 26abceae1cfe..9e8ccdbaa372 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -752,6 +752,122 @@ describe('input', function() { // INPUT TYPES + describe('date', function () { + it('should set the view if the model is valid ISO8601 date', function() { + compileInput(''); + + scope.$apply(function(){ + scope.birthday = '1977-10-22'; + }); + + expect(inputElm.val()).toBe('1977-10-22'); + }); + + 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); + }); + + 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); + }); + + expect(inputElm.val()).toBe('2014-09-14'); + + try { + //set to text for browsers with date validation. + inputElm[0].setAttribute('type', 'text'); + } catch(e) { + //for IE8 + } + + changeInputValueTo('1-2-3'); + expect(inputElm.val()).toBe('1-2-3'); + expect(scope.arrMatey).toBeUndefined(); + expect(inputElm).toBeInvalid(); + }); + + describe('min', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('1999-12-31'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.min).toBeTruthy(); + }); + + it('should validate', function (){ + changeInputValueTo('2000-01-01'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2000, 0, 1)); + expect(scope.form.alias.$error.min).toBeFalsy(); + }); + }); + + describe('max', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('2019-12-31'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.max).toBeTruthy(); + }); + + it('should validate', function() { + changeInputValueTo('2000-01-01'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2000, 0, 1)); + expect(scope.form.alias.$error.max).toBeFalsy(); + }); + }); + + it('should validate even if max value changes on-the-fly', function(done) { + scope.max = '2013-01-01'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('2014-01-01'); + expect(inputElm).toBeInvalid(); + + scope.max = '2001-01-01'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + + it('should validate even if min value changes on-the-fly', function(done) { + scope.min = '2013-01-01'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('2010-01-01'); + expect(inputElm).toBeInvalid(); + + scope.min = '2014-01-01'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + }); + describe('number', function() { it('should reset the model if view is invalid', function() { From 9cc6b73295eee01af9a2e21162f083b565241fd3 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Sun, 15 Dec 2013 22:26:55 -0500 Subject: [PATCH 2/5] feat(input) add support for datetime-local partially closes #757 --- src/ng/directive/input.js | 151 ++++++++++++++++++++++++++++++--- test/ng/directive/inputSpec.js | 117 +++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 10 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index a9db67547d4b..0736822e8c9b 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -12,6 +12,7 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\ var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; +var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/; var inputType = { @@ -156,6 +157,71 @@ var inputType = { */ 'date': dateInputType, + /** + * @ngdoc inputType + * @name ng.directive:input.dateTimeLocal + * + * @description + * HTML5 or text input with datetime validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601 + * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:12`. Will also accept a valid ISO + * datetime string or Date object as model input, but will always output a Date object to the model. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + it('should initialize to model', function() { + expect(binding('value')).toEqual('2010-12-28T14:57'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('2015-01-01T23:59'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
+ */ + 'datetime-local': dateTimeLocalInputType, /** * @ngdoc inputType * @name ng.directive:input.number @@ -596,7 +662,78 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } } -function dateInputType(scope, element, attr, ctrl, $sniffer, $browser) { +function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + if(ctrl.$isEmpty(value)) { + ctrl.$setValidity('datetimelocal', true); + return value; + } + + if(DATETIMELOCAL_REGEXP.test(value)) { + ctrl.$setValidity('datetimelocal', true); + return new Date(getTime(value)); + } + + ctrl.$setValidity('datetimelocal', false); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if(isDate(value)) { + return $filter('date')(value, 'yyyy-MM-ddTHH:mm:ss'); + } + return ctrl.$isEmpty(value) ? '' : '' + value; + }); + + if(attr.min) { + var minValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (getTime(value) >= getTime(attr.min)); + ctrl.$setValidity('min', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if(attr.max) { + var maxValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (getTime(value) <= getTime(attr.max)); + ctrl.$setValidity('max', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + function getTime(iso) { + if(isDate(iso)) { + return +iso; + } + + if(isString(iso)) { + DATETIMELOCAL_REGEXP.lastIndex = 0; + var parts = DATETIMELOCAL_REGEXP.exec(iso), + yyyy = +parts[1], + MM = +parts[2] - 1, + dd = +parts[3], + HH = +parts[4], + mm = +parts[5], + ss = +parts[6]; + + return +new Date(yyyy, MM, dd, HH, mm, ss); + } + + return NaN; + } +} + +function dateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); ctrl.$parsers.push(function(value) { @@ -616,13 +753,7 @@ function dateInputType(scope, element, attr, ctrl, $sniffer, $browser) { ctrl.$formatters.push(function(value) { if(isDate(value)) { - var year = value.getFullYear(), - month = value.getMonth() + 1, - day = value.getDate(); - - month = (month < 10 ? '0' : '') + month; - day = (day < 10 ? '0' : '') + day; - return year + '-' + month + '-' + day; + return $filter('date')(value, 'yyyy-MM-dd'); } return ctrl.$isEmpty(value) ? '' : '' + value; }); @@ -912,14 +1043,14 @@ function checkboxInputType(scope, element, attr, ctrl) { */ -var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { +var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sniffer, $filter) { return { restrict: 'E', require: '?ngModel', link: function(scope, element, attr, ctrl) { if (ctrl) { (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, - $browser); + $browser, $filter); } } }; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 9e8ccdbaa372..383c5a33bb30 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -752,6 +752,123 @@ describe('input', function() { // INPUT TYPES + + describe('datetime-local', function () { + it('should set the view if the model is valid ISO8601 local datetime', function() { + compileInput(''); + + scope.$apply(function(){ + scope.lunchtime = '2013-12-16T11:30:15'; + }); + + expect(inputElm.val()).toBe('2013-12-16T11:30:15'); + }); + + it('should set the view if the model if a valid Date object.', function(){ + compileInput(''); + + scope.$apply(function (){ + scope.tenSecondsToNextYear = new Date(2013, 11, 31, 23, 59, 50); + }); + + expect(inputElm.val()).toBe('2013-12-31T23:59:50'); + }); + + 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, 10); + }); + + expect(inputElm.val()).toBe('2009-01-06T16:25:10'); + + try { + //set to text for browsers with datetime-local validation. + inputElm[0].setAttribute('type', 'text'); + } catch(e) { + //for IE8 + } + + changeInputValueTo('stuff'); + expect(inputElm.val()).toBe('stuff'); + expect(scope.breakMe).toBeUndefined(); + expect(inputElm).toBeInvalid(); + }); + + describe('min', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('1999-12-31T01:02:03'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.min).toBeTruthy(); + }); + + it('should validate', function (){ + changeInputValueTo('2000-01-01T23:02:01'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2, 1)); + expect(scope.form.alias.$error.min).toBeFalsy(); + }); + }); + + describe('max', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('2019-12-31T01:02:03'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.max).toBeTruthy(); + }); + + it('should validate', function() { + changeInputValueTo('2000-01-01T01:02:03'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3)); + expect(scope.form.alias.$error.max).toBeFalsy(); + }); + }); + + it('should validate even if max value changes on-the-fly', function(done) { + scope.max = '2013-01-01T01:02:03'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('2014-01-01T12:34:56'); + expect(inputElm).toBeInvalid(); + + scope.max = '2001-01-01T01:02:03'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + + it('should validate even if min value changes on-the-fly', function(done) { + scope.min = '2013-01-01T01:02:03'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('2010-01-01T12:34:56'); + expect(inputElm).toBeInvalid(); + + scope.min = '2014-01-01T01:02:03'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + }); + describe('date', function () { it('should set the view if the model is valid ISO8601 date', function() { compileInput(''); From e5c0da0774c274c44f58b8275887cdd5d62002e5 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 16 Dec 2013 00:04:53 -0500 Subject: [PATCH 3/5] feat(input) add support to for week Add support to date filter for outputing week of year Partially closes #757 --- src/ng/directive/input.js | 181 +++++++++++++++++++++++++++++++++ src/ng/filter/filters.js | 37 ++++++- test/ng/directive/inputSpec.js | 88 ++++++++++++++++ test/ng/filter/filtersSpec.js | 8 +- 4 files changed, 311 insertions(+), 3 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 0736822e8c9b..b26c9215419b 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -13,6 +13,7 @@ var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/; +var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; var inputType = { @@ -222,6 +223,73 @@ var inputType = { */ 'datetime-local': dateTimeLocalInputType, + + /** + * @ngdoc inputType + * @name ng.directive:input.week + * + * @description + * HTML5 or text input with week-of-the-year validation and transformation to Date. In browsers that do not yet support + * the HTML5 week input, a text element will be used. The text must be entered in a valid ISO-8601 + * week format (yyyy-W##), for example: `2013-W02`. Will also accept a valid ISO + * week string or Date object as model input, but will always output a Date object to the model. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + it('should initialize to model', function() { + expect(binding('value')).toEqual('2013-W01'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('2015-W01'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
+ */ + 'week': weekInputType, + /** * @ngdoc inputType * @name ng.directive:input.number @@ -662,6 +730,119 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } } + +function weekInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + if(ctrl.$isEmpty(value)) { + ctrl.$setValidity('week', true); + return value; + } + + if(WEEK_REGEXP.test(value)) { + ctrl.$setValidity('week', true); + return new Date(getTime(value).time); + } + + ctrl.$setValidity('week', false); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if(isDate(value)) { + return $filter('date')(value, 'yyyy-Www'); + } + return ctrl.$isEmpty(value) ? '' : ''+value; + }); + + if(attr.min) { + var minValidator = function(value) { + var valTime = getTime(value), + minTime = getTime(attr.min); + + var valid = ctrl.$isEmpty(value) || + valTime.time >= minTime.time; + + ctrl.$setValidity('min', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if(attr.max) { + var maxValidator = function(value) { + debugger; + var valTime = getTime(value), + maxTime = getTime(attr.max); + + var valid = ctrl.$isEmpty(value) || + valTime.time <= maxTime.time; + + ctrl.$setValidity('max', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + function getFirstThursday(year) { + var d = 1, date; + while(true) { + date = new Date(year, 0, d++); + if(date.getDay() === 4) { + return date; + } + } + } + + function getThisThursday(date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate() + (4 - date.getDay())); + } + + var MILLISECONDS_PER_WEEK = 6.048e8; + + function getWeek(date) { + var firstThurs = getFirstThursday(date.getFullYear()), + thisThurs = getThisThursday(date), + diff = +thisThurs - +firstThurs; + + return 1 + Math.round(diff / MILLISECONDS_PER_WEEK); + } + + function getTime(isoWeek) { + if(isDate(isoWeek)) { + return { + year: isoWeek.getFullYear(), + week: getWeek(isoWeek), + time: +isoWeek + }; + } + + if(isString(isoWeek)) { + WEEK_REGEXP.lastIndex = 0; + var parts = WEEK_REGEXP.exec(isoWeek); + if(parts) { + var year = +parts[1], + week = +parts[2], + firstThurs = getFirstThursday(year), + addDays = (week - 1) * 7; + + return { + time: +new Date(year, 0, firstThurs.getDate() + addDays), + week: week, + year: year + }; + } + } + + return NaN; + } +} + function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js index c92ed384792e..04f2f64fdd6c 100644 --- a/src/ng/filter/filters.js +++ b/src/ng/filter/filters.js @@ -228,6 +228,35 @@ function timeZoneGetter(date) { return paddedZone; } +function getFirstThursday(year) { + var d = 1, + date = new Date(year, 0, d); + while(date.getDay() !== 4) { + d++; + date = new Date(year, 0, d); + } + return date; +} + +function getThursdayThisWeek(date) { + var day = date.getDay(), + d = date.getDate(); + + return new Date(date.getFullYear(), date.getMonth(), d + (4 - day)); +} + +function weekGetter(size) { + return function(date) { + var firstThurs = getFirstThursday(date.getFullYear()), + thisThurs = getThursdayThisWeek(date); + + var diff = +thisThurs - +firstThurs, + result = 1 + Math.round(diff / 6.048e8); + + return padNumber(result, size); + } +} + function ampmGetter(date, formats) { return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; } @@ -256,10 +285,12 @@ var DATE_FORMATS = { EEEE: dateStrGetter('Day'), EEE: dateStrGetter('Day', true), a: ampmGetter, - Z: timeZoneGetter + Z: timeZoneGetter, + ww: weekGetter(2), + w: weekGetter(1) }; -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, NUMBER_STRING = /^\-?\d+$/; /** @@ -294,6 +325,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+ * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) * * `'a'`: am/pm marker * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) + * * `'ww'`: ISO-8601 week of year (00-53) + * * `'w'`: ISO-8601 week of year (0-53) * * `format` string can also be one of the following predefined * {@link guide/i18n localizable formats}: diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 383c5a33bb30..5db680157c13 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -752,6 +752,94 @@ describe('input', function() { // INPUT TYPES + describe('week', function (){ + it('should set the view if the model is valid ISO8601 week', function() { + compileInput(''); + + scope.$apply(function(){ + scope.secondWeek = '2013-W02'; + }); + + expect(inputElm.val()).toBe('2013-W02'); + }); + + 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); + }); + + expect(inputElm.val()).toBe('2013-W02'); + }); + + 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); + }); + + + expect(inputElm.val()).toBe('2013-W02'); + + try { + //set to text for browsers with datetime-local validation. + inputElm[0].setAttribute('type', 'text'); + } catch(e) { + //for IE8 + } + + changeInputValueTo('stuff'); + expect(inputElm.val()).toBe('stuff'); + expect(scope.value).toBeUndefined(); + expect(inputElm).toBeInvalid(); + }); + + + + describe('min', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('2012-W12'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.min).toBeTruthy(); + }); + + it('should validate', function (){ + changeInputValueTo('2013-W03'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2013, 0, 17)); + expect(scope.form.alias.$error.min).toBeFalsy(); + }); + }); + + describe('max', function(){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should validate', function (){ + changeInputValueTo('2012-W01'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2012, 0, 5)); + expect(scope.form.alias.$error.max).toBeFalsy(); + }); + + it('should invalidate', function (){ + changeInputValueTo('2013-W03'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeUndefined(); + expect(scope.form.alias.$error.max).toBeTruthy(); + }); + }); + }); describe('datetime-local', function () { it('should set the view if the model is valid ISO8601 local datetime', function() { diff --git a/test/ng/filter/filtersSpec.js b/test/ng/filter/filtersSpec.js index 2d648b652984..da19c41adc4f 100644 --- a/test/ng/filter/filtersSpec.js +++ b/test/ng/filter/filtersSpec.js @@ -192,7 +192,7 @@ describe('filters', function() { var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z'); - + var secondWeek = new angular.mock.TzDate(+5, '2013-01-11T12:00:00.000Z'); //Friday Jan 11, 2012 var date; beforeEach(inject(function($filter) { @@ -215,6 +215,12 @@ describe('filters', function() { }); it('should accept various format strings', function() { + expect(date(secondWeek, 'yyyy-Ww')). + toEqual('2013-W2'); + + expect(date(secondWeek, 'yyyy-Www')). + toEqual('2013-W02'); + expect(date(morning, "yy-MM-dd HH:mm:ss")). toEqual('10-09-03 07:05:08'); From 523608d0b6f5f30c8cd292e83f67d5aa5b01320c Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Fri, 17 Jan 2014 10:28:04 -0500 Subject: [PATCH 4/5] feat(input): add month support --- src/ng/directive/input.js | 134 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index b26c9215419b..a3acb04b2256 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -14,6 +14,7 @@ var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/; var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; +var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; var inputType = { @@ -289,6 +290,72 @@ var inputType = { */ 'week': weekInputType, + + /** + * @ngdoc inputType + * @name ng.directive:input.month + * + * @description + * HTML5 or text input with month validation and transformation. In browsers that do not yet support + * the HTML5 month input, a text element will be used. The text must be entered in a valid ISO-8601 + * month format (yyyy-MM), for example: `2009-01`. Will also accept a valid ISO month or Date object + * as model input, but will always output a Date object to the model. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
+ Pick a month int 2013: + + + Required! + + Not a valid month! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + it('should initialize to model', function() { + expect(binding('value')).toEqual('2013-10'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + inp ut('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('2015-01'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
+ */ + 'month': monthInputType, /** * @ngdoc inputType @@ -914,6 +981,73 @@ function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, } } +function monthInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + if(ctrl.$isEmpty(value)) { + ctrl.$setValidity('month', true); + return value; + } + + if(MONTH_REGEXP.test(value)) { + ctrl.$setValidity('month', true); + return new Date(getTime(value)); + } + + ctrl.$setValidity('month', false); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if(isDate(value)) { + return $filter('month')(value, 'yyyy-MM'); + } + return ctrl.$isEmpty(value) ? '' : '' + value; + }); + + if(attr.min) { + var minValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (getTime(value) >= getTime(attr.min)); + ctrl.$setValidity('min', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if(attr.max) { + var maxValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (getTime(value) <= getTime(attr.max)); + ctrl.$setValidity('max', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + function getTime(iso) { + if(isDate(iso)) { + return +iso; + } + + if(isString(iso)) { + DATE_REGEXP.lastIndex = 0; + var parts = DATE_REGEXP.exec(iso), + yyyy = +parts[1], + mm = +parts[2] - 1, + time = new Date(yyyy, mm, 1); + return +time; + } + + return NaN; + } +} + function dateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); From 08ef544c233da799c36014b57a862cd3c9d26a42 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Thu, 26 Dec 2013 12:52:16 -0500 Subject: [PATCH 5/5] feat(input): add month input type --- src/ng/directive/input.js | 220 +++++++-------------------------- test/ng/directive/inputSpec.js | 128 ++++++++++++++++--- 2 files changed, 151 insertions(+), 197 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index a3acb04b2256..06ce78c4c8e4 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -12,7 +12,7 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\ var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; -var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/; +var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/; var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; @@ -166,7 +166,7 @@ var inputType = { * @description * HTML5 or text input with datetime validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. The text must be entered in a valid ISO-8601 - * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:12`. Will also accept a valid ISO + * local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. Will also accept a valid ISO * datetime string or Date object as model input, but will always output a Date object to the model. * * @param {string} ngModel Assignable angular expression to data-bind to. @@ -185,41 +185,41 @@ var inputType = {
- Pick a date between in 2013: - - - Required! - - Not a valid date! - value = {{value}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
+ Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
it('should initialize to model', function() { - expect(binding('value')).toEqual('2010-12-28T14:57'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); + expect(binding('value')).toEqual('2010-12-28T14:57'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); it('should be invalid if empty', function() { - input('value').enter(''); - expect(binding('value')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); it('should be invalid if over max', function() { - input('value').enter('2015-01-01T23:59'); - expect(binding('value')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); + input('value').enter('2015-01-01T23:59'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); */ @@ -255,18 +255,18 @@ var inputType = { }
- Pick a date between in 2013: - - - Required! - - Not a valid date! - value = {{value}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
+ Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{value}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
@@ -291,72 +291,6 @@ var inputType = { */ 'week': weekInputType, - /** - * @ngdoc inputType - * @name ng.directive:input.month - * - * @description - * HTML5 or text input with month validation and transformation. In browsers that do not yet support - * the HTML5 month input, a text element will be used. The text must be entered in a valid ISO-8601 - * month format (yyyy-MM), for example: `2009-01`. Will also accept a valid ISO month or Date object - * as model input, but will always output a Date object to the model. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Pick a month int 2013: - - - Required! - - Not a valid month! - value = {{value}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - it('should initialize to model', function() { - expect(binding('value')).toEqual('2013-10'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - inp ut('value').enter(''); - expect(binding('value')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - - it('should be invalid if over max', function() { - input('value').enter('2015-01'); - expect(binding('value')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - -
- */ - 'month': monthInputType, - /** * @ngdoc inputType * @name ng.directive:input.number @@ -797,7 +731,6 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } } - function weekInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); @@ -841,7 +774,6 @@ function weekInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) if(attr.max) { var maxValidator = function(value) { - debugger; var valTime = getTime(value), maxTime = getTime(attr.max); @@ -910,6 +842,8 @@ function weekInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) } } +} + function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); @@ -930,7 +864,7 @@ function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, ctrl.$formatters.push(function(value) { if(isDate(value)) { - return $filter('date')(value, 'yyyy-MM-ddTHH:mm:ss'); + return $filter('date')(value, 'yyyy-MM-ddTHH:mm'); } return ctrl.$isEmpty(value) ? '' : '' + value; }); @@ -971,83 +905,15 @@ function dateTimeLocalInputType(scope, element, attr, ctrl, $sniffer, $browser, MM = +parts[2] - 1, dd = +parts[3], HH = +parts[4], - mm = +parts[5], - ss = +parts[6]; + mm = +parts[5]; - return +new Date(yyyy, MM, dd, HH, mm, ss); + return +new Date(yyyy, MM, dd, HH, mm); } return NaN; } } -function monthInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - ctrl.$parsers.push(function(value) { - if(ctrl.$isEmpty(value)) { - ctrl.$setValidity('month', true); - return value; - } - - if(MONTH_REGEXP.test(value)) { - ctrl.$setValidity('month', true); - return new Date(getTime(value)); - } - - ctrl.$setValidity('month', false); - return undefined; - }); - - ctrl.$formatters.push(function(value) { - if(isDate(value)) { - return $filter('month')(value, 'yyyy-MM'); - } - return ctrl.$isEmpty(value) ? '' : '' + value; - }); - - if(attr.min) { - var minValidator = function(value) { - var valid = ctrl.$isEmpty(value) || - (getTime(value) >= getTime(attr.min)); - ctrl.$setValidity('min', valid); - return valid ? value : undefined; - }; - - ctrl.$parsers.push(minValidator); - ctrl.$formatters.push(minValidator); - } - - if(attr.max) { - var maxValidator = function(value) { - var valid = ctrl.$isEmpty(value) || - (getTime(value) <= getTime(attr.max)); - ctrl.$setValidity('max', valid); - return valid ? value : undefined; - }; - - ctrl.$parsers.push(maxValidator); - ctrl.$formatters.push(maxValidator); - } - - function getTime(iso) { - if(isDate(iso)) { - return +iso; - } - - if(isString(iso)) { - DATE_REGEXP.lastIndex = 0; - var parts = DATE_REGEXP.exec(iso), - yyyy = +parts[1], - mm = +parts[2] - 1, - time = new Date(yyyy, mm, 1); - return +time; - } - - return NaN; - } -} - function dateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 5db680157c13..69df3f03810b 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -751,6 +751,94 @@ describe('input', function() { // INPUT TYPES + describe('month', function (){ + it('should set the view if the model is valid ISO8601 month', function() { + compileInput(''); + + scope.$apply(function(){ + scope.january = '2013-01'; + }); + + expect(inputElm.val()).toBe('2013-01'); + }); + + 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); + }); + + 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); + }); + + + expect(inputElm.val()).toBe('2013-01'); + + try { + //set to text for browsers with datetime-local validation. + inputElm[0].setAttribute('type', 'text'); + } catch(e) { + //for IE8 + } + + changeInputValueTo('stuff'); + expect(inputElm.val()).toBe('stuff'); + expect(scope.value).toBeUndefined(); + expect(inputElm).toBeInvalid(); + }); + + + + describe('min', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('2012-12'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.min).toBeTruthy(); + }); + + it('should validate', function (){ + changeInputValueTo('2013-07'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2013, 6, 1)); + expect(scope.form.alias.$error.min).toBeFalsy(); + }); + }); + + describe('max', function(){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should validate', function (){ + changeInputValueTo('2012-03'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2012, 2, 1)); + expect(scope.form.alias.$error.max).toBeFalsy(); + }); + + it('should invalidate', function (){ + changeInputValueTo('2013-05'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeUndefined(); + expect(scope.form.alias.$error.max).toBeTruthy(); + }); + }); + }); describe('week', function (){ it('should set the view if the model is valid ISO8601 week', function() { @@ -846,30 +934,30 @@ describe('input', function() { compileInput(''); scope.$apply(function(){ - scope.lunchtime = '2013-12-16T11:30:15'; + scope.lunchtime = '2013-12-16T11:30'; }); - expect(inputElm.val()).toBe('2013-12-16T11:30:15'); + expect(inputElm.val()).toBe('2013-12-16T11:30'); }); it('should set the view if the model if a valid Date object.', function(){ compileInput(''); scope.$apply(function (){ - scope.tenSecondsToNextYear = new Date(2013, 11, 31, 23, 59, 50); + scope.tenSecondsToNextYear = new Date(2013, 11, 31, 23, 59); }); - expect(inputElm.val()).toBe('2013-12-31T23:59:50'); + expect(inputElm.val()).toBe('2013-12-31T23:59'); }); 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, 10); + scope.breakMe = new Date(2009, 0, 6, 16, 25); }); - expect(inputElm.val()).toBe('2009-01-06T16:25:10'); + expect(inputElm.val()).toBe('2009-01-06T16:25'); try { //set to text for browsers with datetime-local validation. @@ -886,55 +974,55 @@ describe('input', function() { describe('min', function (){ beforeEach(function (){ - compileInput(''); + compileInput(''); scope.$digest(); }); it('should invalidate', function (){ - changeInputValueTo('1999-12-31T01:02:03'); + changeInputValueTo('1999-12-31T01:02'); expect(inputElm).toBeInvalid(); expect(scope.value).toBeFalsy(); expect(scope.form.alias.$error.min).toBeTruthy(); }); it('should validate', function (){ - changeInputValueTo('2000-01-01T23:02:01'); + changeInputValueTo('2000-01-01T23:02'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2, 1)); + expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2)); expect(scope.form.alias.$error.min).toBeFalsy(); }); }); describe('max', function (){ beforeEach(function (){ - compileInput(''); + compileInput(''); scope.$digest(); }); it('should invalidate', function (){ - changeInputValueTo('2019-12-31T01:02:03'); + changeInputValueTo('2019-12-31T01:02'); expect(inputElm).toBeInvalid(); expect(scope.value).toBeFalsy(); expect(scope.form.alias.$error.max).toBeTruthy(); }); it('should validate', function() { - changeInputValueTo('2000-01-01T01:02:03'); + changeInputValueTo('2000-01-01T01:02'); expect(inputElm).toBeValid(); - expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2, 3)); + expect(+scope.value).toBe(+new Date(2000, 0, 1, 1, 2)); expect(scope.form.alias.$error.max).toBeFalsy(); }); }); it('should validate even if max value changes on-the-fly', function(done) { - scope.max = '2013-01-01T01:02:03'; + scope.max = '2013-01-01T01:02'; compileInput(''); scope.$digest(); - changeInputValueTo('2014-01-01T12:34:56'); + changeInputValueTo('2014-01-01T12:34'); expect(inputElm).toBeInvalid(); - scope.max = '2001-01-01T01:02:03'; + scope.max = '2001-01-01T01:02'; scope.$digest(function () { expect(inputElm).toBeValid(); done(); @@ -942,14 +1030,14 @@ describe('input', function() { }); it('should validate even if min value changes on-the-fly', function(done) { - scope.min = '2013-01-01T01:02:03'; + scope.min = '2013-01-01T01:02'; compileInput(''); scope.$digest(); - changeInputValueTo('2010-01-01T12:34:56'); + changeInputValueTo('2010-01-01T12:34'); expect(inputElm).toBeInvalid(); - scope.min = '2014-01-01T01:02:03'; + scope.min = '2014-01-01T01:02'; scope.$digest(function () { expect(inputElm).toBeValid(); done();