From e0f87cb0cdd3748341b1e4cdbdac6cbd1427ebb1 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 14 Sep 2017 12:51:02 -0700 Subject: [PATCH 1/6] add fromISODateString to DateAdapter --- src/lib/core/datetime/date-adapter.ts | 11 ++++++++-- src/lib/core/datetime/native-date-adapter.ts | 21 ++++++++++++++++++- src/lib/datepicker/datepicker-input.ts | 4 ++-- .../adapter/moment-date-adapter.spec.ts | 2 +- .../adapter/moment-date-adapter.ts | 7 ++++++- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/lib/core/datetime/date-adapter.ts b/src/lib/core/datetime/date-adapter.ts index 86369f8c85bf..aca8b46e9995 100644 --- a/src/lib/core/datetime/date-adapter.ts +++ b/src/lib/core/datetime/date-adapter.ts @@ -164,12 +164,19 @@ export abstract class DateAdapter { abstract addCalendarDays(date: D, days: number): D; /** - * Gets the RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339) for the given + * Gets the RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339) for the given * date. * @param date The date to get the ISO date string for. * @returns The ISO date string date string. */ - abstract getISODateString(date: D): string; + abstract toISODateString(date: D): string; + + /** + * Creates a date from an RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339). + * @param iso8601String The ISO date string to create a date from + * @returns The date created from the ISO date string. + */ + abstract fromISODateString(iso8601String: string): D | null; /** * Checks whether the given object is considered a date instance by this DateAdapter. diff --git a/src/lib/core/datetime/native-date-adapter.ts b/src/lib/core/datetime/native-date-adapter.ts index 6eb459c17206..1ed1b384150b 100644 --- a/src/lib/core/datetime/native-date-adapter.ts +++ b/src/lib/core/datetime/native-date-adapter.ts @@ -38,6 +38,15 @@ const DEFAULT_DAY_OF_WEEK_NAMES = { }; +/** + * Matches strings that have the form of a valid RFC 3339 string + * (https://tools.ietf.org/html/rfc3339). Note that the string may not actually be a valid date + * because the regex will match strings an with out of bounds month, date, etc. + */ +const ISO_8601_REGEX = + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))$/; + + /** Creates an array and fills it with values. */ function range(length: number, valueFunction: (index: number) => T): T[] { const valuesArray = Array(length); @@ -202,7 +211,7 @@ export class NativeDateAdapter extends DateAdapter { this.getYear(date), this.getMonth(date), this.getDate(date) + days); } - getISODateString(date: Date): string { + toISODateString(date: Date): string { return [ date.getUTCFullYear(), this._2digit(date.getUTCMonth() + 1), @@ -210,6 +219,16 @@ export class NativeDateAdapter extends DateAdapter { ].join('-'); } + fromISODateString(iso8601String: string): Date | null { + if (iso8601String.match(ISO_8601_REGEX)) { + let d = new Date(iso8601String); + if (this.isValid(d)) { + return d; + } + } + return null; + } + isDateInstance(obj: any) { return obj instanceof Date; } diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index ee97403108b7..d1a3e2fdbc59 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -74,8 +74,8 @@ export class MdDatepickerInputEvent { host: { '[attr.aria-haspopup]': 'true', '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null', - '[attr.min]': 'min ? _dateAdapter.getISODateString(min) : null', - '[attr.max]': 'max ? _dateAdapter.getISODateString(max) : null', + '[attr.min]': 'min ? _dateAdapter.toISODateString(min) : null', + '[attr.max]': 'max ? _dateAdapter.toISODateString(max) : null', '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(change)': '_onChange()', diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts index c8330e8f1eaf..885bcba169c2 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -342,7 +342,7 @@ describe('MomentDateAdapter', () => { adapter.addCalendarDays(date, 1); adapter.addCalendarMonths(date, 1); adapter.addCalendarYears(date, 1); - adapter.getISODateString(date); + adapter.toISODateString(date); adapter.isDateInstance(date); adapter.isValid(date); expect(date.locale()).toBe('en'); diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index a941eec54354..58bb25c028ca 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -170,10 +170,15 @@ export class MomentDateAdapter extends DateAdapter { return this.clone(date).add({days}); } - getISODateString(date: Moment): string { + toISODateString(date: Moment): string { return this.clone(date).format(); } + fromISODateString(iso8601String: string): Moment | null { + let d = moment(iso8601String, moment.ISO_8601).locale(this.locale); + return this.isValid(d) ? d : null; + } + isDateInstance(obj: any): boolean { return moment.isMoment(obj); } From 48e2d8b7a46327e601b69ddbb1f232c3385dd03a Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 14 Sep 2017 14:38:38 -0700 Subject: [PATCH 2/6] coerce ISO 8601 strings to dates --- src/lib/datepicker/calendar.ts | 32 ++++++++++++++++++++++---- src/lib/datepicker/datepicker-input.ts | 17 ++++++++++++-- src/lib/datepicker/datepicker.ts | 14 ++++++++++- src/lib/datepicker/month-view.ts | 16 +++++++++++-- src/lib/datepicker/year-view.ts | 16 +++++++++++-- 5 files changed, 84 insertions(+), 11 deletions(-) diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index fd4cb3f5fa1f..1311ced61a40 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -63,19 +63,31 @@ export class MdCalendar implements AfterContentInit, OnDestroy { private _intlChanges: Subscription; /** A date representing the period (month or year) to start the calendar in. */ - @Input() startAt: D; + @Input() + get startAt(): D { return this._startAt; } + set startAt(value: D) { this._startAt = this._coerceDateProperty(value); } + private _startAt: D; /** Whether the calendar should be started in month or year view. */ @Input() startView: 'month' | 'year' = 'month'; /** The currently selected date. */ - @Input() selected: D | null; + @Input() + get selected(): D | null { return this._selected; } + set selected(value: D | null) { this._selected = this._coerceDateProperty(value); } + private _selected: D | null; /** The minimum selectable date. */ - @Input() minDate: D | null; + @Input() + get minDate(): D | null { return this._minDate; } + set minDate(value: D | null) { this._minDate = this._coerceDateProperty(value); } + private _minDate: D | null; /** The maximum selectable date. */ - @Input() maxDate: D | null; + @Input() + get maxDate(): D | null { return this._maxDate; } + set maxDate(value: D | null) { this._maxDate = this._coerceDateProperty(value); } + private _maxDate: D | null; /** A function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; @@ -354,4 +366,16 @@ export class MdCalendar implements AfterContentInit, OnDestroy { (this._dateAdapter.getMonth(date) >= 7 ? 5 : 12); return this._dateAdapter.addCalendarMonths(date, increment); } + + /** + * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid + * ISO 8601 string, returns the original vlaue. + */ + private _coerceDateProperty(value: any): any { + if (typeof value === 'string') { + const d = this._dateAdapter.fromISODateString(value); + return d || value; + } + return value; + } } diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index d1a3e2fdbc59..4dc800e01544 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -122,6 +122,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces return this._value; } set value(value: D | null) { + value = this._coerceDateProperty(value); if (value != null && !this._dateAdapter.isDateInstance(value)) { throw Error('Datepicker: value not recognized as a date object by DateAdapter.'); } @@ -142,7 +143,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces @Input() get min(): D | null { return this._min; } set min(value: D | null) { - this._min = value; + this._min = this._coerceDateProperty(value); this._validatorOnChange(); } private _min: D | null; @@ -151,7 +152,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces @Input() get max(): D | null { return this._max; } set max(value: D | null) { - this._max = value; + this._max = this._coerceDateProperty(value); this._validatorOnChange(); } private _max: D | null; @@ -328,4 +329,16 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces private _getValidDateOrNull(obj: any): D | null { return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; } + + /** + * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid + * ISO 8601 string, returns the original vlaue. + */ + private _coerceDateProperty(value: any): any { + if (typeof value === 'string') { + const d = this._dateAdapter.fromISODateString(value); + return d || value; + } + return value; + } } diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index df5c6190499e..e9458568beaf 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -129,7 +129,7 @@ export class MdDatepicker implements OnDestroy { // selected value is. return this._startAt || (this._datepickerInput ? this._datepickerInput.value : null); } - set startAt(date: D | null) { this._startAt = date; } + set startAt(date: D | null) { this._startAt = this._coerceDateProperty(date); } private _startAt: D | null; /** The view that the calendar should start in. */ @@ -360,4 +360,16 @@ export class MdDatepicker implements OnDestroy { { overlayX: 'end', overlayY: 'bottom' } ); } + + /** + * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid + * ISO 8601 string, returns the original vlaue. + */ + private _coerceDateProperty(value: any): any { + if (typeof value === 'string') { + const d = this._dateAdapter.fromISODateString(value); + return d || value; + } + return value; + } } diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 379d2edc1c18..0332ebbfa7d9 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -44,7 +44,7 @@ export class MdMonthView implements AfterContentInit { get activeDate(): D { return this._activeDate; } set activeDate(value: D) { let oldActiveDate = this._activeDate; - this._activeDate = value || this._dateAdapter.today(); + this._activeDate = this._coerceDateProperty(value) || this._dateAdapter.today(); if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) { this._init(); } @@ -55,7 +55,7 @@ export class MdMonthView implements AfterContentInit { @Input() get selected(): D { return this._selected; } set selected(value: D) { - this._selected = value; + this._selected = this._coerceDateProperty(value); this._selectedDate = this._getDateInCurrentMonth(this.selected); } private _selected: D; @@ -181,4 +181,16 @@ export class MdMonthView implements AfterContentInit { return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) && this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2)); } + + /** + * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid + * ISO 8601 string, returns the original vlaue. + */ + private _coerceDateProperty(value: any): any { + if (typeof value === 'string') { + const d = this._dateAdapter.fromISODateString(value); + return d || value; + } + return value; + } } diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index 81560fbf93ee..bde13aeb9378 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -39,7 +39,7 @@ export class MdYearView implements AfterContentInit { get activeDate(): D { return this._activeDate; } set activeDate(value: D) { let oldActiveDate = this._activeDate; - this._activeDate = value || this._dateAdapter.today(); + this._activeDate = this._coerceDateProperty(value) || this._dateAdapter.today(); if (this._dateAdapter.getYear(oldActiveDate) != this._dateAdapter.getYear(this._activeDate)) { this._init(); } @@ -50,7 +50,7 @@ export class MdYearView implements AfterContentInit { @Input() get selected(): D { return this._selected; } set selected(value: D) { - this._selected = value; + this._selected = this._coerceDateProperty(value); this._selectedMonth = this._getMonthInCurrentYear(this.selected); } private _selected: D; @@ -150,4 +150,16 @@ export class MdYearView implements AfterContentInit { return false; } + + /** + * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid + * ISO 8601 string, returns the original vlaue. + */ + private _coerceDateProperty(value: any): any { + if (typeof value === 'string') { + const d = this._dateAdapter.fromISODateString(value); + return d || value; + } + return value; + } } From 45286d9f9abf47b05a45eadd8fe23731969381cd Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 14 Sep 2017 15:30:51 -0700 Subject: [PATCH 3/6] add tests --- .../core/datetime/native-date-adapter.spec.ts | 8 ++++ src/lib/datepicker/datepicker-input.ts | 13 +++--- src/lib/datepicker/datepicker.spec.ts | 41 +++++++++++++++++++ .../adapter/moment-date-adapter.spec.ts | 8 ++++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/lib/core/datetime/native-date-adapter.spec.ts b/src/lib/core/datetime/native-date-adapter.spec.ts index cc03629e724e..e84523f99bfb 100644 --- a/src/lib/core/datetime/native-date-adapter.spec.ts +++ b/src/lib/core/datetime/native-date-adapter.spec.ts @@ -331,6 +331,14 @@ describe('NativeDateAdapter', () => { let d = '1/1/2017'; expect(adapter.isDateInstance(d)).toBe(false); }); + + it('should create dates from valid ISO strings', () => { + expect(adapter.fromISODateString('1985-04-12T23:20:50.52Z')).not.toBeNull(); + expect(adapter.fromISODateString('1996-12-19T16:39:57-08:00')).not.toBeNull(); + expect(adapter.fromISODateString('1937-01-01T12:00:27.87+00:20')).not.toBeNull(); + expect(adapter.fromISODateString('1990-13-31T23:59:00Z')).toBeNull(); + expect(adapter.fromISODateString('1/1/2017')).toBeNull(); + }); }); diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index 4dc800e01544..ece5684c2883 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -200,21 +200,24 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces /** The form control validator for the min date. */ private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - return (!this.min || !control.value || - this._dateAdapter.compareDate(this.min, control.value) <= 0) ? + const controlValue = this._coerceDateProperty(control.value); + return (!this.min || !controlValue || + this._dateAdapter.compareDate(this.min, controlValue) <= 0) ? null : {'mdDatepickerMin': {'min': this.min, 'actual': control.value}}; } /** The form control validator for the max date. */ private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - return (!this.max || !control.value || - this._dateAdapter.compareDate(this.max, control.value) >= 0) ? + const controlValue = this._coerceDateProperty(control.value); + return (!this.max || !controlValue || + this._dateAdapter.compareDate(this.max, controlValue) >= 0) ? null : {'mdDatepickerMax': {'max': this.max, 'actual': control.value}}; } /** The form control validator for the date filter. */ private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - return !this._dateFilter || !control.value || this._dateFilter(control.value) ? + const controlValue = this._coerceDateProperty(control.value); + return !this._dateFilter || !controlValue || this._dateFilter(controlValue) ? null : {'mdDatepickerFilter': true}; } diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index c488d6492710..942066222a75 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -865,6 +865,32 @@ describe('MdDatepicker', () => { expect(testComponent.onDateInput).toHaveBeenCalled(); }); }); + + describe('with ISO 8601 strings as input', () => { + let fixture: ComponentFixture; + let testComponent: DatepickerWithISOStrings; + + beforeEach(async(() => { + fixture = TestBed.createComponent(DatepickerWithISOStrings); + testComponent = fixture.componentInstance; + })); + + afterEach(async(() => { + testComponent.datepicker.close(); + fixture.detectChanges(); + })); + + it('should coerce ISO strings', async(() => { + expect(() => fixture.detectChanges()).not.toThrow(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(testComponent.datepicker.startAt).toEqual(new Date(2017, JUL, 1)); + expect(testComponent.datepickerInput.value).toEqual(new Date(2017, JUN, 1)); + expect(testComponent.datepickerInput.min).toEqual(new Date(2017, JAN, 1)); + expect(testComponent.datepickerInput.max).toEqual(new Date(2017, DEC, 31)); + }); + })); + }); }); describe('with missing DateAdapter and MD_DATE_FORMATS', () => { @@ -1179,3 +1205,18 @@ class DatepickerWithi18n { @ViewChild('d') datepicker: MdDatepicker; @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; } + +@Component({ + template: ` + + + ` +}) +class DatepickerWithISOStrings { + value = new Date(2017, JUN, 1).toISOString(); + min = new Date(2017, JAN, 1).toISOString(); + max = new Date (2017, DEC, 31).toISOString(); + startAt = new Date(2017, JUL, 1).toISOString(); + @ViewChild('d') datepicker: MdDatepicker; + @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; +} diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts index 885bcba169c2..2d3478d2f91a 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -309,6 +309,14 @@ describe('MomentDateAdapter', () => { expect(adapter.isDateInstance(d)).toBe(false); }); + it('should create dates from valid ISO strings', () => { + expect(adapter.fromISODateString('1985-04-12T23:20:50.52Z')).not.toBeNull(); + expect(adapter.fromISODateString('1996-12-19T16:39:57-08:00')).not.toBeNull(); + expect(adapter.fromISODateString('1937-01-01T12:00:27.87+00:20')).not.toBeNull(); + expect(adapter.fromISODateString('1990-13-31T23:59:00Z')).toBeNull(); + expect(adapter.fromISODateString('1/1/2017')).toBeNull(); + }); + it('setLocale should not modify global moment locale', () => { expect(moment.locale()).toBe('en'); adapter.setLocale('ja-JP'); From 9fd57453b0618cb4384f29224dc07f3660590719 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 14 Sep 2017 15:55:00 -0700 Subject: [PATCH 4/6] fix imports after rebase --- src/lib/datepicker/datepicker.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index 942066222a75..c761133781e6 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -12,6 +12,8 @@ import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import { DEC, JAN, + JUL, + JUN, MAT_DATE_LOCALE, MdNativeDateModule, NativeDateModule, From 03fbdc459c66526f6c1858ba83b91f0d8372fa25 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 15 Sep 2017 10:32:11 -0700 Subject: [PATCH 5/6] extract coerceDateProperty to separate file & address comments --- src/lib/core/datetime/date-adapter.ts | 9 ++-- .../core/datetime/native-date-adapter.spec.ts | 10 ++-- src/lib/core/datetime/native-date-adapter.ts | 8 +-- src/lib/datepicker/calendar.ts | 25 +++------ .../datepicker/coerce-date-property.spec.ts | 54 +++++++++++++++++++ src/lib/datepicker/coerce-date-property.ts | 35 ++++++++++++ src/lib/datepicker/datepicker-input.ts | 36 ++++--------- src/lib/datepicker/datepicker.spec.ts | 5 +- src/lib/datepicker/datepicker.ts | 15 +----- src/lib/datepicker/month-view.ts | 31 ++++------- src/lib/datepicker/public_api.ts | 1 + src/lib/datepicker/year-view.ts | 27 +++------- .../adapter/moment-date-adapter.spec.ts | 12 ++--- .../adapter/moment-date-adapter.ts | 4 +- 14 files changed, 153 insertions(+), 119 deletions(-) create mode 100644 src/lib/datepicker/coerce-date-property.spec.ts create mode 100644 src/lib/datepicker/coerce-date-property.ts diff --git a/src/lib/core/datetime/date-adapter.ts b/src/lib/core/datetime/date-adapter.ts index aca8b46e9995..ec645c214882 100644 --- a/src/lib/core/datetime/date-adapter.ts +++ b/src/lib/core/datetime/date-adapter.ts @@ -164,19 +164,18 @@ export abstract class DateAdapter { abstract addCalendarDays(date: D, days: number): D; /** - * Gets the RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339) for the given - * date. + * Gets the RFC 3339 compatible string (https://tools.ietf.org/html/rfc3339) for the given date. * @param date The date to get the ISO date string for. * @returns The ISO date string date string. */ - abstract toISODateString(date: D): string; + abstract toIso8601(date: D): string; /** - * Creates a date from an RFC 3339 compatible date string (https://tools.ietf.org/html/rfc3339). + * Creates a date from an RFC 3339 compatible string (https://tools.ietf.org/html/rfc3339). * @param iso8601String The ISO date string to create a date from * @returns The date created from the ISO date string. */ - abstract fromISODateString(iso8601String: string): D | null; + abstract fromIso8601(iso8601String: string): D | null; /** * Checks whether the given object is considered a date instance by this DateAdapter. diff --git a/src/lib/core/datetime/native-date-adapter.spec.ts b/src/lib/core/datetime/native-date-adapter.spec.ts index e84523f99bfb..7ed61ee3753b 100644 --- a/src/lib/core/datetime/native-date-adapter.spec.ts +++ b/src/lib/core/datetime/native-date-adapter.spec.ts @@ -333,11 +333,11 @@ describe('NativeDateAdapter', () => { }); it('should create dates from valid ISO strings', () => { - expect(adapter.fromISODateString('1985-04-12T23:20:50.52Z')).not.toBeNull(); - expect(adapter.fromISODateString('1996-12-19T16:39:57-08:00')).not.toBeNull(); - expect(adapter.fromISODateString('1937-01-01T12:00:27.87+00:20')).not.toBeNull(); - expect(adapter.fromISODateString('1990-13-31T23:59:00Z')).toBeNull(); - expect(adapter.fromISODateString('1/1/2017')).toBeNull(); + expect(adapter.fromIso8601('1985-04-12T23:20:50.52Z')).not.toBeNull(); + expect(adapter.fromIso8601('1996-12-19T16:39:57-08:00')).not.toBeNull(); + expect(adapter.fromIso8601('1937-01-01T12:00:27.87+00:20')).not.toBeNull(); + expect(adapter.fromIso8601('1990-13-31T23:59:00Z')).toBeNull(); + expect(adapter.fromIso8601('1/1/2017')).toBeNull(); }); }); diff --git a/src/lib/core/datetime/native-date-adapter.ts b/src/lib/core/datetime/native-date-adapter.ts index 1ed1b384150b..af5fc07637bc 100644 --- a/src/lib/core/datetime/native-date-adapter.ts +++ b/src/lib/core/datetime/native-date-adapter.ts @@ -211,7 +211,7 @@ export class NativeDateAdapter extends DateAdapter { this.getYear(date), this.getMonth(date), this.getDate(date) + days); } - toISODateString(date: Date): string { + toIso8601(date: Date): string { return [ date.getUTCFullYear(), this._2digit(date.getUTCMonth() + 1), @@ -219,8 +219,10 @@ export class NativeDateAdapter extends DateAdapter { ].join('-'); } - fromISODateString(iso8601String: string): Date | null { - if (iso8601String.match(ISO_8601_REGEX)) { + fromIso8601(iso8601String: string): Date | null { + // The `Date` constructor accepts formats other than ISO 8601, so we need to make sure the + // string is the right format first. + if (ISO_8601_REGEX.test(iso8601String)) { let d = new Date(iso8601String); if (this.isValid(d)) { return d; diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index 1311ced61a40..75a4a3531a70 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -40,6 +40,7 @@ import { } from '@angular/material/core'; import {first} from 'rxjs/operator/first'; import {Subscription} from 'rxjs/Subscription'; +import {coerceDateProperty} from './coerce-date-property'; import {createMissingDateImplError} from './datepicker-errors'; import {MdDatepickerIntl} from './datepicker-intl'; @@ -64,9 +65,9 @@ export class MdCalendar implements AfterContentInit, OnDestroy { /** A date representing the period (month or year) to start the calendar in. */ @Input() - get startAt(): D { return this._startAt; } - set startAt(value: D) { this._startAt = this._coerceDateProperty(value); } - private _startAt: D; + get startAt(): D | null { return this._startAt; } + set startAt(value: D | null) { this._startAt = coerceDateProperty(this._dateAdapter, value); } + private _startAt: D | null; /** Whether the calendar should be started in month or year view. */ @Input() startView: 'month' | 'year' = 'month'; @@ -74,19 +75,19 @@ export class MdCalendar implements AfterContentInit, OnDestroy { /** The currently selected date. */ @Input() get selected(): D | null { return this._selected; } - set selected(value: D | null) { this._selected = this._coerceDateProperty(value); } + set selected(value: D | null) { this._selected = coerceDateProperty(this._dateAdapter, value); } private _selected: D | null; /** The minimum selectable date. */ @Input() get minDate(): D | null { return this._minDate; } - set minDate(value: D | null) { this._minDate = this._coerceDateProperty(value); } + set minDate(value: D | null) { this._minDate = coerceDateProperty(this._dateAdapter, value); } private _minDate: D | null; /** The maximum selectable date. */ @Input() get maxDate(): D | null { return this._maxDate; } - set maxDate(value: D | null) { this._maxDate = this._coerceDateProperty(value); } + set maxDate(value: D | null) { this._maxDate = coerceDateProperty(this._dateAdapter, value); } private _maxDate: D | null; /** A function used to filter which dates are selectable. */ @@ -366,16 +367,4 @@ export class MdCalendar implements AfterContentInit, OnDestroy { (this._dateAdapter.getMonth(date) >= 7 ? 5 : 12); return this._dateAdapter.addCalendarMonths(date, increment); } - - /** - * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid - * ISO 8601 string, returns the original vlaue. - */ - private _coerceDateProperty(value: any): any { - if (typeof value === 'string') { - const d = this._dateAdapter.fromISODateString(value); - return d || value; - } - return value; - } } diff --git a/src/lib/datepicker/coerce-date-property.spec.ts b/src/lib/datepicker/coerce-date-property.spec.ts new file mode 100644 index 000000000000..52c15b36655e --- /dev/null +++ b/src/lib/datepicker/coerce-date-property.spec.ts @@ -0,0 +1,54 @@ +import {async, inject, TestBed} from '@angular/core/testing'; +import {DateAdapter, JAN, MdNativeDateModule} from '@angular/material/core'; +import {coerceDateProperty} from './index'; + + +describe('coerceDateProperty', () => { + let adapter: DateAdapter; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdNativeDateModule], + }); + + TestBed.compileComponents(); + })); + + beforeEach(inject([DateAdapter], (dateAdapter: DateAdapter) => { + adapter = dateAdapter; + })); + + it('should pass through existing date', () => { + const d = new Date(2017, JAN, 1); + expect(coerceDateProperty(adapter, d)).toBe(d); + }); + + it('should pass through invalid date', () => { + const d = new Date(NaN); + expect(coerceDateProperty(adapter, d)).toBe(d); + }); + + it('should pass through null and undefined', () => { + expect(coerceDateProperty(adapter, null)).toBeNull(); + expect(coerceDateProperty(adapter, undefined)).toBeUndefined(); + }); + + it('should coerce empty string to null', () => { + expect(coerceDateProperty(adapter, '')).toBe(null); + }); + + it('should coerce ISO 8601 string to date', () => { + let isoString = '2017-01-01T00:00:00Z'; + expect(coerceDateProperty(adapter, isoString)).toEqual(new Date(isoString)); + }); + + it('should throw when given a number', () => { + expect(() => coerceDateProperty(adapter, 5)).toThrow(); + expect(() => coerceDateProperty(adapter, 0)).toThrow(); + }); + + it('should throw when given a string with incorrect format', () => { + expect(() => coerceDateProperty(adapter, '1/1/2017')).toThrow(); + expect(() => coerceDateProperty(adapter, 'hello')).toThrow(); + }); +}); diff --git a/src/lib/datepicker/coerce-date-property.ts b/src/lib/datepicker/coerce-date-property.ts new file mode 100644 index 000000000000..08fb8ffbb9a6 --- /dev/null +++ b/src/lib/datepicker/coerce-date-property.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {DateAdapter} from '@angular/material/core'; + + +/** + * Function that attempts to coerce a value to a date using a DateAdapter. Date instances, null, + * and undefined will be passed through. Empty strings will be coerced to null. Valid ISO 8601 + * strings (https://www.ietf.org/rfc/rfc3339.txt) will be coerced to dates. All other values will + * result in an error being thrown. + * @param adapter The date adapter to use for coercion + * @param value The value to coerce. + * @return A date object coerced from the value. + * @throws Throws when the value cannot be coerced. + */ +export function coerceDateProperty(adapter: DateAdapter, value: any): D | null { + if (typeof value === 'string') { + if (value == '') { + value = null; + } else { + value = adapter.fromIso8601(value) || value; + } + } + if (value == null || adapter.isDateInstance(value)) { + return value; + } + throw Error(`Datepicker: Value must be either a date object recognized by the DateAdapter or ` + + `an ISO 8601 string. Instead got: ${value}`); +} diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index ece5684c2883..6116ad87c219 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -34,6 +34,7 @@ import { import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core'; import {MdFormField} from '@angular/material/form-field'; import {Subscription} from 'rxjs/Subscription'; +import {coerceDateProperty} from './coerce-date-property'; import {MdDatepicker} from './datepicker'; import {createMissingDateImplError} from './datepicker-errors'; @@ -74,8 +75,8 @@ export class MdDatepickerInputEvent { host: { '[attr.aria-haspopup]': 'true', '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null', - '[attr.min]': 'min ? _dateAdapter.toISODateString(min) : null', - '[attr.max]': 'max ? _dateAdapter.toISODateString(max) : null', + '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null', + '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null', '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(change)': '_onChange()', @@ -122,10 +123,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces return this._value; } set value(value: D | null) { - value = this._coerceDateProperty(value); - if (value != null && !this._dateAdapter.isDateInstance(value)) { - throw Error('Datepicker: value not recognized as a date object by DateAdapter.'); - } + value = coerceDateProperty(this._dateAdapter, value); this._lastValueValid = !value || this._dateAdapter.isValid(value); value = this._getValidDateOrNull(value); @@ -143,7 +141,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces @Input() get min(): D | null { return this._min; } set min(value: D | null) { - this._min = this._coerceDateProperty(value); + this._min = coerceDateProperty(this._dateAdapter, value); this._validatorOnChange(); } private _min: D | null; @@ -152,7 +150,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces @Input() get max(): D | null { return this._max; } set max(value: D | null) { - this._max = this._coerceDateProperty(value); + this._max = coerceDateProperty(this._dateAdapter, value); this._validatorOnChange(); } private _max: D | null; @@ -200,23 +198,23 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces /** The form control validator for the min date. */ private _minValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const controlValue = this._coerceDateProperty(control.value); + const controlValue = coerceDateProperty(this._dateAdapter, control.value); return (!this.min || !controlValue || this._dateAdapter.compareDate(this.min, controlValue) <= 0) ? - null : {'mdDatepickerMin': {'min': this.min, 'actual': control.value}}; + null : {'mdDatepickerMin': {'min': this.min, 'actual': controlValue}}; } /** The form control validator for the max date. */ private _maxValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const controlValue = this._coerceDateProperty(control.value); + const controlValue = coerceDateProperty(this._dateAdapter, control.value); return (!this.max || !controlValue || this._dateAdapter.compareDate(this.max, controlValue) >= 0) ? - null : {'mdDatepickerMax': {'max': this.max, 'actual': control.value}}; + null : {'mdDatepickerMax': {'max': this.max, 'actual': controlValue}}; } /** The form control validator for the date filter. */ private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const controlValue = this._coerceDateProperty(control.value); + const controlValue = coerceDateProperty(this._dateAdapter, control.value); return !this._dateFilter || !controlValue || this._dateFilter(controlValue) ? null : {'mdDatepickerFilter': true}; } @@ -332,16 +330,4 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces private _getValidDateOrNull(obj: any): D | null { return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; } - - /** - * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid - * ISO 8601 string, returns the original vlaue. - */ - private _coerceDateProperty(value: any): any { - if (typeof value === 'string') { - const d = this._dateAdapter.fromISODateString(value); - return d || value; - } - return value; - } } diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index c761133781e6..c16b452a1f44 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -49,6 +49,7 @@ describe('MdDatepicker', () => { DatepickerWithChangeAndInputEvents, DatepickerWithFilterAndValidation, DatepickerWithFormControl, + DatepickerWithISOStrings, DatepickerWithMinAndMaxValidation, DatepickerWithNgModel, DatepickerWithStartAt, @@ -272,8 +273,8 @@ describe('MdDatepicker', () => { it('should throw when given wrong data type', () => { testComponent.date = '1/1/2017' as any; - expect(() => fixture.detectChanges()) - .toThrowError(/Datepicker: value not recognized as a date object by DateAdapter\./); + expect(() => fixture.detectChanges()).toThrowError( + /Datepicker: Value must be either a date object recognized by the DateAdapter or an ISO 8601 string\. Instead got: 1\/1\/2017/); testComponent.date = null; }); diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index e9458568beaf..72f8358e52f8 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -42,6 +42,7 @@ import {DOCUMENT} from '@angular/platform-browser'; import {Subject} from 'rxjs/Subject'; import {Subscription} from 'rxjs/Subscription'; import {MdCalendar} from './calendar'; +import {coerceDateProperty} from './coerce-date-property'; import {createMissingDateImplError} from './datepicker-errors'; import {MdDatepickerInput} from './datepicker-input'; @@ -129,7 +130,7 @@ export class MdDatepicker implements OnDestroy { // selected value is. return this._startAt || (this._datepickerInput ? this._datepickerInput.value : null); } - set startAt(date: D | null) { this._startAt = this._coerceDateProperty(date); } + set startAt(date: D | null) { this._startAt = coerceDateProperty(this._dateAdapter, date); } private _startAt: D | null; /** The view that the calendar should start in. */ @@ -360,16 +361,4 @@ export class MdDatepicker implements OnDestroy { { overlayX: 'end', overlayY: 'bottom' } ); } - - /** - * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid - * ISO 8601 string, returns the original vlaue. - */ - private _coerceDateProperty(value: any): any { - if (typeof value === 'string') { - const d = this._dateAdapter.fromISODateString(value); - return d || value; - } - return value; - } } diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 0332ebbfa7d9..11d3e27236df 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -19,6 +19,7 @@ import { } from '@angular/core'; import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core'; import {MdCalendarCell} from './calendar-body'; +import {coerceDateProperty} from './coerce-date-property'; import {createMissingDateImplError} from './datepicker-errors'; @@ -44,7 +45,7 @@ export class MdMonthView implements AfterContentInit { get activeDate(): D { return this._activeDate; } set activeDate(value: D) { let oldActiveDate = this._activeDate; - this._activeDate = this._coerceDateProperty(value) || this._dateAdapter.today(); + this._activeDate = coerceDateProperty(this._dateAdapter, value) || this._dateAdapter.today(); if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) { this._init(); } @@ -53,12 +54,12 @@ export class MdMonthView implements AfterContentInit { /** The currently selected date. */ @Input() - get selected(): D { return this._selected; } - set selected(value: D) { - this._selected = this._coerceDateProperty(value); - this._selectedDate = this._getDateInCurrentMonth(this.selected); + get selected(): D | null { return this._selected; } + set selected(value: D | null) { + this._selected = coerceDateProperty(this._dateAdapter, value); + this._selectedDate = this._getDateInCurrentMonth(this._selected); } - private _selected: D; + private _selected: D | null; /** A function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; @@ -171,26 +172,14 @@ export class MdMonthView implements AfterContentInit { * Gets the date in this month that the given Date falls on. * Returns null if the given Date is in another month. */ - private _getDateInCurrentMonth(date: D): number | null { - return this._hasSameMonthAndYear(date, this.activeDate) ? + private _getDateInCurrentMonth(date: D | null): number | null { + return date && this._hasSameMonthAndYear(date, this.activeDate) ? this._dateAdapter.getDate(date) : null; } /** Checks whether the 2 dates are non-null and fall within the same month of the same year. */ - private _hasSameMonthAndYear(d1: D, d2: D): boolean { + private _hasSameMonthAndYear(d1: D | null, d2: D | null): boolean { return !!(d1 && d2 && this._dateAdapter.getMonth(d1) == this._dateAdapter.getMonth(d2) && this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2)); } - - /** - * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid - * ISO 8601 string, returns the original vlaue. - */ - private _coerceDateProperty(value: any): any { - if (typeof value === 'string') { - const d = this._dateAdapter.fromISODateString(value); - return d || value; - } - return value; - } } diff --git a/src/lib/datepicker/public_api.ts b/src/lib/datepicker/public_api.ts index 176f930bba88..c2d4b7d44456 100644 --- a/src/lib/datepicker/public_api.ts +++ b/src/lib/datepicker/public_api.ts @@ -29,6 +29,7 @@ import {MdYearView} from './year-view'; export * from './calendar'; export * from './calendar-body'; +export * from './coerce-date-property'; export * from './datepicker'; export * from './datepicker-input'; export * from './datepicker-intl'; diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index bde13aeb9378..dab337978842 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -19,6 +19,7 @@ import { } from '@angular/core'; import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core'; import {MdCalendarCell} from './calendar-body'; +import {coerceDateProperty} from './coerce-date-property'; import {createMissingDateImplError} from './datepicker-errors'; @@ -39,7 +40,7 @@ export class MdYearView implements AfterContentInit { get activeDate(): D { return this._activeDate; } set activeDate(value: D) { let oldActiveDate = this._activeDate; - this._activeDate = this._coerceDateProperty(value) || this._dateAdapter.today(); + this._activeDate = coerceDateProperty(this._dateAdapter, value) || this._dateAdapter.today(); if (this._dateAdapter.getYear(oldActiveDate) != this._dateAdapter.getYear(this._activeDate)) { this._init(); } @@ -48,12 +49,12 @@ export class MdYearView implements AfterContentInit { /** The currently selected date. */ @Input() - get selected(): D { return this._selected; } - set selected(value: D) { - this._selected = this._coerceDateProperty(value); - this._selectedMonth = this._getMonthInCurrentYear(this.selected); + get selected(): D | null { return this._selected; } + set selected(value: D | null) { + this._selected = coerceDateProperty(this._dateAdapter, value); + this._selectedMonth = this._getMonthInCurrentYear(this._selected); } - private _selected: D; + private _selected: D | null; /** A function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; @@ -117,7 +118,7 @@ export class MdYearView implements AfterContentInit { * Gets the month in this year that the given Date falls on. * Returns null if the given Date is in another year. */ - private _getMonthInCurrentYear(date: D) { + private _getMonthInCurrentYear(date: D | null) { return date && this._dateAdapter.getYear(date) == this._dateAdapter.getYear(this.activeDate) ? this._dateAdapter.getMonth(date) : null; } @@ -150,16 +151,4 @@ export class MdYearView implements AfterContentInit { return false; } - - /** - * Attempts to coerce a property to a date by parsing it as a ISO 8601 string. If not a valid - * ISO 8601 string, returns the original vlaue. - */ - private _coerceDateProperty(value: any): any { - if (typeof value === 'string') { - const d = this._dateAdapter.fromISODateString(value); - return d || value; - } - return value; - } } diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts index 2d3478d2f91a..032b5c4c47bf 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.spec.ts @@ -310,11 +310,11 @@ describe('MomentDateAdapter', () => { }); it('should create dates from valid ISO strings', () => { - expect(adapter.fromISODateString('1985-04-12T23:20:50.52Z')).not.toBeNull(); - expect(adapter.fromISODateString('1996-12-19T16:39:57-08:00')).not.toBeNull(); - expect(adapter.fromISODateString('1937-01-01T12:00:27.87+00:20')).not.toBeNull(); - expect(adapter.fromISODateString('1990-13-31T23:59:00Z')).toBeNull(); - expect(adapter.fromISODateString('1/1/2017')).toBeNull(); + expect(adapter.fromIso8601('1985-04-12T23:20:50.52Z')).not.toBeNull(); + expect(adapter.fromIso8601('1996-12-19T16:39:57-08:00')).not.toBeNull(); + expect(adapter.fromIso8601('1937-01-01T12:00:27.87+00:20')).not.toBeNull(); + expect(adapter.fromIso8601('1990-13-31T23:59:00Z')).toBeNull(); + expect(adapter.fromIso8601('1/1/2017')).toBeNull(); }); it('setLocale should not modify global moment locale', () => { @@ -350,7 +350,7 @@ describe('MomentDateAdapter', () => { adapter.addCalendarDays(date, 1); adapter.addCalendarMonths(date, 1); adapter.addCalendarYears(date, 1); - adapter.toISODateString(date); + adapter.toIso8601(date); adapter.isDateInstance(date); adapter.isValid(date); expect(date.locale()).toBe('en'); diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index 58bb25c028ca..cbf42b5b2199 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -170,11 +170,11 @@ export class MomentDateAdapter extends DateAdapter { return this.clone(date).add({days}); } - toISODateString(date: Moment): string { + toIso8601(date: Moment): string { return this.clone(date).format(); } - fromISODateString(iso8601String: string): Moment | null { + fromIso8601(iso8601String: string): Moment | null { let d = moment(iso8601String, moment.ISO_8601).locale(this.locale); return this.isValid(d) ? d : null; } From d3602b2ca90a2ed8ef5002f22bc50094c21e224d Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 19 Sep 2017 09:00:39 -0700 Subject: [PATCH 6/6] fix lint --- src/lib/datepicker/datepicker.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index c16b452a1f44..a5f4a93f99b9 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -274,7 +274,8 @@ describe('MdDatepicker', () => { testComponent.date = '1/1/2017' as any; expect(() => fixture.detectChanges()).toThrowError( - /Datepicker: Value must be either a date object recognized by the DateAdapter or an ISO 8601 string\. Instead got: 1\/1\/2017/); + 'Datepicker: Value must be either a date object recognized by the DateAdapter or an ' + + 'ISO 8601 string. Instead got: 1/1/2017'); testComponent.date = null; });