From 56f5c82c72a0ee8573dec6337ca787e004b3b53f Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Mon, 25 Jan 2016 08:13:31 -0800 Subject: [PATCH 01/10] chore(release): start 1.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c6d127936..2a3da807b6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", "name": "angular-ui-bootstrap", - "version": "1.1.1", + "version": "1.1.2-SNAPSHOT", "homepage": "http://angular-ui.github.io/bootstrap/", "dependencies": {}, "directories": { From 61cdef1ee6478600ce6c3b6be0cdc8ede39bd0fa Mon Sep 17 00:00:00 2001 From: "Towne, Jeremy" Date: Mon, 25 Jan 2016 10:35:34 -0500 Subject: [PATCH 02/10] feat(accordion): add aria tags - Improve accessibility of accordion Closes #5338 --- src/accordion/accordion.js | 4 ++++ src/accordion/test/accordion.spec.js | 26 +++++++++++++++++++++++++ template/accordion/accordion-group.html | 8 ++++---- template/accordion/accordion.html | 2 +- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/accordion/accordion.js b/src/accordion/accordion.js index e44cb2d1bb..002daedca9 100644 --- a/src/accordion/accordion.js +++ b/src/accordion/accordion.js @@ -91,6 +91,10 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) } } }; + + var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + scope.headingId = id + '-tab'; + scope.panelId = id + '-panel'; } }; }) diff --git a/src/accordion/test/accordion.spec.js b/src/accordion/test/accordion.spec.js index f80e7e1453..abbf83691e 100644 --- a/src/accordion/test/accordion.spec.js +++ b/src/accordion/test/accordion.spec.js @@ -123,6 +123,12 @@ describe('uib-accordion', function() { $templateCache = _$templateCache_; })); + it('should be a tablist', function() { + element = $compile('')(scope); + scope.$digest(); + expect(element.html()).toContain('role="tablist"'); + }); + it('should expose the controller on the view', function() { $templateCache.put('uib/template/accordion/accordion.html', '
{{accordion.text}}
'); @@ -150,6 +156,9 @@ describe('uib-accordion', function() { describe('uib-accordion-group', function() { var scope, $compile; var element, groups; + var findGroupHeading = function(index) { + return groups.eq(index).find('.panel-heading').eq(0); + }; var findGroupLink = function(index) { return groups.eq(index).find('.accordion-toggle').eq(0); }; @@ -177,6 +186,7 @@ describe('uib-accordion', function() { describe('with static panels', function() { beforeEach(function() { + spyOn(Math, 'random').and.returnValue(0.1); var tpl = '' + 'Content 1' + @@ -204,20 +214,26 @@ describe('uib-accordion', function() { findGroupLink(0).click(); scope.$digest(); expect(findGroupBody(0).scope().isOpen).toBe(true); + expect(findGroupHeading(0).html()).toContain('aria-expanded="true"'); findGroupLink(1).click(); scope.$digest(); expect(findGroupBody(0).scope().isOpen).toBe(false); + expect(findGroupHeading(0).html()).toContain('aria-expanded="false"'); expect(findGroupBody(1).scope().isOpen).toBe(true); + expect(findGroupHeading(1).html()).toContain('aria-expanded="true"'); }); it('should toggle element on click', function() { findGroupLink(0).click(); scope.$digest(); expect(findGroupBody(0).scope().isOpen).toBe(true); + expect(groups.eq(0).html()).toContain('aria-hidden="false"'); + findGroupLink(0).click(); scope.$digest(); expect(findGroupBody(0).scope().isOpen).toBe(false); + expect(groups.eq(0).html()).toContain('aria-hidden="true"'); }); it('should add, by default, "panel-open" when opened', function() { @@ -256,6 +272,16 @@ describe('uib-accordion', function() { expect(group).not.toHaveClass('panel-open'); }); + + it('should generate an Id for the heading', function() { + var groupScope = findGroupBody(0).scope(); + expect(groupScope.headingId).toEqual('accordiongroup-' + groupScope.$id + '-1000-tab'); + }); + + it('should generate an Id for the panel', function() { + var groupScope = findGroupBody(0).scope(); + expect(groupScope.panelId).toEqual('accordiongroup-' + groupScope.$id + '-1000-panel'); + }); }); describe('with open-class attribute', function() { diff --git a/template/accordion/accordion-group.html b/template/accordion/accordion-group.html index 4c30f9b477..88c05f4eda 100644 --- a/template/accordion/accordion-group.html +++ b/template/accordion/accordion-group.html @@ -1,10 +1,10 @@
-
+ -
-
+
+
diff --git a/template/accordion/accordion.html b/template/accordion/accordion.html index ba428f3b5e..2a55b9bd73 100644 --- a/template/accordion/accordion.html +++ b/template/accordion/accordion.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file From e58c42c5b26c3694d75bb57cf427c31262d47bdd Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Mon, 25 Jan 2016 09:00:59 -0800 Subject: [PATCH 03/10] feat(datepicker): add datepickerOptions support - Add support for options object to concisely configure datepicker Closes #5340 --- src/datepicker/datepicker.js | 197 ++++++++++++---- src/datepicker/docs/readme.md | 23 ++ src/datepicker/test/datepicker.spec.js | 300 ++++++++++++++++++++++++- 3 files changed, 472 insertions(+), 48 deletions(-) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 0a227a6bde..549d523fcf 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -31,66 +31,172 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst // Modes chain this.modes = ['day', 'month', 'year']; - // Interpolated configuration attributes - angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) { - self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key]; - }); + if ($attrs.datepickerOptions) { + angular.forEach([ + 'formatDay', + 'formatDayHeader', + 'formatDayTitle', + 'formatMonth', + 'formatMonthTitle', + 'formatYear', + 'initDate', + 'maxDate', + 'maxMode', + 'minDate', + 'minMode', + 'showWeeks', + 'shortcutPropagation', + 'startingDay', + 'yearColumns', + 'yearRows' + ], function(key) { + switch (key) { + case 'formatDay': + case 'formatDayHeader': + case 'formatDayTitle': + case 'formatMonth': + case 'formatMonthTitle': + case 'formatYear': + self[key] = angular.isDefined($scope.datepickerOptions[key]) ? $interpolate($scope.datepickerOptions[key])($scope.$parent) : datepickerConfig[key]; + break; + case 'showWeeks': + case 'shortcutPropagation': + case 'yearColumns': + case 'yearRows': + self[key] = angular.isDefined($scope.datepickerOptions[key]) ? + $scope.datepickerOptions[key] : datepickerConfig[key]; + break; + case 'startingDay': + if (angular.isDefined($scope.datepickerOptions.startingDay)) { + self.startingDay = $scope.datepickerOptions.startingDay; + } else if (angular.isNumber(datepickerConfig.startingDay)) { + self.startingDay = datepickerConfig.startingDay; + } else { + self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; + } - // Evaled configuration attributes - angular.forEach(['showWeeks', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) { - self[key] = angular.isDefined($attrs[key]) ? - $scope.$parent.$eval($attrs[key]) : datepickerConfig[key]; - }); + break; + case 'maxDate': + case 'minDate': + if ($scope.datepickerOptions[key]) { + $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) { + if (value) { + if (angular.isDate(value)) { + self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); + } else { + self[key] = new Date(dateFilter(value, 'medium')); + } + } else { + self[key] = null; + } + + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null; + } - if (angular.isDefined($attrs.startingDay)) { - self.startingDay = $scope.$parent.$eval($attrs.startingDay); - } else if (angular.isNumber(datepickerConfig.startingDay)) { - self.startingDay = datepickerConfig.startingDay; + break; + case 'maxMode': + case 'minMode': + if ($scope.datepickerOptions[key]) { + $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) { + self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key]; + if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) || + key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) { + $scope.datepickerMode = self[key]; + } + }); + } else { + self[key] = $scope[key] = datepickerConfig[key] || null; + } + + break; + case 'initDate': + if ($scope.datepickerOptions.initDate) { + this.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date(); + $scope.$watch(function() { return $scope.datepickerOptions.initDate; }, function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); + self.refreshView(); + } + }); + } else { + this.activeDate = new Date(); + } + } + }); } else { - self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; - } + // Interpolated configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) { + self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key]; + }); - // Watchable date attributes - angular.forEach(['minDate', 'maxDate'], function(key) { - if ($attrs[key]) { - watchListeners.push($scope.$parent.$watch($attrs[key], function(value) { - self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null; - self.refreshView(); - })); + // Evaled configuration attributes + angular.forEach(['showWeeks', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) { + self[key] = angular.isDefined($attrs[key]) ? + $scope.$parent.$eval($attrs[key]) : datepickerConfig[key]; + }); + + if (angular.isDefined($attrs.startingDay)) { + self.startingDay = $scope.$parent.$eval($attrs.startingDay); + } else if (angular.isNumber(datepickerConfig.startingDay)) { + self.startingDay = datepickerConfig.startingDay; } else { - self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null; + self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; } - }); - angular.forEach(['minMode', 'maxMode'], function(key) { - if ($attrs[key]) { - watchListeners.push($scope.$parent.$watch($attrs[key], function(value) { - self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key]; - if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) || - key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) { - $scope.datepickerMode = self[key]; + // Watchable date attributes + angular.forEach(['minDate', 'maxDate'], function(key) { + if ($attrs[key]) { + watchListeners.push($scope.$parent.$watch($attrs[key], function(value) { + if (value) { + if (angular.isDate(value)) { + self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); + } else { + self[key] = new Date(dateFilter(value, 'medium')); + } + } else { + self[key] = null; + } + + self.refreshView(); + })); + } else { + self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null; + } + }); + + angular.forEach(['minMode', 'maxMode'], function(key) { + if ($attrs[key]) { + watchListeners.push($scope.$parent.$watch($attrs[key], function(value) { + self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key]; + if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) || + key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) { + $scope.datepickerMode = self[key]; + } + })); + } else { + self[key] = $scope[key] = datepickerConfig[key] || null; + } + }); + + if (angular.isDefined($attrs.initDate)) { + this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date(); + watchListeners.push($scope.$parent.$watch($attrs.initDate, function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); + self.refreshView(); } })); } else { - self[key] = $scope[key] = datepickerConfig[key] || null; + this.activeDate = new Date(); } - }); + } $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); - if (angular.isDefined($attrs.initDate)) { - this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date(); - watchListeners.push($scope.$parent.$watch($attrs.initDate, function(initDate) { - if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { - self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); - self.refreshView(); - } - })); - } else { - this.activeDate = new Date(); - } - $scope.disabled = angular.isDefined($attrs.disabled) || false; if (angular.isDefined($attrs.ngDisabled)) { watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) { @@ -501,6 +607,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst }, scope: { datepickerMode: '=?', + datepickerOptions: '=?', dateDisabled: '&', customClass: '&', shortcutPropagation: '&?' diff --git a/src/datepicker/docs/readme.md b/src/datepicker/docs/readme.md index b686bc725d..277e2e61ed 100644 --- a/src/datepicker/docs/readme.md +++ b/src/datepicker/docs/readme.md @@ -27,6 +27,29 @@ The datepicker has 3 modes: _(Default: `day`)_ - Current mode of the datepicker _(day|month|year)_. Can be used to initialize the datepicker in a specific mode. +* `datepicker-options` + $ - + An optional object to configure the datepicker in one place. If this attribute is used, all supported options must be specified instead of the attributes. + + The supported options are: + + - formatDay + - formatDayHeader + - formatDayTitle + - formatMonth + - formatMonthTitle + - formatYear + - initDate + - maxDate + - maxMode + - minDate + - minMode + - shortcutPropagation + - showWeeks + - startingDay + - yearColumns + - yearRows + * `format-day` C _(Default: `dd`)_ - diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index b2a9263ebf..84c79cf643 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -866,7 +866,301 @@ describe('datepicker', function() { }); }); - describe('attribute `starting-day`', function () { + describe('attribute `datepicker-options`', function() { + describe('startingDay', function() { + beforeEach(function() { + $rootScope.datepickerOptions = { + startingDay: 1 + }; + element = $compile('')($rootScope); + $rootScope.$digest(); + }); + + it('shows the day labels rotated', function() { + expect(getLabels(true)).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); + }); + + it('renders the calendar days correctly', function() { + expect(getOptions(true)).toEqual([ + ['30', '31', '01', '02', '03', '04', '05'], + ['06', '07', '08', '09', '10', '11', '12'], + ['13', '14', '15', '16', '17', '18', '19'], + ['20', '21', '22', '23', '24', '25', '26'], + ['27', '28', '29', '30', '01', '02', '03'], + ['04', '05', '06', '07', '08', '09', '10'] + ]); + }); + + it('renders the week numbers correctly', function() { + expect(getWeeks()).toEqual(['35', '36', '37', '38', '39', '40']); + }); + }); + + describe('showWeeks', function() { + beforeEach(function() { + $rootScope.datepickerOptions = { + showWeeks: false + }; + element = $compile('')($rootScope); + $rootScope.$digest(); + }); + + it('hides week numbers based on variable', function() { + expect(getLabelsRow().find('th').length).toEqual(7); + var tr = element.find('tbody').find('tr'); + for (var i = 0; i < 5; i++) { + expect(tr.eq(i).find('td').length).toEqual(7); + } + }); + }); + + describe('minDate', function() { + beforeEach(function() { + $rootScope.datepickerOptions = { + minDate: new Date('September 12, 2010') + }; + element = $compile('')($rootScope); + $rootScope.$digest(); + }); + + it('disables appropriate days in current month', function() { + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index < 14); + }); + }); + + it('disables appropriate days when min date changes', function() { + $rootScope.datepickerOptions.minDate = new Date('September 5, 2010'); + $rootScope.$digest(); + + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index < 7); + }); + }); + + it('invalidates when model is a disabled date', function() { + $rootScope.datepickerOptions.minDate = new Date('September 5, 2010'); + $rootScope.date = new Date('September 2, 2010'); + $rootScope.$digest(); + expect(element.hasClass('ng-invalid')).toBeTruthy(); + expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy(); + }); + + it('disables all days in previous month', function() { + clickPreviousButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(true); + }); + }); + + it('disables no days in next month', function() { + clickNextButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(false); + }); + }); + + it('disables appropriate months in current year', function() { + clickTitleButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index < 8); + }); + }); + + it('disables all months in previous year', function() { + clickTitleButton(); + clickPreviousButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(true); + }); + }); + + it('disables no months in next year', function() { + clickTitleButton(); + clickNextButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(false); + }); + }); + + it('enables everything before if it is cleared', function() { + $rootScope.datepickerOptions.minDate = null; + $rootScope.date = new Date('December 20, 1949'); + $rootScope.$digest(); + + clickTitleButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(false); + }); + }); + + it('accepts literals, \'yyyy-MM-dd\' case', function() { + $rootScope.datepickerOptions.minDate = '2010-09-05'; + element = $compile('')($rootScope); + $rootScope.$digest(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index < 7); + }); + }); + }); + + describe('maxDate', function() { + beforeEach(function() { + $rootScope.datepickerOptions = { + maxDate: new Date('September 25, 2010') + }; + element = $compile('')($rootScope); + $rootScope.$digest(); + }); + + it('disables appropriate days in current month', function() { + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index > 27); + }); + }); + + it('disables appropriate days when max date changes', function() { + $rootScope.datepickerOptions.maxDate = new Date('September 18, 2010'); + $rootScope.$digest(); + + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index > 20); + }); + }); + + it('invalidates when model is a disabled date', function() { + $rootScope.datepickerOptions.maxDate = new Date('September 18, 2010'); + $rootScope.$digest(); + expect(element.hasClass('ng-invalid')).toBeTruthy(); + expect(element.hasClass('ng-invalid-date-disabled')).toBeTruthy(); + }); + + it('disables no days in previous month', function() { + clickPreviousButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(false); + }); + }); + + it('disables all days in next month', function() { + clickNextButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(true); + }); + }); + + it('disables appropriate months in current year', function() { + clickTitleButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(index > 8); + }); + }); + + it('disables no months in previous year', function() { + clickTitleButton(); + clickPreviousButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(false); + }); + }); + + it('disables all months in next year', function() { + clickTitleButton(); + clickNextButton(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(true); + }); + }); + + it('enables everything after if it is cleared', function() { + $rootScope.datepickerOptions.maxDate = null; + $rootScope.$digest(); + var buttons = getAllOptionsEl(); + angular.forEach(buttons, function(button, index) { + expect(angular.element(button).prop('disabled')).toBe(false); + }); + }); + }); + + describe('formatting', function() { + beforeEach(function() { + $rootScope.datepickerOptions = { + formatDay: 'd', + formatDayHeader: 'EEEE', + formatDayTitle: 'MMMM, yy', + formatMonth: 'MMM', + formatMonthTitle: 'yy', + formatYear: 'yy', + yearColumns: 4, + yearRows: 3 + }; + element = $compile('')($rootScope); + $rootScope.$digest(); + }); + + it('changes the title format in `day` mode', function() { + expect(getTitle()).toBe('September, 10'); + }); + + it('changes the title & months format in `month` mode', function() { + clickTitleButton(); + + expect(getTitle()).toBe('10'); + expect(getOptions()).toEqual([ + ['Jan', 'Feb', 'Mar'], + ['Apr', 'May', 'Jun'], + ['Jul', 'Aug', 'Sep'], + ['Oct', 'Nov', 'Dec'] + ]); + }); + + it('changes the title, year format & range in `year` mode', function() { + clickTitleButton(); + clickTitleButton(); + + expect(getTitle()).toBe('05 - 16'); + expect(getOptions()).toEqual([ + ['05', '06', '07', '08'], + ['09', '10', '11', '12'], + ['13', '14', '15', '16'] + ]); + }); + + it('shows day labels', function() { + expect(getLabels(true)).toEqual(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']); + }); + + it('changes the day format', function() { + expect(getOptions(true)).toEqual([ + ['29', '30', '31', '1', '2', '3', '4'], + ['5', '6', '7', '8', '9', '10', '11'], + ['12', '13', '14', '15', '16', '17', '18'], + ['19', '20', '21', '22', '23', '24', '25'], + ['26', '27', '28', '29', '30', '1', '2'], + ['3', '4', '5', '6', '7', '8', '9'] + ]); + }); + }); + }); + + describe('attribute `starting-day`', function() { beforeEach(function() { $rootScope.startingDay = 1; element = $compile('')($rootScope); @@ -909,7 +1203,7 @@ describe('datepicker', function() { }); }); - describe('`min-date` attribute', function () { + describe('`min-date` attribute', function() { beforeEach(function() { $rootScope.mindate = new Date('September 12, 2010'); element = $compile('')($rootScope); @@ -1088,7 +1382,7 @@ describe('datepicker', function() { }); }); - describe('date-disabled expression', function () { + describe('date-disabled expression', function() { beforeEach(function() { $rootScope.dateDisabledHandler = jasmine.createSpy('dateDisabledHandler'); element = $compile('')($rootScope); From b1ad080741a01dd7e0db697acb98f26efbc1cf8a Mon Sep 17 00:00:00 2001 From: Foxandxss Date: Thu, 28 Jan 2016 00:36:21 +0100 Subject: [PATCH 04/10] docs(datepicker): fix italics --- src/datepicker/docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datepicker/docs/readme.md b/src/datepicker/docs/readme.md index 277e2e61ed..f97a83099e 100644 --- a/src/datepicker/docs/readme.md +++ b/src/datepicker/docs/readme.md @@ -142,7 +142,7 @@ The datepicker has 3 modes: * `starting-day` $ C - _(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)_ - + *(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)* - Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday). * `template-url` From d2621e3f985b8ca3e2d12a3c9a0f24562000bf9c Mon Sep 17 00:00:00 2001 From: AndriIushchuk Date: Mon, 18 Jan 2016 13:51:03 +0200 Subject: [PATCH 05/10] fix(modal): ensure shift+tab is trapped in modal - Fixes where shift+tab keystrokes are not causing focus to remain trapped in modal Closes #5294 Fixes #5229 --- src/modal/modal.js | 12 +++++++++++- src/modal/test/modal.spec.js | 12 ++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/modal/modal.js b/src/modal/modal.js index c88840562a..67101c74d2 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -404,7 +404,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) $modalStack.loadFocusElementList(modal); var focusChanged = false; if (evt.shiftKey) { - if ($modalStack.isFocusInFirstItem(evt)) { + if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) { focusChanged = $modalStack.focusLastFocusableElement(); } } else { @@ -547,6 +547,16 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) return false; }; + $modalStack.isModalFocused = function(evt, modalWindow) { + if (evt && modalWindow) { + var modalDomEl = modalWindow.value.modalDomEl; + if (modalDomEl && modalDomEl.length) { + return (evt.target || evt.srcElement) === modalDomEl[0]; + } + } + return false; + }; + $modalStack.isFocusInFirstItem = function(evt) { if (focusableElementList.length > 0) { return (evt.target || evt.srcElement) === focusableElementList[0]; diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index 3695927dff..676c0d30be 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -620,11 +620,15 @@ describe('$uibModal', function() { template:'' + '' }); + $rootScope.$digest(); expect($document).toHaveModalsOpen(1); + triggerKeyDown(angular.element(document.activeElement), 9, true); + expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button'); + var lastElement = angular.element(document.getElementById('tab-focus-link')); lastElement.focus(); - triggerKeyDown(lastElement, 9, true); + triggerKeyDown(angular.element(document.activeElement), 9, true); expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button'); initialPage.remove(); @@ -660,11 +664,15 @@ describe('$uibModal', function() { '', keyboard: false }); + $rootScope.$digest(); expect($document).toHaveModalsOpen(1); + triggerKeyDown(angular.element(document.activeElement), 9, true); + expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button'); + var lastElement = angular.element(document.getElementById('tab-focus-link')); lastElement.focus(); - triggerKeyDown(lastElement, 9, true); + triggerKeyDown(angular.element(document.activeElement), 9, true); expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button'); initialPage.remove(); From dbe9e8171f3069a5eebbe6842fa10e15514edee9 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Thu, 28 Jan 2016 05:53:42 -0800 Subject: [PATCH 06/10] fix(typeahead): add guard for no $viewValue - Add guard when $viewValue is undefined, and when showHint & min-length 0 is used Closes #5358 Fixes #5357 --- src/typeahead/typeahead.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index b1a405d397..acc7d1df75 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -247,10 +247,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap if (showHint) { var firstLabel = scope.matches[0].label; - if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { + if (angular.isString(inputValue) && + inputValue.length > 0 && + firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { hintInputElem.val(inputValue + firstLabel.slice(inputValue.length)); - } - else { + } else { hintInputElem.val(''); } } From 0b7e742f6e5132f70afe59c687f8625b77b36940 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Thu, 28 Jan 2016 06:19:35 -0800 Subject: [PATCH 07/10] docs(carousel): make sure request is made to http - Ensure request uses http protocol and not https potentially Closes #5359 --- src/carousel/docs/demo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/carousel/docs/demo.js b/src/carousel/docs/demo.js index 828a92f30a..7a42a01fd6 100644 --- a/src/carousel/docs/demo.js +++ b/src/carousel/docs/demo.js @@ -7,7 +7,7 @@ angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($sc $scope.addSlide = function() { var newWidth = 600 + slides.length + 1; slides.push({ - image: '//lorempixel.com/' + newWidth + '/300', + image: 'http://lorempixel.com/' + newWidth + '/300', text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4], id: currIndex++ }); From 48c9cd8f5183239219ccda7e4f7fbadde24bc57b Mon Sep 17 00:00:00 2001 From: Matthew McLeod Date: Thu, 28 Jan 2016 17:13:23 +0200 Subject: [PATCH 08/10] fix(tooltip): prevent closing on $locationChangeSuccess - Remove incorrect logic for closing tooltip/popover on $locationChangeSuccess Closes #5360 Fixes #5337 --- src/tooltip/test/tooltip.spec.js | 19 ------------------- src/tooltip/tooltip.js | 13 +------------ 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 11d570996d..870c67d461 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -998,25 +998,6 @@ describe('$uibTooltipProvider', function() { expect($body.children().length).toEqual(bodyLength); })); - it('should close on location change', inject(function($rootScope, $compile) { - elmBody = angular.element( - '
Selector Text
' - ); - - scope = $rootScope; - $compile(elmBody)(scope); - scope.$digest(); - elm = elmBody.find('span'); - elmScope = elm.scope(); - tooltipScope = elmScope.$$childTail; - - trigger(elm, 'mouseenter'); - expect(tooltipScope.isOpen).toBe(true); - - scope.$broadcast('$locationChangeSuccess'); - scope.$digest(); - expect(tooltipScope.isOpen).toBe(false); - })); }); describe('triggers', function() { diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 440243e924..7609d0eb72 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -540,18 +540,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s } appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; - - // if a tooltip is attached to we need to remove it on - // location change as its parent scope will probably not be destroyed - // by the change. - if (appendToBody) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() { - if (ttScope.isOpen) { - hide(); - } - }); - } - + // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTooltip() { unregisterTriggers(); From c3b14315d99f9be87853eb4063dd8b6721b4dc22 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Mon, 1 Feb 2016 07:16:32 -0800 Subject: [PATCH 09/10] fix(datepicker): fix validation for M! & d! - Fix display & validation for M! & d! Closes #5376 Fixes #5225 --- src/dateparser/dateparser.js | 163 ++++++++++++---- src/dateparser/test/dateparser.spec.js | 248 ++++++++++++++++++++++++- src/datepicker/datepicker.js | 13 +- 3 files changed, 384 insertions(+), 40 deletions(-) diff --git a/src/dateparser/dateparser.js b/src/dateparser/dateparser.js index deaf548d39..cb4a8290c9 100644 --- a/src/dateparser/dateparser.js +++ b/src/dateparser/dateparser.js @@ -1,6 +1,6 @@ angular.module('ui.bootstrap.dateparser', []) -.service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) { +.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) { // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; @@ -11,115 +11,164 @@ angular.module('ui.bootstrap.dateparser', []) localeId = $locale.id; this.parsers = {}; + this.formatters = {}; formatCodeToRegex = [ { key: 'yyyy', regex: '\\d{4}', - apply: function(value) { this.year = +value; } + apply: function(value) { this.year = +value; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'yyyy'); + } }, { key: 'yy', regex: '\\d{2}', - apply: function(value) { this.year = +value + 2000; } + apply: function(value) { this.year = +value + 2000; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'yy'); + } }, { key: 'y', regex: '\\d{1,4}', - apply: function(value) { this.year = +value; } + apply: function(value) { this.year = +value; }, + formatter: function(date) { + var _date = new Date(); + _date.setFullYear(Math.abs(date.getFullYear())); + return dateFilter(_date, 'y'); + } }, { key: 'M!', regex: '0?[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { + var value = date.getMonth(); + if (/^[0-9]$/.test(value)) { + return dateFilter(date, 'MM'); + } + + return dateFilter(date, 'M'); + } }, { key: 'MMMM', regex: $locale.DATETIME_FORMATS.MONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, + formatter: function(date) { return dateFilter(date, 'MMMM'); } }, { key: 'MMM', regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, + formatter: function(date) { return dateFilter(date, 'MMM'); } }, { key: 'MM', regex: '0[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { return dateFilter(date, 'MM'); } }, { key: 'M', regex: '[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } + apply: function(value) { this.month = value - 1; }, + formatter: function(date) { return dateFilter(date, 'M'); } }, { key: 'd!', regex: '[0-2]?[0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } + apply: function(value) { this.date = +value; }, + formatter: function(date) { + var value = date.getDate(); + if (/^[1-9]$/.test(value)) { + return dateFilter(date, 'dd'); + } + + return dateFilter(date, 'd'); + } }, { key: 'dd', regex: '[0-2][0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } + apply: function(value) { this.date = +value; }, + formatter: function(date) { return dateFilter(date, 'dd'); } }, { key: 'd', regex: '[1-2]?[0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } + apply: function(value) { this.date = +value; }, + formatter: function(date) { return dateFilter(date, 'd'); } }, { key: 'EEEE', - regex: $locale.DATETIME_FORMATS.DAY.join('|') + regex: $locale.DATETIME_FORMATS.DAY.join('|'), + formatter: function(date) { return dateFilter(date, 'EEEE'); } }, { key: 'EEE', - regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), + formatter: function(date) { return dateFilter(date, 'EEE'); } }, { key: 'HH', regex: '(?:0|1)[0-9]|2[0-3]', - apply: function(value) { this.hours = +value; } + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'HH'); } }, { key: 'hh', regex: '0[0-9]|1[0-2]', - apply: function(value) { this.hours = +value; } + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'hh'); } }, { key: 'H', regex: '1?[0-9]|2[0-3]', - apply: function(value) { this.hours = +value; } + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'H'); } }, { key: 'h', regex: '[0-9]|1[0-2]', - apply: function(value) { this.hours = +value; } + apply: function(value) { this.hours = +value; }, + formatter: function(date) { return dateFilter(date, 'h'); } }, { key: 'mm', regex: '[0-5][0-9]', - apply: function(value) { this.minutes = +value; } + apply: function(value) { this.minutes = +value; }, + formatter: function(date) { return dateFilter(date, 'mm'); } }, { key: 'm', regex: '[0-9]|[1-5][0-9]', - apply: function(value) { this.minutes = +value; } + apply: function(value) { this.minutes = +value; }, + formatter: function(date) { return dateFilter(date, 'm'); } }, { key: 'sss', regex: '[0-9][0-9][0-9]', - apply: function(value) { this.milliseconds = +value; } + apply: function(value) { this.milliseconds = +value; }, + formatter: function(date) { return dateFilter(date, 'sss'); } }, { key: 'ss', regex: '[0-5][0-9]', - apply: function(value) { this.seconds = +value; } + apply: function(value) { this.seconds = +value; }, + formatter: function(date) { return dateFilter(date, 'ss'); } }, { key: 's', regex: '[0-9]|[1-5][0-9]', - apply: function(value) { this.seconds = +value; } + apply: function(value) { this.seconds = +value; }, + formatter: function(date) { return dateFilter(date, 's'); } }, { key: 'a', @@ -132,7 +181,8 @@ angular.module('ui.bootstrap.dateparser', []) if (value === 'PM') { this.hours += 12; } - } + }, + formatter: function(date) { return dateFilter(date, 'a'); } }, { key: 'Z', @@ -144,38 +194,47 @@ angular.module('ui.bootstrap.dateparser', []) minutes = matches[3]; this.hours += toInt(sign + hours); this.minutes += toInt(sign + minutes); + }, + formatter: function(date) { + return dateFilter(date, 'Z'); } }, { key: 'ww', - regex: '[0-4][0-9]|5[0-3]' + regex: '[0-4][0-9]|5[0-3]', + formatter: function(date) { return dateFilter(date, 'ww'); } }, { key: 'w', - regex: '[0-9]|[1-4][0-9]|5[0-3]' + regex: '[0-9]|[1-4][0-9]|5[0-3]', + formatter: function(date) { return dateFilter(date, 'w'); } }, { key: 'GGGG', - regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s') + regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), + formatter: function(date) { return dateFilter(date, 'GGGG'); } }, { key: 'GGG', - regex: $locale.DATETIME_FORMATS.ERAS.join('|') + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'GGG'); } }, { key: 'GG', - regex: $locale.DATETIME_FORMATS.ERAS.join('|') + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'GG'); } }, { key: 'G', - regex: $locale.DATETIME_FORMATS.ERAS.join('|') + regex: $locale.DATETIME_FORMATS.ERAS.join('|'), + formatter: function(date) { return dateFilter(date, 'G'); } } ]; }; this.init(); - function createParser(format) { + function createParser(format, func) { var map = [], regex = format.split(''); // check for literal values @@ -223,7 +282,8 @@ angular.module('ui.bootstrap.dateparser', []) map.push({ index: index, - apply: data.apply, + key: data.key, + apply: data[func], matcher: data.regex }); } @@ -235,6 +295,41 @@ angular.module('ui.bootstrap.dateparser', []) }; } + this.filter = function(date, format) { + if (!angular.isDate(date) || isNaN(date) || !format) { + return ''; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.formatters[format]) { + this.formatters[format] = createParser(format, 'formatter'); + } + + var parser = this.formatters[format], + map = parser.map; + + var _format = format; + + return map.reduce(function(str, mapper, i) { + var match = _format.match(new RegExp('(.*)' + mapper.key)); + if (match && angular.isString(match[1])) { + str += match[1]; + _format = _format.replace(match[1] + mapper.key, ''); + } + + if (mapper.apply) { + return str + mapper.apply.call(null, date); + } + + return str; + }, ''); + }; + this.parse = function(input, format, baseDate) { if (!angular.isString(input) || !format) { return input; @@ -248,7 +343,7 @@ angular.module('ui.bootstrap.dateparser', []) } if (!this.parsers[format]) { - this.parsers[format] = createParser(format); + this.parsers[format] = createParser(format, 'apply'); } var parser = this.parsers[format], @@ -336,7 +431,7 @@ angular.module('ui.bootstrap.dateparser', []) this.timezoneToOffset = timezoneToOffset; this.addDateMinutes = addDateMinutes; this.convertTimezoneToLocal = convertTimezoneToLocal; - + function toTimezone(date, timezone) { return date && timezone ? convertTimezoneToLocal(date, timezone) : date; } diff --git a/src/dateparser/test/dateparser.spec.js b/src/dateparser/test/dateparser.spec.js index 36ed89d8f4..22dff9759f 100644 --- a/src/dateparser/test/dateparser.spec.js +++ b/src/dateparser/test/dateparser.spec.js @@ -8,6 +8,10 @@ describe('date parser', function() { oldDate.setFullYear(1); })); + function expectFilter(date, format, display) { + expect(dateParser.filter(date, format)).toEqual(display); + } + function expectParse(input, format, date) { expect(dateParser.parse(input, format)).toEqual(date); } @@ -16,6 +20,249 @@ describe('date parser', function() { expect(dateParser.parse(input, format, baseDate)).toEqual(date); } + describe('filter', function() { + it('should work correctly for `dd`, `MM`, `yyyy`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy', '17.11.2013'); + expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.yyyy', '31.12.2013'); + expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-yyyy', '08-03-1991'); + expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/yyyy', '03/05/1980'); + expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/yyyy', '10.01/1983'); + expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-yyyy', '11-09-1980'); + expectFilter(new Date(2011, 1, 5, 0), 'yyyy/MM/dd', '2011/02/05'); + expectFilter(oldDate, 'yyyy/MM/dd', '0001/03/06'); + }); + + it('should work correctly for `yy`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yy', '17.11.13'); + expectFilter(new Date(2011, 4, 2, 0), 'dd-MM-yy', '02-05-11'); + expectFilter(new Date(2080, 1, 5, 0), 'MM/dd/yy', '02/05/80'); + expectFilter(new Date(2055, 1, 5, 0), 'yy/MM/dd', '55/02/05'); + expectFilter(new Date(2013, 7, 11, 0), 'dd-MM-yy', '11-08-13'); + }); + + it('should work correctly for `y`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.y', '17.11.2013'); + expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.y', '31.12.2013'); + expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-y', '08-03-1991'); + expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/y', '03/05/1980'); + expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/y', '10.01/1983'); + expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-y', '11-09-1980'); + expectFilter(new Date(2011, 1, 5, 0), 'y/MM/dd', '2011/02/05'); + }); + + it('should work correctly for `MMMM`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'dd.MMMM.yy', '17.November.13'); + expectFilter(new Date(1980, 2, 5, 0), 'dd-MMMM-yyyy', '05-March-1980'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/dd/yyyy', 'February/05/1980'); + expectFilter(new Date(1949, 11, 20, 0), 'yyyy/MMMM/dd', '1949/December/20'); + expectFilter(oldDate, 'yyyy/MMMM/dd', '0001/March/06'); + }); + + it('should work correctly for `MMM`', function() { + expectFilter(new Date(2010, 8, 30, 0), 'dd.MMM.yy', '30.Sep.10'); + expectFilter(new Date(2011, 4, 2, 0), 'dd-MMM-yy', '02-May-11'); + expectFilter(new Date(1980, 1, 5, 0), 'MMM/dd/yyyy', 'Feb/05/1980'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMM/dd', '1955/Feb/05'); + expectFilter(oldDate, 'yyyy/MMM/dd', '0001/Mar/06'); + }); + + it('should work correctly for `M`', function() { + expectFilter(new Date(2013, 7, 11, 0), 'M/dd/yyyy', '8/11/2013'); + expectFilter(new Date(2005, 10, 7, 0), 'dd.M.yy', '07.11.05'); + expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11'); + expectFilter(new Date(1980, 1, 5, 0), 'M/dd/yyyy', '2/05/1980'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M/dd', '1955/2/05'); + expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11'); + }); + + it('should work correctly for `M!`', function() { + expectFilter(new Date(2013, 7, 11, 0), 'M!/dd/yyyy', '08/11/2013'); + expectFilter(new Date(2005, 10, 7, 0), 'dd.M!.yy', '07.11.05'); + expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11'); + expectFilter(new Date(1980, 1, 5, 0), 'M!/dd/yyyy', '02/05/1980'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M!/dd', '1955/02/05'); + expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11'); + expectFilter(oldDate, 'yyyy/M!/dd', '0001/03/06'); + }); + + it('should work correctly for `d`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy', '17.November.13'); + expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy', '8-March-1991'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy', 'February/5/1980'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d', '1955/February/5'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy', '11-08-13'); + expectFilter(oldDate, 'yyyy/MM/d', '0001/03/6'); + }); + + it('should work correctly for `d!`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd!.MMMM.yy', '17.November.13'); + expectFilter(new Date(1991, 2, 8, 0), 'd!-MMMM-yyyy', '08-March-1991'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d!/yyyy', 'February/05/1980'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d!', '1955/February/05'); + expectFilter(new Date(2013, 7, 11, 0), 'd!-MM-yy', '11-08-13'); + expectFilter(oldDate, 'yyyy/MM/d!', '0001/03/06'); + }); + + it('should work correctly for `EEEE`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'EEEE.d.MMMM.yy', 'Sunday.17.November.13'); + expectFilter(new Date(1991, 2, 8, 0), 'd-EEEE-MMMM-yyyy', '8-Friday-March-1991'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEEE', 'February/5/1980/Tuesday'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEEE/MMMM/d', '1955/Saturday/February/5'); + }); + + it('should work correctly for `EEE`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'EEE.d.MMMM.yy', 'Sun.17.November.13'); + expectFilter(new Date(1991, 2, 8, 0), 'd-EEE-MMMM-yyyy', '8-Fri-March-1991'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEE', 'February/5/1980/Tue'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEE/MMMM/d', '1955/Sat/February/5'); + }); + + it('should work correctly for `HH`', function() { + expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.HH', '22.March.15.22'); + expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-HH', '8-March-1991-11'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/HH', 'February/5/1980/00'); + expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d HH', '1955/February/5 03'); + expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy HH', '11-08-13 23'); + }); + + it('should work correctly for `H`', function() { + expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.H', '22.March.15.22'); + expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-H', '8-March-1991-11'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/H', 'February/5/1980/0'); + expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d H', '1955/February/5 3'); + expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy H', '11-08-13 23'); + }); + + it('should work correctly for `hh`', function() { + expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.hh', '22.March.15.12'); + expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hh', '8-March-1991-11'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hh', 'February/5/1980/12'); + expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hh', '1955/February/5 03'); + expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hh', '11-08-13 09'); + }); + + it('should work correctly for `h`', function() { + expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.h', '22.March.15.12'); + expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-h', '8-March-1991-11'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/h', 'February/5/1980/12'); + expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d h', '1955/February/5 3'); + expectFilter(new Date(2013, 7, 11, 3), 'd-MM-yy h', '11-08-13 3'); + }); + + it('should work correctly for `mm`', function() { + expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.mm', '22.March.15.22'); + expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-mm', '8-March-1991-59'); + expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/mm', 'February/5/1980/00'); + expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d mm', '1955/February/5 03'); + expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy mm', '11-08-13 46'); + expectFilter(new Date(2015, 2, 22, 22, 33), 'd.MMMM.yy.HH:mm', '22.March.15.22:33'); + expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:mm', '22.March.15.2:01'); + }); + + it('should work correctly for `m`', function() { + expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.m', '22.March.15.22'); + expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-m', '8-March-1991-59'); + expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/m', 'February/5/1980/0'); + expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d m', '1955/February/5 3'); + expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy m', '11-08-13 46'); + expectFilter(new Date(2015, 2, 22, 22, 3), 'd.MMMM.yy.HH:m', '22.March.15.22:3'); + expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:m', '22.March.15.2:1'); + }); + + it('should work correctly for `sss`', function() { + expectFilter(new Date(2015, 2, 22, 0, 0, 0, 123), 'd.MMMM.yy.sss', '22.March.15.123'); + expectFilter(new Date(1991, 2, 8, 0, 0, 0, 59), 'd-MMMM-yyyy-sss', '8-March-1991-059'); + expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/sss', 'February/5/1980/000'); + expectFilter(new Date(1955, 1, 5, 0, 0, 0, 3), 'yyyy/MMMM/d sss', '1955/February/5 003'); + expectFilter(new Date(2013, 7, 11, 0, 0, 0, 46), 'd-MM-yy sss', '11-08-13 046'); + expectFilter(new Date(2015, 2, 22, 22, 33, 0, 44), 'd.MMMM.yy.HH:mm:sss', '22.March.15.22:33:044'); + expectFilter(new Date(2015, 2, 22, 0, 0, 0, 1), 'd.MMMM.yy.H:m:sss', '22.March.15.0:0:001'); + }); + + it('should work correctly for `ss`', function() { + expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.ss', '22.March.15.22'); + expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-ss', '8-March-1991-59'); + expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/ss', 'February/5/1980/00'); + expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d ss', '1955/February/5 03'); + expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy ss', '11-08-13 46'); + expectFilter(new Date(2015, 2, 22, 22, 33, 44), 'd.MMMM.yy.HH:mm:ss', '22.March.15.22:33:44'); + expectFilter(new Date(2015, 2, 22, 0, 0, 1), 'd.MMMM.yy.H:m:ss', '22.March.15.0:0:01'); + }); + + it('should work correctly for `s`', function() { + expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.s', '22.March.15.22'); + expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-s', '8-March-1991-59'); + expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/s', 'February/5/1980/0'); + expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d s', '1955/February/5 3'); + expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy s', '11-08-13 46'); + expectFilter(new Date(2015, 2, 22, 22, 33, 4), 'd.MMMM.yy.HH:mm:s', '22.March.15.22:33:4'); + expectFilter(new Date(2015, 2, 22, 22, 3, 4), 'd.MMMM.yy.HH:m:s', '22.March.15.22:3:4'); + }); + + it('should work correctly for `a`', function() { + expectFilter(new Date(2015, 2, 22, 10), 'd.MMMM.yy.hha', '22.March.15.10AM'); + expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.hha', '22.March.15.10PM'); + expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hha', '8-March-1991-11AM'); + expectFilter(new Date(1991, 2, 8, 23), 'd-MMMM-yyyy-hha', '8-March-1991-11PM'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hha', 'February/5/1980/12AM'); + expectFilter(new Date(1980, 1, 5, 12), 'MMMM/d/yyyy/hha', 'February/5/1980/12PM'); + expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hha', '1955/February/5 03AM'); + expectFilter(new Date(1955, 1, 5, 15), 'yyyy/MMMM/d hha', '1955/February/5 03PM'); + expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hha', '11-08-13 09AM'); + expectFilter(new Date(2013, 7, 11, 21), 'd-MM-yy hha', '11-08-13 09PM'); + }); + + it('should work correctly for `ww`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.ww', '17.November.13.47'); + expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-ww', '8-March-1991-10'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/ww', 'February/5/1980/06'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/ww', '1955/February/5/05'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy ww', '11-08-13 33'); + expectFilter(oldDate, 'yyyy/MM/d ww', '0001/03/6 10'); + }); + + it('should work correctly for `w`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.w', '17.November.13.47'); + expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-w', '8-March-1991-10'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/w', 'February/5/1980/6'); + expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/w', '1955/February/5/5'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy w', '11-08-13 33'); + expectFilter(oldDate, 'yyyy/MM/d w', '0001/03/6 10'); + }); + + it('should work correctly for `G`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.G', '17.November.13.AD'); + expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-G', '8-March-1991-BC'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/G', 'February/5/1980/AD'); + expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/G', '1955/February/5/BC'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy G', '11-08-13 AD'); + }); + + it('should work correctly for `GG`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GG', '17.November.13.AD'); + expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GG', '8-March-1991-BC'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GG', 'February/5/1980/AD'); + expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GG', '1955/February/5/BC'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GG', '11-08-13 AD'); + }); + + it('should work correctly for `GGG`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGG', '17.November.13.AD'); + expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGG', '8-March-1991-BC'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGG', 'February/5/1980/AD'); + expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGG', '1955/February/5/BC'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGG', '11-08-13 AD'); + }); + + it('should work correctly for `GGGG`', function() { + expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGGG', '17.November.13.Anno Domini'); + expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGGG', '8-March-1991-Before Christ'); + expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGGG', 'February/5/1980/Anno Domini'); + expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGGG', '1955/February/5/Before Christ'); + expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGGG', '11-08-13 Anno Domini'); + }); + }); + describe('with custom formats', function() { it('should work correctly for `dd`, `MM`, `yyyy`', function() { expectParse('17.11.2013', 'dd.MM.yyyy', new Date(2013, 10, 17, 0)); @@ -391,7 +638,6 @@ describe('date parser', function() { expect(dateParser.init).toHaveBeenCalled(); })); - describe('timezone functions', function() { describe('toTimezone', function() { it('adjusts date: PST - EST', function() { diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 549d523fcf..6cd7afcdea 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -260,7 +260,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst model = dateParser.fromTimezone(model, ngModelOptions.timezone); var dt = { date: date, - label: dateFilter(date, format.replace(/d!/, 'dd')).replace(/M!/, 'MM'), + label: dateParser.filter(date, format), selected: model && this.compare(date, model) === 0, disabled: this.isDisabled(date), current: this.compare(date, new Date()) === 0, @@ -848,11 +848,14 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi scope.date = value; return value; } + scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); - dateFormat = dateFormat.replace(/M!/, 'MM') - .replace(/d!/, 'dd'); - return dateFilter(scope.date, dateFormat); + if (angular.isNumber(scope.date)) { + scope.date = new Date(scope.date); + } + + return dateParser.filter(scope.date, dateFormat); }); } else { ngModel.$formatters.push(function(value) { @@ -920,7 +923,7 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi if (angular.isDefined(dt)) { scope.date = dt; } - var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function + var date = scope.date ? dateParser.filter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function element.val(date); ngModel.$setViewValue(date); From dbd3947002752bae6a4221bf4ca91ea1b7b45c1c Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Mon, 1 Feb 2016 08:58:21 -0800 Subject: [PATCH 10/10] chore(release): 1.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a3da807b6..fd8e39b640 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", "name": "angular-ui-bootstrap", - "version": "1.1.2-SNAPSHOT", + "version": "1.1.2", "homepage": "http://angular-ui.github.io/bootstrap/", "dependencies": {}, "directories": {