diff --git a/src/.jshintrc b/src/.jshintrc index f32caa451ed6..4e19601d536f 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -101,6 +101,9 @@ "getter": false, "getBlockElements": false, + /* filters.js */ + "getFirstThursdayOfYear": false, + /* AngularPublic.js */ "version": false, "publishExternalAPI": false, diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index c31bb4004947..91fd52c9efc7 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -11,6 +11,11 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; 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)$/; +var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; +var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; +var TIME_REGEXP = /^(\d\d):(\d\d)$/; var inputType = { @@ -91,7 +96,426 @@ var inputType = { */ 'text': textInputType, + /** + * @ngdoc input + * @name 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. In that case, text must be entered in a valid ISO-8601 + * date format (yyyy-MM-dd), for example: `2009-01-06`. The model must always be a Date object. + * + * @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`. This must be a + * valid ISO date string (yyyy-MM-dd). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO date string (yyyy-MM-dd). + * @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 | date: "yyyy-MM-dd"}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var value = element(by.binding('value | date: "yyyy-MM-dd"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers. + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-10-22'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01-01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
f + */ + 'date': createDateInputType('date', DATE_REGEXP, + createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), + 'yyyy-MM-dd'), + + /** + * @ngdoc input + * @name 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. In that case, the text must be entered in a valid ISO-8601 + * local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. The model must be a Date object. + * + * @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`. This must be a + * valid ISO datetime format (yyyy-MM-ddTHH:mm). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO datetime format (yyyy-MM-ddTHH:mm). + * @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 | date: "yyyy-MM-ddTHH:mm"}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers. + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2010-12-28T14:57'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01-01T23:59'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
+ */ + 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, + createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm']), + 'yyyy-MM-ddTHH:mm'), + + /** + * @ngdoc input + * @name input[time] + * + * @description + * HTML5 or text input with time validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * local time format (HH:mm), for example: `14:57`. Model must be a Date object. This binding will always output a + * Date object to the model of January 1, 1900, or local date `new Date(0, 0, 1, HH, mm)`. + * + * @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`. This must be a + * valid ISO time format (HH:mm). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a + * valid ISO time format (HH:mm). + * @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 between 8am and 5pm: + + + Required! + + Not a valid date! + value = {{value | date: "HH:mm"}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var value = element(by.binding('value | date: "HH:mm"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers. + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('14:57'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('23:59'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
+ */ + 'time': createDateInputType('time', TIME_REGEXP, + createDateParser(TIME_REGEXP, ['HH', 'mm']), + 'HH:mm'), + + /** + * @ngdoc input + * @name 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. In that case, the text must be entered in a valid ISO-8601 + * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object. + * + * @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`. This must be a + * valid ISO week format (yyyy-W##). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO week format (yyyy-W##). + * @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 | date: "yyyy-Www"}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var value = element(by.binding('value | date: "yyyy-Www"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers. + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-W01'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-W01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
+ */ + 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), + /** + * @ngdoc input + * @name 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. In that case, the text must be entered in a valid ISO-8601 + * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is + * not set to the first of the month, the first of that model's month is assumed. + * + * @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`. This must be + * a valid ISO month format (yyyy-MM). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must + * be a valid ISO month format (yyyy-MM). + * @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 | date: "yyyy-MM"}}
+ myForm.input.$valid = {{myForm.input.$valid}}
+ myForm.input.$error = {{myForm.input.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.required = {{!!myForm.$error.required}}
+
+
+ + var value = element(by.binding('value | date: "yyyy-MM"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers. + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-10'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
+ */ + 'month': createDateInputType('month', MONTH_REGEXP, + createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), + 'yyyy-MM'), + /** * @ngdoc input * @name input[number] @@ -593,6 +1017,108 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } } +function weekParser(isoWeek) { + if(isDate(isoWeek)) { + return isoWeek; + } + + if(isString(isoWeek)) { + WEEK_REGEXP.lastIndex = 0; + var parts = WEEK_REGEXP.exec(isoWeek); + if(parts) { + var year = +parts[1], + week = +parts[2], + firstThurs = getFirstThursdayOfYear(year), + addDays = (week - 1) * 7; + return new Date(year, 0, firstThurs.getDate() + addDays); + } + } + + return NaN; +} + +function createDateParser(regexp, mapping) { + return function(iso) { + var parts, map; + + if(isDate(iso)) { + return iso; + } + + if(isString(iso)) { + regexp.lastIndex = 0; + parts = regexp.exec(iso); + + if(parts) { + parts.shift(); + map = { yyyy: 0, MM: 1, dd: 1, HH: 0, mm: 0 }; + + forEach(parts, function(part, index) { + if(index < mapping.length) { + map[mapping[index]] = +part; + } + }); + + return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm); + } + } + + return NaN; + }; +} + +function createDateInputType(type, regexp, parseDate, format) { + return function dynamicDateInputType(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(type, true); + return null; + } + + if(regexp.test(value)) { + ctrl.$setValidity(type, true); + return parseDate(value); + } + + ctrl.$setValidity(type, false); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if(isDate(value)) { + return $filter('date')(value, format); + } + return ''; + }); + + if(attr.min) { + var minValidator = function(value) { + var valid = ctrl.$isEmpty(value) || + (parseDate(value) >= parseDate(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) || + (parseDate(value) <= parseDate(attr.max)); + ctrl.$setValidity('max', valid); + return valid ? value : undefined; + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + }; +} + function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); @@ -852,14 +1378,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); } } }; @@ -1247,6 +1773,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * - {@link input[number] number} * - {@link input[email] email} * - {@link input[url] url} + * - {@link input[date] date} + * - {@link input[dateTimeLocal] dateTimeLocal} + * - {@link input[time] time} + * - {@link input[month] month} + * - {@link input[week] week} * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js index b81838c68041..7c805660775b 100644 --- a/src/ng/filter/filters.js +++ b/src/ng/filter/filters.js @@ -235,6 +235,32 @@ function timeZoneGetter(date) { return paddedZone; } +function getFirstThursdayOfYear(year) { + // 0 = index of January + var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); + // 4 = index of Thursday (+1 to account for 1st = 5) + // 11 = index of *next* Thursday (+1 account for 1st = 12) + return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); +} + +function getThursdayThisWeek(datetime) { + return new Date(datetime.getFullYear(), datetime.getMonth(), + // 4 = index of Thursday + datetime.getDate() + (4 - datetime.getDay())); +} + +function weekGetter(size) { + return function(date) { + var firstThurs = getFirstThursdayOfYear(date.getFullYear()), + thisThurs = getThursdayThisWeek(date); + + var diff = +thisThurs - +firstThurs, + result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week + + return padNumber(result, size); + }; +} + function ampmGetter(date, formats) { return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; } @@ -263,10 +289,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+$/; /** @@ -301,6 +329,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 e3e50e02a69e..eba3028e7bce 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -762,6 +762,718 @@ describe('input', function() { // INPUT TYPES + describe('month', function (){ + it('should render blank if model is not a Date object', function() { + compileInput(''); + + scope.$apply(function(){ + scope.january = '2013-01'; + }); + + expect(inputElm.val()).toBe(''); + }); + + 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(); + }); + + it('should render as blank if null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toEqual(''); + }); + + it('should come up blank when no value specified', function() { + compileInput(''); + + scope.$digest(); + expect(inputElm.val()).toBe(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toBe(''); + }); + + + it('should parse empty string to null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = new Date(2011, 0, 1); + }); + + changeInputValueTo(''); + expect(scope.test).toBeNull(); + expect(inputElm).toBeValid(); + }); + + + 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 render blank if model is not a Date object', function() { + compileInput(''); + + scope.$apply(function(){ + scope.secondWeek = '2013-W02'; + }); + + expect(inputElm.val()).toBe(''); + }); + + 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(); + }); + + it('should render as blank if null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toEqual(''); + }); + + it('should come up blank when no value specified', function() { + compileInput(''); + + scope.$digest(); + expect(inputElm.val()).toBe(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toBe(''); + }); + + + it('should parse empty string to null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = new Date(2011, 0, 1); + }); + + changeInputValueTo(''); + expect(scope.test).toBeNull(); + expect(inputElm).toBeValid(); + }); + + 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 render blank if model is not a Date object', function() { + compileInput(''); + + scope.$apply(function(){ + scope.lunchtime = '2013-12-16T11:30'; + }); + + expect(inputElm.val()).toBe(''); + }); + + 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); + }); + + 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); + }); + + expect(inputElm.val()).toBe('2009-01-06T16:25'); + + 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(); + }); + + it('should render as blank if null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toEqual(''); + }); + + it('should come up blank when no value specified', function() { + compileInput(''); + + scope.$digest(); + expect(inputElm.val()).toBe(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toBe(''); + }); + + + it('should parse empty string to null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = new Date(2011, 0, 1); + }); + + changeInputValueTo(''); + expect(scope.test).toBeNull(); + expect(inputElm).toBeValid(); + }); + + describe('min', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + 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'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(2000, 0, 1, 23, 2)); + expect(scope.form.alias.$error.min).toBeFalsy(); + }); + }); + + describe('max', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + 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'); + expect(inputElm).toBeValid(); + 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'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('2014-01-01T12:34'); + expect(inputElm).toBeInvalid(); + + scope.max = '2001-01-01T01:02'; + 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'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('2010-01-01T12:34'); + expect(inputElm).toBeInvalid(); + + scope.min = '2014-01-01T01:02'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + }); + + describe('time', function () { + it('should render blank if model is not a Date object', function() { + compileInput(''); + + scope.$apply(function(){ + scope.lunchtime = '11:30'; + }); + + expect(inputElm.val()).toBe(''); + }); + + it('should set the view if the model if a valid Date object.', function(){ + compileInput(''); + + scope.$apply(function (){ + scope.threeFortyOnePm = new Date(0, 0, 1, 15, 41); + }); + + expect(inputElm.val()).toBe('15:41'); + }); + + it('should set the model undefined if the view is invalid', function (){ + compileInput(''); + + scope.$apply(function (){ + scope.breakMe = new Date(0, 0, 1, 16, 25); + }); + + expect(inputElm.val()).toBe('16:25'); + + try { + //set to text for browsers with time validation. + inputElm[0].setAttribute('type', 'text'); + } catch(e) { + //for IE8 + } + + changeInputValueTo('stuff'); + expect(inputElm.val()).toBe('stuff'); + expect(scope.breakMe).toBeUndefined(); + expect(inputElm).toBeInvalid(); + }); + + it('should render as blank if null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toEqual(''); + }); + + it('should come up blank when no value specified', function() { + compileInput(''); + + scope.$digest(); + expect(inputElm.val()).toBe(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toBe(''); + }); + + + it('should parse empty string to null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = new Date(2011, 0, 1); + }); + + changeInputValueTo(''); + expect(scope.test).toBeNull(); + expect(inputElm).toBeValid(); + }); + + describe('min', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('01:02'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.min).toBeTruthy(); + }); + + it('should validate', function (){ + changeInputValueTo('23:02'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(0, 0, 1, 23, 2)); + expect(scope.form.alias.$error.min).toBeFalsy(); + }); + }); + + describe('max', function (){ + beforeEach(function (){ + compileInput(''); + scope.$digest(); + }); + + it('should invalidate', function (){ + changeInputValueTo('23:00'); + expect(inputElm).toBeInvalid(); + expect(scope.value).toBeFalsy(); + expect(scope.form.alias.$error.max).toBeTruthy(); + }); + + it('should validate', function() { + changeInputValueTo('05:30'); + expect(inputElm).toBeValid(); + expect(+scope.value).toBe(+new Date(0, 0, 1, 5, 30)); + expect(scope.form.alias.$error.max).toBeFalsy(); + }); + }); + + it('should validate even if max value changes on-the-fly', function(done) { + scope.max = '21:02'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('22:34'); + expect(inputElm).toBeInvalid(); + + scope.max = '12:34'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + + it('should validate even if min value changes on-the-fly', function(done) { + scope.min = '08:45'; + compileInput(''); + scope.$digest(); + + changeInputValueTo('06:15'); + expect(inputElm).toBeInvalid(); + + scope.min = '13:50'; + scope.$digest(function () { + expect(inputElm).toBeValid(); + done(); + }); + }); + }); + + describe('date', function () { + it('should render blank if model is not a Date object.', function() { + compileInput(''); + + scope.$apply(function(){ + scope.birthday = '1977-10-22'; + }); + + 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); + }); + + 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(); + }); + + it('should render as blank if null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toEqual(''); + }); + + it('should come up blank when no value specified', function() { + compileInput(''); + + scope.$digest(); + expect(inputElm.val()).toBe(''); + + scope.$apply(function() { + scope.test = null; + }); + + expect(scope.test).toBeNull(); + expect(inputElm.val()).toBe(''); + }); + + + it('should parse empty string to null', function() { + compileInput(''); + + scope.$apply(function() { + scope.test = new Date(2011, 0, 1); + }); + + changeInputValueTo(''); + expect(scope.test).toBeNull(); + expect(inputElm).toBeValid(); + }); + + 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() { @@ -798,7 +1510,6 @@ describe('input', function() { expect(inputElm.val()).toEqual(''); }); - it('should come up blank when no value specified', function() { compileInput(''); diff --git a/test/ng/filter/filtersSpec.js b/test/ng/filter/filtersSpec.js index 8e6ec1f128f4..efe12e056d46 100644 --- a/test/ng/filter/filtersSpec.js +++ b/test/ng/filter/filtersSpec.js @@ -197,7 +197,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) { @@ -220,6 +220,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');