diff --git a/config/karma-docs.conf.js b/config/karma-docs.conf.js
index 7016cb4a007..fc3dc226b19 100644
--- a/config/karma-docs.conf.js
+++ b/config/karma-docs.conf.js
@@ -18,6 +18,7 @@ module.exports = function(config) {
'node_modules/angular-messages/angular-messages.js',
'node_modules/angular-route/angular-route.js',
'node_modules/angular-mocks/angular-mocks.js',
+ 'node_modules/moment/moment.js',
'dist/angular-material.js',
'config/test-utils.js',
'dist/docs/docs.js',
diff --git a/config/karma.conf.js b/config/karma.conf.js
index 7cd94fe59cc..838e70ff163 100644
--- a/config/karma.conf.js
+++ b/config/karma.conf.js
@@ -38,6 +38,7 @@ module.exports = function(config) {
'node_modules/angular-sanitize/angular-sanitize.js',
'node_modules/angular-touch/angular-touch.js',
'node_modules/angular-mocks/angular-mocks.js',
+ 'node_modules/moment/moment.js',
'test/angular-material-mocks.js',
'test/angular-material-spec.js'
]);
diff --git a/src/components/datepicker/js/dateLocale.spec.js b/src/components/datepicker/js/dateLocale.spec.js
index 9cbb9a21c06..26c029ffa4b 100644
--- a/src/components/datepicker/js/dateLocale.spec.js
+++ b/src/components/datepicker/js/dateLocale.spec.js
@@ -1,4 +1,3 @@
-
describe('$mdDateLocale', function() {
var dateLocale, dateUtil;
@@ -81,7 +80,7 @@ describe('$mdDateLocale', function() {
describe('with custom values', function() {
var fakeMonths = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'];
- var fakeshortMonths = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'j', 'l'];
+ var fakeShortMonths = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'j', 'l'];
var fakeDays = ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7'];
var fakeShortDays = ['1', '2', '3', '4', '5', '6', '7'];
var fakeDates = [undefined, 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10', 'X11',
@@ -90,7 +89,7 @@ describe('$mdDateLocale', function() {
beforeEach(module(function($mdDateLocaleProvider) {
$mdDateLocaleProvider.months = fakeMonths;
- $mdDateLocaleProvider.shortMonths = fakeshortMonths;
+ $mdDateLocaleProvider.shortMonths = fakeShortMonths;
$mdDateLocaleProvider.days = fakeDays;
$mdDateLocaleProvider.shortDays = fakeShortDays;
$mdDateLocaleProvider.dates = fakeDates;
@@ -113,7 +112,7 @@ describe('$mdDateLocale', function() {
it('should expose custom settings', function() {
expect(dateLocale.months).toEqual(fakeMonths);
- expect(dateLocale.shortMonths).toEqual(fakeshortMonths);
+ expect(dateLocale.shortMonths).toEqual(fakeShortMonths);
expect(dateLocale.days).toEqual(fakeDays);
expect(dateLocale.shortDays).toEqual(fakeShortDays);
expect(dateLocale.dates).toEqual(fakeDates);
@@ -124,4 +123,38 @@ describe('$mdDateLocale', function() {
expect(dateLocale.isDateComplete('Anything Else')).toBe(false);
});
});
+
+ describe('with MomentJS custom formatting', function() {
+ beforeEach(module(function($mdDateLocaleProvider) {
+ $mdDateLocaleProvider.formatDate = function(date) {
+ return date ? moment(date).format('M/D') : '';
+ };
+ $mdDateLocaleProvider.parseDate = function(dateString) {
+ var m = moment(dateString, 'M/D', true);
+ return m.isValid() ? m.toDate() : new Date(NaN);
+ };
+ $mdDateLocaleProvider.isDateComplete = function(dateString) {
+ dateString = dateString.trim();
+ // Look for two chunks of content (either numbers or text) separated by delimiters.
+ var re = /^(([a-zA-Z]{3,}|[0-9]{1,4})([ .,]+|[/-]))([a-zA-Z]{3,}|[0-9]{1,4})/;
+ return re.test(dateString);
+ };
+ }));
+
+ beforeEach(inject(function($mdDateLocale, $$mdDateUtil) {
+ dateLocale = $mdDateLocale;
+ dateUtil = $$mdDateUtil;
+ }));
+
+ it('should respect custom formatting', function() {
+ var now = new Date();
+ expect(dateLocale.formatDate(new Date('2020-08-31T00:00:00-04:00'))).toEqual('8/31');
+ expect(dateLocale.parseDate('8/31')).toEqual(new Date(now.getFullYear(), 7, 31));
+ expect(dateLocale.parseDate('1/1')).toEqual(new Date(now.getFullYear(), 0, 1));
+ expect(dateLocale.isDateComplete('8/31')).toBe(true);
+ expect(dateLocale.isDateComplete('8-31')).toBe(true);
+ expect(dateLocale.isDateComplete('August_31st')).toBe(false);
+ expect(dateLocale.isDateComplete('2020')).toBe(false);
+ });
+ });
});
diff --git a/src/components/datepicker/js/dateUtil.js b/src/components/datepicker/js/dateUtil.js
index 9836bbafb2d..e0e345b184b 100644
--- a/src/components/datepicker/js/dateUtil.js
+++ b/src/components/datepicker/js/dateUtil.js
@@ -314,7 +314,12 @@
* @return {Date} date with local timezone offset removed
*/
function removeLocalTzAndReparseDate(value) {
- return $mdDateLocale.parseDate(value.getTime() + 60000 * value.getTimezoneOffset());
+ var dateValue, formattedDate;
+ // Remove the local timezone offset before calling formatDate.
+ dateValue = new Date(value.getTime() + 60000 * value.getTimezoneOffset());
+ formattedDate = $mdDateLocale.formatDate(dateValue);
+ // parseDate only works with a date formatted by formatDate when using Moment validation.
+ return $mdDateLocale.parseDate(formattedDate);
}
});
})();
diff --git a/src/components/datepicker/js/datepickerDirective.spec.js b/src/components/datepicker/js/datepickerDirective.spec.js
index 5c552a74ffb..3f3466aa9d0 100644
--- a/src/components/datepicker/js/datepickerDirective.spec.js
+++ b/src/components/datepicker/js/datepickerDirective.spec.js
@@ -1,29 +1,53 @@
+// When constructing a Date, the month is zero-based. This can be confusing, since people are
+// used to seeing them one-based. So we create these aliases to make reading the tests easier.
+var JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9,
+ NOV = 10, DEC = 11;
+
+var initialDate = new Date(2015, FEB, 15);
+
+var ngElement, element, scope, pageScope, controller;
+var $compile, $timeout, $$rAF, $animate, $window, keyCodes, dateUtil, dateLocale;
+
+var DATEPICKER_TEMPLATE =
+ '' +
+ '';
+
+/**
+ * Compile and link the given template and store values for element, scope, and controller.
+ * @param {string} template
+ * @returns {JQLite} The root compiled element.
+ */
+function createDatepickerInstance(template) {
+ var outputElement = $compile(template)(pageScope);
+ pageScope.$apply();
+
+ ngElement = outputElement[0].tagName === 'MD-DATEPICKER' ?
+ outputElement : outputElement.find('md-datepicker');
+ element = ngElement[0];
+ scope = ngElement.isolateScope();
+ controller = ngElement.controller('mdDatepicker');
+
+ return outputElement;
+}
+
+/** Populates the inputElement with a value and triggers the input events. */
+function populateInputElement(inputString) {
+ controller.ngInputElement.val(inputString).triggerHandler('input');
+ $timeout.flush();
+ pageScope.$apply();
+}
describe('md-datepicker', function() {
- // When constructing a Date, the month is zero-based. This can be confusing, since people are
- // used to seeing them one-based. So we create these aliases to make reading the tests easier.
- var JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9,
- NOV = 10, DEC = 11;
-
- var initialDate = new Date(2015, FEB, 15);
-
- var ngElement, element, scope, pageScope, controller;
- var $compile, $timeout, $$rAF, $animate, $window, keyCodes, dateUtil, dateLocale;
-
- var DATEPICKER_TEMPLATE =
- '' +
- '';
-
beforeEach(module('material.components.datepicker', 'material.components.input', 'ngAnimateMock'));
beforeEach(inject(function($rootScope, $injector) {
@@ -51,31 +75,6 @@ describe('md-datepicker', function() {
ngElement.remove();
});
- /**
- * Compile and link the given template and store values for element, scope, and controller.
- * @param {string} template
- * @returns {angular.JQLite} The root compiled element.
- */
- function createDatepickerInstance(template) {
- var outputElement = $compile(template)(pageScope);
- pageScope.$apply();
-
- ngElement = outputElement[0].tagName == 'MD-DATEPICKER' ?
- outputElement : outputElement.find('md-datepicker');
- element = ngElement[0];
- scope = ngElement.isolateScope();
- controller = ngElement.controller('mdDatepicker');
-
- return outputElement;
- }
-
- /** Populates the inputElement with a value and triggers the input events. */
- function populateInputElement(inputString) {
- controller.ngInputElement.val(inputString).triggerHandler('input');
- $timeout.flush();
- pageScope.$apply();
- }
-
it('should be the same date object as the initial ng-model', function() {
expect(pageScope.myDate).toBe(initialDate);
});
@@ -591,9 +590,9 @@ describe('md-datepicker', function() {
body.removeChild(element);
});
- it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
+ it('should shrink the calendar pane when it would otherwise not fit on the screen', function() {
// Fake the window being very narrow so that the calendar pane won't fit on-screen.
- controller.$window = {innerWidth: 200, innherHeight: 800};
+ controller.$window = {innerWidth: 200, innerHeight: 800};
// Open the calendar pane.
controller.openCalendarPane({});
@@ -893,3 +892,57 @@ describe('md-datepicker', function() {
});
});
+
+describe('md-datepicker with MomentJS custom formatting', function() {
+ beforeEach(module('material.components.datepicker', 'material.components.input', 'ngAnimateMock'));
+
+ beforeEach(module(function($mdDateLocaleProvider) {
+ $mdDateLocaleProvider.formatDate = function(date) {
+ return date ? moment(date).format('M/D') : '';
+ };
+ $mdDateLocaleProvider.parseDate = function(dateString) {
+ var m = moment(dateString, 'M/D', true);
+ return m.isValid() ? m.toDate() : new Date(NaN);
+ };
+ $mdDateLocaleProvider.isDateComplete = function(dateString) {
+ dateString = dateString.trim();
+ // Look for two chunks of content (either numbers or text) separated by delimiters.
+ var re = /^(([a-zA-Z]{3,}|[0-9]{1,4})([ .,]+|[/-]))([a-zA-Z]{3,}|[0-9]{1,4})/;
+ return re.test(dateString);
+ };
+ }));
+
+ beforeEach(inject(function($rootScope, $injector) {
+ $compile = $injector.get('$compile');
+ $timeout = $injector.get('$timeout');
+
+ pageScope = $rootScope.$new();
+ pageScope.myDate = initialDate;
+ pageScope.isDisabled = false;
+ pageScope.dateChangedHandler = jasmine.createSpy('ng-change handler');
+
+ createDatepickerInstance(DATEPICKER_TEMPLATE);
+ controller.closeCalendarPane();
+ }));
+
+ afterEach(function() {
+ controller.isAttached && controller.closeCalendarPane();
+ pageScope.$destroy();
+ ngElement.remove();
+ });
+
+ it('should update the model value and close the calendar pane', function() {
+ var date = new Date(2020, SEP, 1);
+ controller.openCalendarPane({
+ target: controller.inputElement
+ });
+ scope.$emit('md-calendar-change', date);
+ scope.$apply();
+ expect(pageScope.myDate).toEqual(date);
+ expect(controller.ngModelCtrl.$modelValue).toEqual(date);
+
+ expect(controller.inputElement.value).toEqual('9/1');
+ expect(controller.calendarPaneOpenedFrom).toBe(null);
+ expect(controller.isCalendarOpen).toBe(false);
+ });
+});