From ebebca0468469747d0811a24d8b5b23a016fcedd Mon Sep 17 00:00:00 2001 From: Plamena Miteva Date: Fri, 26 Jun 2020 11:02:48 +0300 Subject: [PATCH] test(date-range-picker): improve coverage and refactor #5732 (#7647) Authored-by: Boris Plamena Miteva --- .../src/lib/date-picker/date-picker.utils.ts | 40 +- .../date-range-picker-inputs.common.ts | 16 +- .../date-range-picker.component.html | 4 +- .../date-range-picker.component.spec.ts | 611 ++++++++++++------ .../date-range-picker.component.ts | 127 ++-- .../date-time-editor.directive.spec.ts | 127 ++-- .../date-time-editor.directive.ts | 40 +- .../lib/test-utils/controls-functions.spec.ts | 125 ++-- 8 files changed, 686 insertions(+), 404 deletions(-) diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts index 39f84ac0714..a73741cea34 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts @@ -1,5 +1,6 @@ import { isIE } from '../core/utils'; import { DatePart, DatePartInfo } from '../directives/date-time-editor/date-time-editor.common'; +import { formatDate, FormatWidth, getLocaleDateFormat } from '@angular/common'; /** * This enum is used to keep the date validation result. @@ -48,7 +49,7 @@ export abstract class DatePickerUtil { /** - * TODO: Unit tests for all public methods. + * TODO: (in issue #6483) Unit tests and docs for all public methods. */ @@ -147,6 +148,38 @@ export abstract class DatePickerUtil { return DatePickerUtil.getMask(parts); } + public static formatDate(value: number | Date, format: string, locale: string, timezone?: string): string { + let formattedDate: string; + try { + formattedDate = formatDate(value, format, locale, timezone); + } catch { + this.logMissingLocaleSettings(locale); + const formatter = new Intl.DateTimeFormat(locale); + formattedDate = formatter.format(value); + } + + return formattedDate; + } + + public static getLocaleDateFormat(locale: string, displayFormat?: string): string { + const formatKeys = Object.keys(FormatWidth) as (keyof FormatWidth)[]; + const targetKey = formatKeys.find(k => k.toLowerCase() === displayFormat?.toLowerCase().replace('date', '')); + if (!targetKey) { + // if displayFormat is not shortDate, longDate, etc. + // or if it is not set by the user + return displayFormat; + } + let format: string; + try { + format = getLocaleDateFormat(locale, FormatWidth[targetKey]); + } catch { + this.logMissingLocaleSettings(locale); + format = DatePickerUtil.getDefaultInputFormat(locale); + } + + return format; + } + public static isDateOrTimeChar(char: string): boolean { return DATE_CHARS.indexOf(char) !== -1 || TIME_CHARS.indexOf(char) !== -1; } @@ -303,6 +336,11 @@ export abstract class DatePickerUtil { return _value.getTime() < _minValue.getTime(); } + private static logMissingLocaleSettings(locale: string): void { + console.warn(`Missing locale data for the locale ${locale}. Please refer to https://angular.io/guide/i18n#i18n-pipes`); + console.warn('Using default browser locale settings.'); + } + private static ensureLeadingZero(part: DatePartInfo) { switch (part.type) { case DatePart.Date: diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts index e7209fec9c0..4921159f623 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts @@ -1,9 +1,9 @@ import { Component, ContentChild, Pipe, PipeTransform, Output, EventEmitter, HostListener, Directive } from '@angular/core'; -import { formatDate } from '@angular/common'; import { NgControl } from '@angular/forms'; import { IgxInputDirective, IgxInputState } from '../input-group/public_api'; import { IgxInputGroupComponent } from '../input-group/input-group.component'; import { IgxInputGroupBase } from '../input-group/input-group.common'; +import { DatePickerUtil } from '../date-picker/date-picker.utils'; import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api'; /** @@ -17,14 +17,17 @@ export interface DateRange { /** @hidden @internal */ @Pipe({ name: 'dateRange' }) export class DateRangePickerFormatPipe implements PipeTransform { - public transform(values: DateRange, inputFormat?: string, locale?: string): string { - if (!values) { + public transform(values: DateRange, appliedFormat?: string, + locale?: string, formatter?: (_: DateRange) => string): string { + if (!values || !values.start && !values.end) { return ''; } + if (formatter) { + return formatter(values); + } const { start, end } = values; - // TODO: move default locale from IgxDateTimeEditorDirective to its commons file/use displayFormat - const startDate = inputFormat ? formatDate(start, inputFormat, locale || 'en') : start?.toLocaleDateString(); - const endDate = inputFormat ? formatDate(end, inputFormat, locale || 'en') : end?.toLocaleDateString(); + const startDate = appliedFormat ? DatePickerUtil.formatDate(start, appliedFormat, locale || 'en') : start?.toLocaleDateString(); + const endDate = appliedFormat ? DatePickerUtil.formatDate(end, appliedFormat, locale || 'en') : end?.toLocaleDateString(); let formatted; if (start) { formatted = `${startDate} - `; @@ -33,7 +36,6 @@ export class DateRangePickerFormatPipe implements PipeTransform { } } - // TODO: no need to set format twice return formatted ? formatted : ''; } } diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html index 04e29fc311a..04d690f4fb9 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html @@ -43,12 +43,12 @@ diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts index 00cf852e2d2..dbcb3bbf36c 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts @@ -3,14 +3,17 @@ import { Component, OnInit, ViewChild, DebugElement } from '@angular/core'; import { IgxInputGroupModule } from '../input-group/public_api'; import { InteractionMode } from '../core/enums'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, FormControl } from '@angular/forms'; import { IgxCalendarComponent } from '../calendar/public_api'; import { IgxDateRangePickerModule } from './date-range-picker.module'; import { By } from '@angular/platform-browser'; +import { ControlsFunction } from '../test-utils/controls-functions.spec'; import { UIInteractions } from '../test-utils/ui-interactions.spec'; import { configureTestSuite } from '../test-utils/configure-suite'; import { HelperTestFunctions } from '../calendar/calendar-helper-utils'; -import { IgxDateTimeEditorModule } from '../directives/date-time-editor/public_api'; +import { CancelableEventArgs } from '../core/utils'; +import { DateRange } from './date-range-picker-inputs.common'; +import { IgxDateTimeEditorModule, IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api'; import { DateRangeType } from '../core/dates'; import { IgxDateRangePickerComponent, IgxDateRangeEndComponent } from './public_api'; import { IgxIconModule } from '../icon/public_api'; @@ -18,17 +21,27 @@ import { IgxIconModule } from '../icon/public_api'; // The number of milliseconds in one day const ONE_DAY = 1000 * 60 * 60 * 24; const DEBOUNCE_TIME = 16; - -const CSS_CLASS_INPUT = 'igx-input-group__input'; +const dEFAULT_ICON_TEXT = 'calendar_today'; +const DEFAULT_FORMAT_OPTIONS = { day: '2-digit', month: '2-digit', year: 'numeric' }; +const CSS_CLASS_INPUT_GROUP = '.igx-input-group__bundle'; +const CSS_CLASS_INPUT = '.igx-input-group__input'; const CSS_CLASS_CALENDAR = 'igx-calendar'; -const CSS_CLASS_CALENDAR_TOGGLE = 'igx-toggle'; // TODO Implementation -> maybe add class for the div container ('igx-date-picker') -const CSS_CLASS_TOGGLE_BUTTON = 'igx-icon'; +const CSS_CLASS_CALENDAR_TOGGLE = '.igx-toggle'; +const CSS_CLASS_ICON = 'igx-icon'; const CSS_CLASS_DONE_BUTTON = 'igx-button--flat'; +const CSS_CLASS_LABEL = 'igx-input-group__label'; describe('IgxDateRangePicker', () => { describe('Unit tests: ', () => { const elementRef = { nativeElement: null }; const calendar = new IgxCalendarComponent(); + const mockNgControl = jasmine.createSpyObj('NgControl', + ['registerOnChangeCb', + 'registerOnTouchedCb', + 'registerOnValidatorChangeCb']); + const mockInjector = jasmine.createSpyObj('Injector', { + 'get': mockNgControl + }); it('should set range dates correctly through selectRange method', () => { const dateRange = new IgxDateRangePickerComponent(elementRef, null, null, null); dateRange.calendar = calendar; @@ -70,8 +83,7 @@ describe('IgxDateRangePicker', () => { expect(dateRange.rangeSelected.emit).toHaveBeenCalledWith({ start: startDate, end: startDate }); }); - it('should correctly implement interface methods - ControlValueAccessor ', () => { - const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']); + it('should correctly implement interface methods - ControlValueAccessor', () => { const range = { start: new Date(2020, 1, 18), end: new Date(2020, 1, 28) }; const rangeUpdate = { start: new Date(2020, 2, 22), end: new Date(2020, 2, 25) }; @@ -99,15 +111,74 @@ describe('IgxDateRangePicker', () => { // awaiting implementation - OnTouched callback // Docs: changes the value, turning the control dirty; or blurs the form control element, setting the control to touched. // when handleSelection fires should be touched&dirty // when input is blurred(two inputs), should be touched. - // dateRangePicker.handleSelection([range.start]); - // expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1); - - // awaiting implementation - setDisabledState - // dateRangePicker.setDisabledState(true); - // expect(dateRangePicker.disabled).toBe(true); - // dateRangePicker.setDisabledState(false); - // expect(dateRangePicker.disabled).toBe(false); + dateRangePicker.handleSelection([range.start]); + (dateRangePicker as any).updateValidityOnBlur(); + expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1); + + dateRangePicker.setDisabledState(true); + expect(dateRangePicker.disabled).toBe(true); + dateRangePicker.setDisabledState(false); + expect(dateRangePicker.disabled).toBe(false); }); + + it('should validate correctly minValue and maxValue ', () => { + const dateRange = new IgxDateRangePickerComponent(elementRef, null, null, mockInjector); + dateRange.ngOnInit(); + + dateRange.calendar = calendar; + dateRange.registerOnChange(mockNgControl.registerOnChangeCb); + dateRange.registerOnValidatorChange(mockNgControl.registerOnValidatorChangeCb); + + dateRange.minValue = new Date(2020, 4, 7); + expect(mockNgControl.registerOnValidatorChangeCb).toHaveBeenCalledTimes(1); + dateRange.maxValue = new Date(2020, 8, 7); + expect(mockNgControl.registerOnValidatorChangeCb).toHaveBeenCalledTimes(2); + + const range = { start: new Date(2020, 4, 18), end: new Date(2020, 6, 28) }; + dateRange.writeValue(range); + const mockFormControl = new FormControl(dateRange.value); + expect(dateRange.validate(mockFormControl)).toBeNull(); + + range.start.setMonth(2); + expect(dateRange.validate(mockFormControl)).toEqual({ minValue: true }); + + range.end.setMonth(10); + expect(dateRange.validate(mockFormControl)).toEqual({ minValue: true, maxValue: true }); + }); + + it('should disable calendar dates when min and/or max values as dates are provided', fakeAsync(() => { + const dateRange = new IgxDateRangePickerComponent(elementRef, null, null, mockInjector); + dateRange.ngOnInit(); + + dateRange.calendar = calendar; + dateRange.minValue = new Date(2000, 10, 1); + dateRange.maxValue = new Date(2000, 10, 20); + + spyOn(calendar, 'deselectDate').and.returnValue(null); + (dateRange as any).updateCalendar(); + expect(dateRange.calendar.disabledDates.length).toEqual(2); + expect(dateRange.calendar.disabledDates[0].type).toEqual(DateRangeType.Before); + expect(dateRange.calendar.disabledDates[0].dateRange[0]).toEqual(dateRange.minValue); + expect(dateRange.calendar.disabledDates[1].type).toEqual(DateRangeType.After); + expect(dateRange.calendar.disabledDates[1].dateRange[0]).toEqual(dateRange.maxValue); + })); + + it('should disable calendar dates when min and/or max values as strings are provided', fakeAsync(() => { + const dateRange = new IgxDateRangePickerComponent(elementRef, null, null, mockInjector); + dateRange.ngOnInit(); + + dateRange.calendar = calendar; + dateRange.minValue = '2000/10/1'; + dateRange.maxValue = '2000/10/30'; + + spyOn(calendar, 'deselectDate').and.returnValue(null); + (dateRange as any).updateCalendar(); + expect(dateRange.calendar.disabledDates.length).toEqual(2); + expect(dateRange.calendar.disabledDates[0].type).toEqual(DateRangeType.Before); + expect(dateRange.calendar.disabledDates[0].dateRange[0]).toEqual(new Date(dateRange.minValue)); + expect(dateRange.calendar.disabledDates[1].type).toEqual(DateRangeType.After); + expect(dateRange.calendar.disabledDates[1].dateRange[0]).toEqual(new Date(dateRange.maxValue)); + })); }); describe('Integration tests', () => { @@ -118,18 +189,6 @@ describe('IgxDateRangePicker', () => { let calendar: DebugElement; let calendarDays: DebugElement[]; - /** - * Formats a date to 'MM/dd/yyyy' string - * @param date Date to be formatted - */ - function formatFullDate(date: Date): string { - const year = `${date.getFullYear()}`.padStart(4, '0'); - const month = `${date.getMonth() + 1}`.padStart(2, '0'); - const day = `${date.getDate()}`.padStart(2, '0'); - const fullDate = [month, day, year].join('/'); - return fullDate; - } - function selectDateRangeFromCalendar(startDateDay: number, dayRange: number) { const startDateDayElIndex = startDateDay - 1; const endDateDayElIndex = startDateDayElIndex + dayRange; @@ -143,6 +202,7 @@ describe('IgxDateRangePicker', () => { dateRange.close(); fixture.detectChanges(); } + describe('Single Input', () => { let singleInputElement: DebugElement; configureTestSuite(); @@ -155,6 +215,7 @@ describe('IgxDateRangePicker', () => { imports: [IgxDateRangePickerModule, IgxDateTimeEditorModule, IgxInputGroupModule, + IgxIconModule, FormsModule, NoopAnimationsModule] }) @@ -165,7 +226,7 @@ describe('IgxDateRangePicker', () => { fixture.detectChanges(); dateRange = fixture.componentInstance.dateRange; calendarDays = fixture.debugElement.queryAll(By.css(HelperTestFunctions.DAY_CSSCLASS)); - singleInputElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`)); + singleInputElement = fixture.debugElement.query(By.css(CSS_CLASS_INPUT)); calendar = fixture.debugElement.query(By.css(CSS_CLASS_CALENDAR)); })); @@ -180,6 +241,7 @@ describe('IgxDateRangePicker', () => { describe('Selection tests', () => { it('should assign range dates to the input when selecting a range from the calendar', () => { fixture.componentInstance.mode = InteractionMode.DropDown; + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); const dayRange = 15; @@ -193,6 +255,7 @@ describe('IgxDateRangePicker', () => { it('should assign range values correctly when selecting dates in reversed order', () => { fixture.componentInstance.mode = InteractionMode.DropDown; + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); const dayRange = -5; @@ -205,6 +268,7 @@ describe('IgxDateRangePicker', () => { it('should set start and end dates on single date selection', () => { fixture.componentInstance.mode = InteractionMode.DropDown; + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); const dayRange = 0; @@ -216,6 +280,8 @@ describe('IgxDateRangePicker', () => { }); it('should update input correctly on first and last date selection', () => { + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; + fixture.detectChanges(); const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), 1, 0, 0, 0); endDate = new Date(today.getFullYear(), today.getMonth() + 2, 0, 0, 0, 0); @@ -226,6 +292,8 @@ describe('IgxDateRangePicker', () => { }); it('should assign range values correctly when selecting through API', () => { + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; + fixture.detectChanges(); startDate = new Date(2020, 10, 8, 0, 0, 0); endDate = new Date(2020, 11, 8, 0, 0, 0); dateRange.selectRange(startDate, endDate); @@ -241,7 +309,7 @@ describe('IgxDateRangePicker', () => { }); describe('Properties & events tests', () => { - it('should show date picker with placeholder', () => { + it('should display placeholder', () => { fixture.detectChanges(); expect(singleInputElement.nativeElement.placeholder).toEqual('MM/dd/yyyy - MM/dd/yyyy'); @@ -251,8 +319,71 @@ describe('IgxDateRangePicker', () => { expect(singleInputElement.nativeElement.placeholder).toEqual(placeholder); }); + it('should support different display and input formats', () => { + dateRange.inputFormat = 'dd/MM/yy'; // should not be registered + dateRange.displayFormat = 'longDate'; + fixture.detectChanges(); + expect(dateRange.inputDirective.placeholder).toEqual(`MMMM d, y - MMMM d, y`); + const today = new Date(); + startDate = new Date(today.getFullYear(), today.getMonth(), 1, 0, 0, 0); + endDate = new Date(today.getFullYear(), today.getMonth(), 5, 0, 0, 0); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + const longDateOptions = { month: 'long', day: 'numeric' }; + let inputStartDate = `${ControlsFunction.formatDate(startDate, longDateOptions)}, ${startDate.getFullYear()}`; + let inputEndDate = `${ControlsFunction.formatDate(endDate, longDateOptions)}, ${endDate.getFullYear()}`; + expect(singleInputElement.nativeElement.value).toEqual(`${inputStartDate} - ${inputEndDate}`); + + dateRange.value = null; + dateRange.displayFormat = 'shortDate'; + fixture.detectChanges(); + + expect(dateRange.inputDirective.placeholder).toEqual(`M/d/yy - M/d/yy`); + startDate.setDate(2); + endDate.setDate(19); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const shortDateOptions = { day: 'numeric', month: 'numeric', year: '2-digit' }; + inputStartDate = ControlsFunction.formatDate(startDate, shortDateOptions); + inputEndDate = ControlsFunction.formatDate(endDate, shortDateOptions); + expect(singleInputElement.nativeElement.value).toEqual(`${inputStartDate} - ${inputEndDate}`); + + dateRange.value = null; + dateRange.displayFormat = 'fullDate'; + fixture.detectChanges(); + + expect(dateRange.inputDirective.placeholder).toEqual(`EEEE, MMMM d, y - EEEE, MMMM d, y`); + startDate.setDate(12); + endDate.setDate(23); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const fullDateOptions = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }; + inputStartDate = ControlsFunction.formatDate(startDate, fullDateOptions); + inputEndDate = ControlsFunction.formatDate(endDate, fullDateOptions); + expect(singleInputElement.nativeElement.value).toEqual(`${inputStartDate} - ${inputEndDate}`); + + dateRange.value = null; + dateRange.displayFormat = 'dd-MM-yy'; + fixture.detectChanges(); + + startDate.setDate(9); + endDate.setDate(13); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const customFormatOptions = { day: 'numeric', month: 'numeric', year: '2-digit' }; + inputStartDate = ControlsFunction.formatDate(startDate, customFormatOptions, 'en-GB'). + replace(/\//g, '-'); + inputEndDate = ControlsFunction.formatDate(endDate, customFormatOptions, 'en-GB'). + replace(/\//g, '-'); + expect(singleInputElement.nativeElement.value).toEqual(`${inputStartDate} - ${inputEndDate}`); + }); + it('should close the calendar with the "Done" button', fakeAsync(() => { fixture.componentInstance.mode = InteractionMode.Dialog; + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); spyOn(dateRange.onClosing, 'emit').and.callThrough(); spyOn(dateRange.onClosed, 'emit').and.callThrough(); @@ -329,47 +460,9 @@ describe('IgxDateRangePicker', () => { fixture.detectChanges(); }); - it('should disable calendar dates when min and/or max values as dates are provided', fakeAsync(() => { - // TODO: move this to unit tests - const minDate = new Date(2000, 10, 1); - const maxDate = new Date(2000, 10, 20); - fixture.componentInstance.minValue = minDate; - fixture.componentInstance.maxValue = maxDate; - fixture.detectChanges(); - - dateRange.open(); - tick(); - fixture.detectChanges(); - - const calendarComponent: IgxCalendarComponent = calendar.componentInstance; - expect(calendarComponent.disabledDates.length).toEqual(2); - expect(calendarComponent.disabledDates[0].type).toEqual(DateRangeType.Before); - expect(calendarComponent.disabledDates[0].dateRange[0]).toEqual(minDate); - expect(calendarComponent.disabledDates[1].type).toEqual(DateRangeType.After); - expect(calendarComponent.disabledDates[1].dateRange[0]).toEqual(maxDate); - })); - - it('should disable calendar dates when min and/or max values as strings are provided', fakeAsync(() => { - // TODO: move this to unit tests - const minDate = '2000/10/1'; - const maxDate = '2000/10/30'; - fixture.componentInstance.minValue = minDate; - fixture.componentInstance.maxValue = maxDate; - fixture.detectChanges(); - - dateRange.open(); - tick(); - fixture.detectChanges(); - - const calendarComponent: IgxCalendarComponent = calendar.componentInstance; - expect(calendarComponent.disabledDates.length).toEqual(2); - expect(calendarComponent.disabledDates[0].type).toEqual(DateRangeType.Before); - expect(calendarComponent.disabledDates[0].dateRange[0]).toEqual(new Date(minDate)); - expect(calendarComponent.disabledDates[1].type).toEqual(DateRangeType.After); - expect(calendarComponent.disabledDates[1].dateRange[0]).toEqual(new Date(maxDate)); - })); - it('should emit open/close events - open/close methods', fakeAsync(() => { + fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; + fixture.detectChanges(); spyOn(dateRange.onOpening, 'emit').and.callThrough(); spyOn(dateRange.onOpened, 'emit').and.callThrough(); spyOn(dateRange.onClosing, 'emit').and.callThrough(); @@ -433,6 +526,31 @@ describe('IgxDateRangePicker', () => { expect(dateRange.onClosed.emit).toHaveBeenCalledTimes(1); expect(dateRange.onClosed.emit).toHaveBeenCalledWith({ owner: dateRange }); })); + + it('should not close calendar if closing event is canceled', fakeAsync(() => { + spyOn(dateRange.onClosing, 'emit').and.callThrough(); + spyOn(dateRange.onClosed, 'emit').and.callThrough(); + dateRange.onClosing.subscribe((e: CancelableEventArgs) => e.cancel = true); + + dateRange.toggle(); + tick(DEBOUNCE_TIME); + fixture.detectChanges(); + expect(dateRange.collapsed).toBeFalsy(); + + const dayRange = 6; + const today = new Date(); + startDate = new Date(today.getFullYear(), today.getMonth(), 14, 0, 0, 0); + endDate = new Date(startDate); + endDate.setDate(endDate.getDate() + dayRange); + dateRange.selectRange(startDate, endDate); + + dateRange.close(); + tick(); + fixture.detectChanges(); + expect(dateRange.collapsed).toBeFalsy(); + expect(dateRange.onClosing.emit).toHaveBeenCalled(); + expect(dateRange.onClosed.emit).not.toHaveBeenCalled(); + })); }); describe('Keyboard navigation', () => { @@ -513,8 +631,8 @@ describe('IgxDateRangePicker', () => { function verifyDateRange() { expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(endDate); - expect(startInput.nativeElement.value).toEqual(formatFullDate(startDate)); - const expectedEndDate = endDate ? formatFullDate(endDate) : ''; + expect(startInput.nativeElement.value).toEqual(ControlsFunction.formatDate(startDate, DEFAULT_FORMAT_OPTIONS)); + const expectedEndDate = endDate ? ControlsFunction.formatDate(endDate, DEFAULT_FORMAT_OPTIONS) : ''; expect(endInput.nativeElement.value).toEqual(expectedEndDate); } @@ -586,6 +704,103 @@ describe('IgxDateRangePicker', () => { fixture.detectChanges(); verifyDateRange(); }); + it('should support different input and display formats', () => { + let inputFormat = 'dd/MM/yy'; + let displayFormat = 'longDate'; + fixture.componentInstance.inputFormat = inputFormat; + fixture.componentInstance.displayFormat = displayFormat; + fixture.detectChanges(); + + const startInputEditor = startInput.injector.get(IgxDateTimeEditorDirective); + const endInputEditor = endInput.injector.get(IgxDateTimeEditorDirective); + expect(startInputEditor.inputFormat).toEqual(inputFormat); + expect(startInputEditor.displayFormat).toEqual(displayFormat); + expect(endInputEditor.inputFormat).toEqual(inputFormat); + expect(endInputEditor.displayFormat).toEqual(displayFormat); + + inputFormat = 'yy-MM-dd'; + displayFormat = 'shortDate'; + fixture.componentInstance.inputFormat = inputFormat; + fixture.componentInstance.displayFormat = displayFormat; + fixture.detectChanges(); + + expect(startInputEditor.inputFormat).toEqual(inputFormat); + expect(startInputEditor.displayFormat).toEqual(displayFormat); + expect(endInputEditor.inputFormat).toEqual(inputFormat); + expect(endInputEditor.displayFormat).toEqual(displayFormat); + + inputFormat = 'EE/MM/yy'; + displayFormat = 'fullDate'; + fixture.componentInstance.inputFormat = inputFormat; + fixture.componentInstance.displayFormat = displayFormat; + fixture.detectChanges(); + + expect(startInputEditor.inputFormat).toEqual(inputFormat); + expect(startInputEditor.displayFormat).toEqual(displayFormat); + expect(endInputEditor.inputFormat).toEqual(inputFormat); + expect(endInputEditor.displayFormat).toEqual(displayFormat); + + inputFormat = 'MMM, yy'; + displayFormat = 'MMMM, yyyy'; + fixture.componentInstance.inputFormat = inputFormat; + fixture.componentInstance.displayFormat = displayFormat; + fixture.detectChanges(); + + expect(startInputEditor.inputFormat).toEqual(inputFormat); + expect(startInputEditor.displayFormat).toEqual(displayFormat); + expect(endInputEditor.inputFormat).toEqual(inputFormat); + expect(endInputEditor.displayFormat).toEqual(displayFormat); + }); + + it('should display dates according to the applied display format', () => { + const today = new Date(); + startDate = new Date(today.getFullYear(), today.getMonth(), 1, 0, 0, 0); + endDate = new Date(today.getFullYear(), today.getMonth(), 5, 0, 0, 0); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + expect(startInput.nativeElement.value).toEqual(ControlsFunction.formatDate(startDate, DEFAULT_FORMAT_OPTIONS)); + expect(endInput.nativeElement.value).toEqual(ControlsFunction.formatDate(endDate, DEFAULT_FORMAT_OPTIONS)); + + fixture.componentInstance.displayFormat = 'shortDate'; + fixture.detectChanges(); + + startDate.setDate(2); + endDate.setDate(19); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const shortDateFormatOptions = { day: 'numeric', month: 'numeric', year: '2-digit' }; + expect(startInput.nativeElement.value).toEqual(ControlsFunction.formatDate(startDate, shortDateFormatOptions)); + expect(endInput.nativeElement.value).toEqual(ControlsFunction.formatDate(endDate, shortDateFormatOptions)); + + fixture.componentInstance.displayFormat = 'fullDate'; + fixture.detectChanges(); + + startDate.setDate(12); + endDate.setDate(23); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const fullDateFormatOptions = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }; + expect(startInput.nativeElement.value).toEqual(ControlsFunction.formatDate(startDate, fullDateFormatOptions)); + expect(endInput.nativeElement.value).toEqual(ControlsFunction.formatDate(endDate, fullDateFormatOptions)); + + fixture.componentInstance.displayFormat = 'dd-MM-yy'; + fixture.detectChanges(); + + startDate.setDate(9); + endDate.setDate(13); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const customFormatOptions = { day: 'numeric', month: 'numeric', year: '2-digit' }; + const inputStartDate = ControlsFunction.formatDate(startDate, customFormatOptions, 'en-GB'). + replace(/\//g, '-'); + const inputEndDate = ControlsFunction.formatDate(endDate, customFormatOptions, 'en-GB'). + replace(/\//g, '-'); + expect(startInput.nativeElement.value).toEqual(inputStartDate); + expect(endInput.nativeElement.value).toEqual(inputEndDate); + }); it('should select a range from the calendar only when the two inputs are filled in', fakeAsync(() => { fixture.componentInstance.mode = InteractionMode.DropDown; @@ -714,120 +929,74 @@ describe('IgxDateRangePicker', () => { }); }); - describe('Calendar UI', () => { + describe('Rendering', () => { configureTestSuite(); beforeAll(async(() => { TestBed.configureTestingModule({ declarations: [ DateRangeTestComponent, - DateRangeTwoInputsTestComponent + DateRangeDefaultComponent, + DateRangeCustomComponent, + DateRangeTemplatesComponent ], - imports: [IgxDateRangePickerModule, IgxDateTimeEditorModule, IgxInputGroupModule, FormsModule, NoopAnimationsModule] + imports: [IgxDateRangePickerModule, + IgxDateTimeEditorModule, + IgxInputGroupModule, + IgxIconModule, + FormsModule, + NoopAnimationsModule] }) .compileComponents(); })); - beforeEach(async () => { - fixture = TestBed.createComponent(DateRangeTwoInputsTestComponent); - fixture.detectChanges(); - dateRange = fixture.componentInstance.dateRange; - }); - - xit('should move focus to the calendar on open in dropdown mode', fakeAsync(() => { - fixture = TestBed.createComponent(DateRangeTwoInputsTestComponent); - fixture.componentInstance.mode = InteractionMode.DropDown; - fixture.detectChanges(); - - const startInput = fixture.debugElement.queryAll(By.css('.igx-input-group'))[1]; - // UIInteractions.clickElement(startInput.nativeElement); - tick(100); - fixture.detectChanges(); - expect(document.activeElement.textContent.trim()).toMatch(new Date().getDate().toString()); - })); - - xit('should move focus to the calendar on open in dialog mode', fakeAsync(() => { - fixture = TestBed.createComponent(DateRangeTwoInputsTestComponent); - fixture.componentInstance.mode = InteractionMode.Dialog; - fixture.detectChanges(); - - const startInput = fixture.debugElement.queryAll(By.css('.igx-input-group'))[1]; - // UIInteractions.clickElement(startInput.nativeElement); - tick(100); - fixture.detectChanges(); - expect(document.activeElement.textContent.trim()).toMatch(new Date().getDate().toString()); - })); - - xit('should move the focus to start input on close (date range with two inputs)', fakeAsync(() => { - fixture = TestBed.createComponent(DateRangeTwoInputsTestComponent); - fixture.componentInstance.mode = InteractionMode.DropDown; - fixture.detectChanges(); - - fixture.componentInstance.dateRange.open(); - tick(); - fixture.detectChanges(); - - const button = fixture.debugElement.query(By.css('.igx-button--flat')); - // UIInteractions.clickElement(button); - tick(); - fixture.detectChanges(); - - fixture.componentInstance.dateRange.close(); - tick(); - fixture.detectChanges(); - - expect(document.activeElement).toHaveClass('igx-input-group__input'); - })); - - xit('should move the focus to the single input on close', fakeAsync(() => { + it('should render default toggle icon', () => { fixture = TestBed.createComponent(DateRangeDefaultComponent); - fixture.componentInstance.mode = InteractionMode.DropDown; - fixture.detectChanges(); - - fixture.componentInstance.dateRange.open(); - tick(); fixture.detectChanges(); - const button = fixture.debugElement.query(By.css('.igx-button--flat')); - // UIInteractions.clickElement(button); - tick(); - fixture.detectChanges(); + const inputGroup = fixture.debugElement.query(By.css(CSS_CLASS_INPUT_GROUP)); + expect(inputGroup.children[0].nativeElement.innerText).toBe(dEFAULT_ICON_TEXT); + expect(inputGroup.children[0].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + }); - fixture.componentInstance.dateRange.close(); - tick(); + it('should be able to set toggle icon', () => { + const prefixIconText = 'flight_takeoff'; + const suffixIconText = 'flight_land'; + const additionalIconText = 'calendar_view_day'; + fixture = TestBed.createComponent(DateRangeTemplatesComponent); fixture.detectChanges(); - expect(document.activeElement).toHaveClass('igx-input-group__input'); - })); - }); - - describe('Templating', () => { - }); + const inputGroups = fixture.debugElement.queryAll(By.css(CSS_CLASS_INPUT_GROUP)); + const prefixSingleRangeInput = inputGroups[0]; + expect(prefixSingleRangeInput.children[0].nativeElement.innerText).toBe(prefixIconText); + expect(prefixSingleRangeInput.children[0].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + + const suffixSingleRangeInput = inputGroups[1]; + expect(suffixSingleRangeInput.children[1].nativeElement.innerText).toBe(suffixIconText); + expect(suffixSingleRangeInput.children[1].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + + const addPrefixSingleRangeInput = inputGroups[2]; + expect(addPrefixSingleRangeInput.children[0].nativeElement.innerText).toBe(dEFAULT_ICON_TEXT); + expect(addPrefixSingleRangeInput.children[0].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + expect(addPrefixSingleRangeInput.children[1].nativeElement.innerText).toBe(additionalIconText); + expect(addPrefixSingleRangeInput.children[1].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + + const prefixRangeInput = inputGroups[3]; + expect(prefixRangeInput.children[0].nativeElement.innerText).toBe(prefixIconText); + expect(prefixRangeInput.children[0].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + + const suffixRangeInput = inputGroups[4]; + expect(suffixRangeInput.children[1].nativeElement.innerText).toBe(suffixIconText); + expect(suffixRangeInput.children[1].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + expect(suffixRangeInput.children[2].nativeElement.innerText).toBe(additionalIconText); + expect(suffixRangeInput.children[2].children[0].classes[CSS_CLASS_ICON]).toBeTruthy(); + }); - describe('ARIA', () => { - let singleInputElement: DebugElement; - let calendarElement: DebugElement; - let calendarWrapper: DebugElement; - let toggleBtn: DebugElement; - configureTestSuite(); - beforeAll(async(() => { - TestBed.configureTestingModule({ - declarations: [ - DateRangeTestComponent, - DateRangeDefaultCustomLabelComponent - ], - imports: [IgxDateRangePickerModule, IgxDateTimeEditorModule, IgxInputGroupModule, FormsModule, NoopAnimationsModule] - }) - .compileComponents(); - })); - beforeEach(fakeAsync(() => { - fixture = TestBed.createComponent(DateRangeDefaultCustomLabelComponent); + it('should render aria attributes properly', fakeAsync(() => { + fixture = TestBed.createComponent(DateRangeCustomComponent); fixture.detectChanges(); dateRange = fixture.componentInstance.dateRange; - - })); - it('should render aria attributes properly', fakeAsync(() => { - toggleBtn = fixture.debugElement.query(By.css(`.${CSS_CLASS_TOGGLE_BUTTON}`)); - calendarElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_CALENDAR}`)); - singleInputElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`)); + const toggleBtn = fixture.debugElement.query(By.css(`.${CSS_CLASS_ICON}`)); + const calendarElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_CALENDAR}`)); + const singleInputElement = fixture.debugElement.query(By.css(CSS_CLASS_INPUT)); startDate = new Date(2020, 1, 1); endDate = new Date(2020, 1, 4); const expectedLabelID = dateRange.label.id; @@ -845,7 +1014,7 @@ describe('IgxDateRangePicker', () => { tick(); fixture.detectChanges(); - calendarWrapper = fixture.debugElement.query(By.css(`.${CSS_CLASS_CALENDAR_TOGGLE}`)); + const calendarWrapper = fixture.debugElement.query(By.css(CSS_CLASS_CALENDAR_TOGGLE)); expect(singleInputElement.nativeElement.getAttribute('aria-expanded')).toEqual('true'); expect(calendarWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('false'); @@ -860,6 +1029,30 @@ describe('IgxDateRangePicker', () => { fixture.detectChanges(); expect(singleInputElement.nativeElement.getAttribute('placeholder')).toEqual(''); })); + + it('should render custom label', () => { + fixture = TestBed.createComponent(DateRangeCustomComponent); + fixture.detectChanges(); + + const inputGroup = fixture.debugElement.query(By.css(CSS_CLASS_INPUT_GROUP)); + expect(inputGroup.children[1].children[0].classes[CSS_CLASS_LABEL]).toBeTruthy(); + expect(inputGroup.children[1].children[0].nativeElement.textContent).toEqual('Select Date'); + }); + + it('should be able to apply custom format', () => { + fixture = TestBed.createComponent(DateRangeCustomComponent); + fixture.detectChanges(); + dateRange = fixture.componentInstance.dateRange; + const singleInputElement = fixture.debugElement.query(By.css(CSS_CLASS_INPUT)); + + startDate = new Date(2020, 10, 8, 0, 0, 0); + endDate = new Date(2020, 11, 8, 0, 0, 0); + dateRange.selectRange(startDate, endDate); + fixture.detectChanges(); + + const result = fixture.componentInstance.formatter({ start: startDate, end: endDate }); + expect(singleInputElement.nativeElement.value).toEqual(result); + }); }); }); }); @@ -869,7 +1062,7 @@ describe('IgxDateRangePicker', () => { template: '' }) export class DateRangeTestComponent implements OnInit { - public todayButtonText: string; + [x: string]: any; public doneButtonText: string; public mode: InteractionMode; public minValue: Date | String; @@ -879,15 +1072,22 @@ export class DateRangeTestComponent implements OnInit { public dateRange: IgxDateRangePickerComponent; public ngOnInit(): void { - this.todayButtonText = 'Today'; this.doneButtonText = 'Done'; } } +@Component({ + selector: 'igx-date-range-single-input-test', + template: ` + + + ` +}) +export class DateRangeDefaultComponent extends DateRangeTestComponent { } @Component({ selector: 'igx-date-range-two-inputs-test', template: ` - + calendar_view_day @@ -901,11 +1101,10 @@ export class DateRangeTestComponent implements OnInit { ` }) export class DateRangeTwoInputsTestComponent extends DateRangeTestComponent { - startDate = new Date(2020, 1, 1); - endDate = new Date(2020, 1, 4); range; + inputFormat: string; + displayFormat: string; } - @Component({ selector: 'igx-date-range-two-inputs-ng-model', template: ` @@ -925,22 +1124,62 @@ export class DateRangeTwoInputsNgModelTestComponent extends DateRangeTestCompone @Component({ selector: 'igx-date-range-single-input-label-test', template: ` - - - - ` + + + +` }) -export class DateRangeDefaultCustomLabelComponent extends DateRangeTestComponent { +export class DateRangeCustomComponent extends DateRangeTestComponent { + public date: DateRange; + private monthFormatter = new Intl.DateTimeFormat('en', { month: 'long' }); + + public formatter = (date: DateRange) => { + const startDate = `${this.monthFormatter.format(date.start)} ${date.start.getDate()}, ${date.start.getFullYear()}`; + const endDate = `${this.monthFormatter.format(date.end)} ${date.end.getDate()}, ${date.end.getFullYear()}`; + return `You selected ${startDate}-${endDate}`; + } } - @Component({ - selector: 'igx-date-range-single-input-test', + selector: 'igx-date-range-templates-test', template: ` - + + + flight_takeoff + + + + + flight_land + + + + + + calendar_view_day + + + + + + + flight_takeoff + + + + + + + flight_land + + + + calendar_view_day + + + ` }) -export class DateRangeDefaultComponent extends DateRangeTestComponent { - @ViewChild(IgxDateRangePickerComponent) - public dateRange: IgxDateRangePickerComponent; +export class DateRangeTemplatesComponent extends DateRangeTestComponent { + range; } diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts index 2f37bdac5a0..4e83ec3ab2f 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts @@ -1,33 +1,12 @@ import { - AfterViewInit, - Component, - ContentChild, - ContentChildren, - ElementRef, - EventEmitter, - HostBinding, - Inject, - Injector, - Input, - LOCALE_ID, - OnChanges, - OnDestroy, - OnInit, - Optional, - Output, - QueryList, - SimpleChanges, - TemplateRef, - ViewChild + AfterViewInit, Component, ContentChild, ContentChildren, ElementRef, + EventEmitter, HostBinding, Inject, Injector, Input, LOCALE_ID, + OnChanges, OnDestroy, OnInit, Optional, Output, QueryList, + SimpleChanges, TemplateRef, ViewChild } from '@angular/core'; import { - AbstractControl, - ControlValueAccessor, - NgControl, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validator + AbstractControl, ControlValueAccessor, NgControl, + NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms'; import { fromEvent, Subject, Subscription } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -44,14 +23,12 @@ import { IgxToggleDirective } from '../directives/toggle/toggle.directive'; import { IgxInputDirective, IgxInputGroupComponent, IgxInputState, IgxLabelDirective } from '../input-group/public_api'; import { AutoPositionStrategy, OverlaySettings, PositionSettings } from '../services/public_api'; import { - DateRange, - IgxDateRangeEndComponent, - IgxDateRangeInputsBaseComponent, - IgxDateRangeSeparatorDirective, - IgxDateRangeStartComponent, - IgxPickerToggleComponent + DateRange, IgxDateRangeEndComponent, IgxDateRangeInputsBaseComponent, + IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent, IgxPickerToggleComponent } from './date-range-picker-inputs.common'; +const SingleInputDatesConcatenationString = ' - '; + /** * Provides the ability to select a range of dates from a calendar UI or editable inputs. * @@ -140,10 +117,13 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase public weekStart = WEEKDAYS.SUNDAY; /** - * The `locale` of the calendar. + * Locale settings used for value formatting and calendar. * * @remarks - * Default value is `"en"`. + * Uses Angular's `LOCALE_ID` by default. Affects both input mask and display format if those are not set. + * If a `locale` is set, it must be registered via `registerLocaleData`. + * Please refer to https://angular.io/guide/i18n#i18n-pipes. + * If it is not registered, `Intl` will be used for formatting. * * @example * ```html @@ -374,20 +354,18 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase /** @hidden @internal */ public get appliedFormat(): string { - if (this.formatter) { - return this.formatter(this.value); - } - if (!this.hasProjectedInputs) { - if (this.placeholder !== '') { - return this.placeholder; - } - // TODO: use displayFormat - see how shortDate, longDate can be defined - return this.inputFormat - ? `${this.inputFormat} - ${this.inputFormat}` - : `${DatePickerUtil.DEFAULT_INPUT_FORMAT} - ${DatePickerUtil.DEFAULT_INPUT_FORMAT}`; - } else { - return this.inputFormat || DatePickerUtil.DEFAULT_INPUT_FORMAT; + return DatePickerUtil.getLocaleDateFormat(this.locale, this.displayFormat) + || DatePickerUtil.DEFAULT_INPUT_FORMAT; + } + + /** @hidden @internal */ + public get singleInputFormat(): string { + if (this.placeholder !== '') { + return this.placeholder; } + + const format = this.appliedFormat; + return `${format}${SingleInputDatesConcatenationString}${format}`; } /** @hidden @internal */ @@ -436,10 +414,10 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase constructor(public element: ElementRef, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions, - @Inject(LOCALE_ID) private _locale: any, + @Inject(LOCALE_ID) private localeId: any, private _injector: Injector) { super(_displayDensityOptions); - this.locale = this.locale || this._locale; + this.locale = this.locale || this.localeId; } /** @@ -569,17 +547,16 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase /** @hidden @internal */ public validate(control: AbstractControl): ValidationErrors | null { const value: DateRange = control.value; + const errors = {}; if (value) { - // TODO (in issue #7477) - // Accumulate all errors and return them as one object. if (this.hasProjectedInputs) { const startInput = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent; const endInput = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent; if (!startInput.dateTimeEditor.value) { - return { 'startValue': true }; + Object.assign(errors, { 'startValue': true }); } if (!endInput.dateTimeEditor.value) { - return { 'endValue': true }; + Object.assign(errors, { 'endValue': true }); } } @@ -587,21 +564,17 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase const max = DatePickerUtil.parseDate(this.maxValue); const start = DatePickerUtil.parseDate(value.start); const end = DatePickerUtil.parseDate(value.end); - if (min && start && DatePickerUtil.lessThanMinValue(start, min, false)) { - return { 'minValue': true }; - } - if (min && end && DatePickerUtil.lessThanMinValue(end, min, false)) { - return { 'minValue': true }; - } - if (max && start && DatePickerUtil.greaterThanMaxValue(start, max, false)) { - return { 'maxValue': true }; + if ((min && start && DatePickerUtil.lessThanMinValue(start, min, false)) + || (min && end && DatePickerUtil.lessThanMinValue(end, min, false))) { + Object.assign(errors, { 'minValue': true }); } - if (max && end && DatePickerUtil.greaterThanMaxValue(end, max, false)) { - return { 'maxValue': true }; + if ((max && start && DatePickerUtil.greaterThanMaxValue(start, max, false)) + || (max && end && DatePickerUtil.greaterThanMaxValue(end, max, false))) { + Object.assign(errors, { 'maxValue': true }); } } - return null; + return Object.keys(errors).length > 0 ? errors : null; } /** @hidden @internal */ @@ -656,6 +629,8 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase this.initialSetValue(); this.updateInputs(); }); + this.updateDisplayFormat(); + this.updateInputFormat(); } /** @hidden @internal */ @@ -663,6 +638,12 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase if (changes['locale']) { this.inputFormat = DatePickerUtil.getDefaultInputFormat(this.locale || 'en') || DatePickerUtil.DEFAULT_INPUT_FORMAT; } + if (changes['displayFormat'] && this.hasProjectedInputs) { + this.updateDisplayFormat(); + } + if (changes['inputFormat'] && this.hasProjectedInputs) { + this.updateInputFormat(); + } } /** @hidden @internal */ @@ -980,4 +961,20 @@ export class IgxDateRangePickerComponent extends DisplayDensityBase end.updateInputValue(this.value?.end ?? null); } } + + private updateDisplayFormat(): void { + this.projectedInputs.forEach(i => { + const input = i as IgxDateRangeInputsBaseComponent; + input.dateTimeEditor.displayFormat = this.displayFormat; + }); + } + + private updateInputFormat(): void { + this.projectedInputs.forEach(i => { + const input = i as IgxDateRangeInputsBaseComponent; + if (input.dateTimeEditor.inputFormat !== this.inputFormat) { + input.dateTimeEditor.inputFormat = this.inputFormat; + } + }); + } } diff --git a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts index b48ad97aee6..b7f305f489e 100644 --- a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts @@ -8,6 +8,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxInputGroupModule, IgxInputGroupComponent, IgxInputDirective } from '../../input-group/public_api'; import { configureTestSuite } from '../../test-utils/configure-suite'; +import {ControlsFunction} from '../../test-utils/controls-functions.spec'; import { UIInteractions } from '../../test-utils/ui-interactions.spec'; describe('IgxDateTimeEditor', () => { @@ -331,6 +332,7 @@ describe('IgxDateTimeEditor', () => { }); describe('Integration tests', () => { + const dateTimeOptions = {day: '2-digit', month: '2-digit', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric'}; let fixture; let inputElement: DebugElement; let dateTimeEditorDirective: IgxDateTimeEditorDirective; @@ -351,36 +353,6 @@ describe('IgxDateTimeEditor', () => { inputElement = fixture.debugElement.query(By.css('input')); dateTimeEditorDirective = inputElement.injector.get(IgxDateTimeEditorDirective); }); - // 'dd/MM/yyyy HH:mm:ss'; - function formatFullDateTime(date: Date): string { - const year = `${date.getFullYear()}`.padStart(4, '0'); - const month = `${date.getMonth()}`.padStart(2, '0'); - const day = `${date.getDate()}`.padStart(2, '0'); - const fullDate = [day, month, year].join('/'); - const hours = `${date.getHours()}`.padStart(2, '0'); - const minutes = `${date.getMinutes()}`.padStart(2, '0'); - const seconds = `${date.getSeconds()}`.padStart(2, '0'); - const fullTime = [hours, minutes, seconds].join(':'); - return `${fullDate} ${fullTime}`; - } - function formatFullDate(date: Date): string { - return date.toLocaleString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }); - } - function formatLongDate(date: Date): string { - const result = date.toLocaleString('en-US', { month: 'long', day: 'numeric' }); - const year = date.getFullYear(); - return `${result}, ${year}`; - } - function formatLongTime(date: Date): string { - const result = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true }); - const offset = date.getTimezoneOffset(); - const tz = (offset > 0 ? '-' : '+') + (Math.abs(offset) / 60); - return `${result} GMT${tz}`; - } - function formatShortTime(date: Date): string { - const result = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); - return result; - } it('should correctly display input format during user input', () => { fixture.componentInstance.dateTimeFormat = 'dd/MM/yy'; fixture.detectChanges(); @@ -417,8 +389,8 @@ describe('IgxDateTimeEditor', () => { expect(inputElement.nativeElement.value).toEqual('8_/__/____ __:__:__'); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - const date = new Date(2000, 1, 8, 0, 0, 0); - let result = formatFullDateTime(date); + const date = new Date(2000, 0, 8, 0, 0, 0); + let result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -432,7 +404,7 @@ describe('IgxDateTimeEditor', () => { fixture.detectChanges(); date.setFullYear(2005); date.setDate(1); - result = formatFullDateTime(date); + result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -446,7 +418,7 @@ describe('IgxDateTimeEditor', () => { fixture.detectChanges(); date.setFullYear(2000); date.setHours(3); - result = formatFullDateTime(date); + result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); }); it('should not accept invalid date and time parts.', () => { @@ -497,8 +469,8 @@ describe('IgxDateTimeEditor', () => { expect(inputElement.nativeElement.value).toEqual('__/__/0000 __:__:__'); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - const date = new Date(2000, 1, 1, 0, 0, 0); - let result = formatFullDateTime(date); + const date = new Date(2000, 0, 1, 0, 0, 0); + let result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -511,7 +483,7 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); date.setFullYear(2005); - result = formatFullDateTime(date); + result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -524,7 +496,7 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); date.setFullYear(2016); - result = formatFullDateTime(date); + result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -537,7 +509,7 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); date.setFullYear(169); - result = formatFullDateTime(date); + result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/169,/g, '0169'); expect(inputElement.nativeElement.value).toEqual(result); }); it('should support different display and input formats.', () => { @@ -550,7 +522,8 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); let date = new Date(2000, 0, 9, 0, 0, 0); - let result = formatLongDate(date); + const options = { month: 'long', day: 'numeric' }; + let result = `${ControlsFunction.formatDate(date, options)}, ${date.getFullYear()}`; expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -564,11 +537,9 @@ describe('IgxDateTimeEditor', () => { expect(inputElement.nativeElement.value).toEqual('__/__/169_ __:__:__'); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - date = new Date(169, 1, 1, 0, 0, 0); - const year = date.getFullYear(); - const month = `${date.getMonth()}`.padStart(2, '0'); - const day = `${date.getDate()}`.padStart(2, '0'); - result = [day, month, year].join('/'); + date = new Date(169, 0, 1, 0, 0, 0); + const customOptions = {day: '2-digit', month: '2-digit', year: 'numeric' }; + result = ControlsFunction.formatDate(date, customOptions); expect(inputElement.nativeElement.value).toEqual(result); }); it('should support long and short date formats', () => { @@ -581,7 +552,8 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); let date = new Date(2000, 0, 9, 0, 0, 0); - let result = formatLongDate(date); + const longDateOptions = { month: 'long', day: 'numeric' }; + let result = `${ControlsFunction.formatDate(date, longDateOptions)}, ${date.getFullYear()}`; expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -595,7 +567,8 @@ describe('IgxDateTimeEditor', () => { expect(inputElement.nativeElement.value).toEqual('9_/__/____ __:__:__'); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - result = date.toLocaleDateString('en', { day: 'numeric', month: 'numeric', year: '2-digit' }); + const shortDateOptions = { day: 'numeric', month: 'numeric', year: '2-digit' }; + result = ControlsFunction.formatDate(date, shortDateOptions); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -609,7 +582,8 @@ describe('IgxDateTimeEditor', () => { expect(inputElement.nativeElement.value).toEqual('9_/__/____ __:__:__'); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - result = formatFullDate(date); + const fullDateOptions = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }; + result = ControlsFunction.formatDate(date, fullDateOptions); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -624,7 +598,8 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); date = new Date(0, 0, 0, 1, 0, 0); - result = formatShortTime(date); + const shortTimeOptions = { hour: 'numeric', minute: 'numeric', hour12: true }; + result = ControlsFunction.formatDate(date, shortTimeOptions); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -639,16 +614,20 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); date = new Date(0, 0, 0, 2, 0, 0); - result = formatLongTime(date); + const longTimeOptions = { hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true }; + result = ControlsFunction.formatDate(date, longTimeOptions); + const offset = date.getTimezoneOffset(); + const tz = (offset > 0 ? '-' : '+') + (Math.abs(offset) / 60); + result = `${result} GMT${tz}`; expect(inputElement.nativeElement.value).toEqual(result); }); it('should be able to apply custom display format.', fakeAsync(() => { // default format - const date = new Date(2003, 4, 5, 0, 0, 0); + const date = new Date(2003, 3, 5, 0, 0, 0); fixture.componentInstance.date = new Date(2003, 3, 5, 0, 0, 0); fixture.detectChanges(); tick(); - let result = formatFullDateTime(date); + let result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); // custom format @@ -658,22 +637,22 @@ describe('IgxDateTimeEditor', () => { fixture.detectChanges(); UIInteractions.simulateTyping('1', inputElement); date.setDate(15); - result = formatFullDateTime(date); + result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); date.setMonth(3); - const resultDate = - date.toLocaleString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }) - .replace(/,/g, ''); - result = `${resultDate} ${formatShortTime(date)}`; + const shortTimeOptions = { hour: 'numeric', minute: 'numeric', hour12: true }; + const dateOptions = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }; + const resultDate = ControlsFunction.formatDate(date, dateOptions, 'en-GB').replace(/,/g, ''); + result = `${resultDate} ${ControlsFunction.formatDate(date, shortTimeOptions)}`; expect(inputElement.nativeElement.value).toEqual(result); })); it('should convert dates correctly on paste when different display and input formats are set.', () => { // display format = input format let date = new Date(2020, 10, 10, 10, 10, 10); - let inputDate = formatFullDateTime(date); + let inputDate = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); inputElement.triggerEventHandler('focus', {}); fixture.detectChanges(); UIInteractions.simulatePaste(inputDate, inputElement, 0, 19); @@ -689,8 +668,8 @@ describe('IgxDateTimeEditor', () => { UIInteractions.simulatePaste(inputDate, inputElement, 0, 19); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - let year = (date.getFullYear().toString()).slice(-2); - const result = [date.getDate(), date.getMonth(), year].join('/'); + const shortDateOptions = {day: 'numeric', month: 'numeric', year: '2-digit'}; + const result = ControlsFunction.formatDate(date, shortDateOptions, 'en-GB'); expect(inputElement.nativeElement.value).toEqual(result); inputDate = '6/7/28'; @@ -716,23 +695,23 @@ describe('IgxDateTimeEditor', () => { date = new Date(2028, 7, 16, 0, 0, 0); inputDate = '16/07/28'; - const month = `${date.getMonth()}`.padStart(2, '0'); - year = (date.getFullYear().toString()).slice(-2); - inputDate = [date.getDate(), month, year].join('/'); + const longDateOptions = {day: '2-digit', month: '2-digit', year: '2-digit'}; + inputDate = ControlsFunction.formatDate(date, longDateOptions, 'en-GB'); inputElement.triggerEventHandler('focus', {}); fixture.detectChanges(); UIInteractions.simulatePaste(inputDate, inputElement, 0, 19); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - inputDate = [date.getDate(), month, date.getFullYear()].join('/'); + longDateOptions.year = 'numeric'; + inputDate = ControlsFunction.formatDate(date, longDateOptions, 'en-GB'); expect(inputElement.nativeElement.value).toEqual(inputDate); }); it('should clear input date on clear()', fakeAsync(() => { - const date = new Date(2003, 4, 5); - fixture.componentInstance.date = new Date(2003, 3, 5); + const date = new Date(2003, 3, 5); + fixture.componentInstance.date = date; fixture.detectChanges(); tick(); - const result = formatFullDateTime(date); + const result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); dateTimeEditorDirective.clear(); @@ -740,10 +719,10 @@ describe('IgxDateTimeEditor', () => { })); it('should move the caret to the start/end of the portion with CTRL + arrow left/right keys.', fakeAsync(() => { const date = new Date(2003, 4, 5); - fixture.componentInstance.date = new Date(2003, 3, 5); + fixture.componentInstance.date = date; fixture.detectChanges(); tick(); - const result = formatFullDateTime(date); + const result = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(result); const inputHTMLElement = inputElement.nativeElement as HTMLInputElement; @@ -812,7 +791,7 @@ describe('IgxDateTimeEditor', () => { fixture.detectChanges(); let date = new Date(2009, 10, 10, 10, 10, 10); - let inputDate = formatFullDateTime(date); + let inputDate = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); inputElement.triggerEventHandler('focus', {}); fixture.detectChanges(); UIInteractions.simulatePaste(inputDate, inputElement, 0, 19); @@ -828,8 +807,8 @@ describe('IgxDateTimeEditor', () => { UIInteractions.simulateTyping('27', inputElement, 8, 8); inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - date = new Date(2027, 1, 1, 0, 0, 0); - inputDate = formatFullDateTime(date); + date = new Date(2027, 0, 1, 0, 0, 0); + inputDate = ControlsFunction.formatDate(date, dateTimeOptions, 'en-GB').replace(/,/g, ''); expect(inputElement.nativeElement.value).toEqual(inputDate); }); it('should be able to customize prompt char.', () => { @@ -865,8 +844,8 @@ describe('IgxDateTimeEditor', () => { inputElement.triggerEventHandler('blur', { target: inputElement.nativeElement }); fixture.detectChanges(); - const year = (newDate.getFullYear().toString()).slice(-2); - const result = [newDate.getDate(), newDate.getMonth() + 1, year].join('/'); + const options = {day: '2-digit', month: '2-digit', year: '2-digit'}; + const result = ControlsFunction.formatDate(newDate, options, 'en-GB'); expect(inputElement.nativeElement.value).toEqual(result); expect(dateTimeEditorDirective.valueChange.emit).toHaveBeenCalledTimes(1); expect(dateTimeEditorDirective.valueChange.emit).toHaveBeenCalledWith(newDate); diff --git a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts index 6dbbd5ff7da..99f96640f82 100644 --- a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts @@ -1,12 +1,12 @@ import { Directive, Input, ElementRef, - Renderer2, NgModule, Output, EventEmitter, Inject, LOCALE_ID, OnChanges, SimpleChanges + Renderer2, NgModule, Output, EventEmitter, Inject, LOCALE_ID, OnChanges, SimpleChanges, DoCheck } from '@angular/core'; import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NG_VALIDATORS, NG_VALUE_ACCESSOR, } from '@angular/forms'; -import { formatDate, DOCUMENT } from '@angular/common'; +import { DOCUMENT } from '@angular/common'; import { IgxMaskDirective } from '../mask/mask.directive'; import { MaskParsingService } from '../mask/mask-parsing.service'; import { KEYS } from '../../core/utils'; @@ -51,12 +51,15 @@ import { IgxDateTimeEditorEventArgs, DatePartInfo, DatePart } from './date-time- { provide: NG_VALIDATORS, useExisting: IgxDateTimeEditorDirective, multi: true } ] }) -export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnChanges, Validator, ControlValueAccessor { +export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnChanges, DoCheck, Validator, ControlValueAccessor { /** * Locale settings used for value formatting. * * @remarks * Uses Angular's `LOCALE_ID` by default. Affects both input mask and display format if those are not set. + * If a `locale` is set, it must be registered via `registerLocaleData`. + * Please refer to https://angular.io/guide/i18n#i18n-pipes. + * If it is not registered, `Intl` will be used for formatting. * * @example * ```html @@ -192,6 +195,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnCh private _format: string; private document: Document; private _isFocused: boolean; + private _inputFormat: string; private _minValue: string | Date; private _maxValue: string | Date; private _oldValue: Date | string; @@ -245,14 +249,14 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnCh /** @hidden @internal */ public ngOnChanges(changes: SimpleChanges) { if (changes['inputFormat'] || changes['locale']) { - const defPlaceholder = this.inputFormat || DatePickerUtil.getDefaultInputFormat(this.locale); - this._inputDateParts = DatePickerUtil.parseDateTimeFormat(this.inputFormat); - this.inputFormat = this._inputDateParts.map(p => p.format).join(''); - if (!this.nativeElement.placeholder) { - this.renderer.setAttribute(this.nativeElement, 'placeholder', defPlaceholder); - } - // TODO: fill in partial dates? - this.updateMask(); + this.updateInputFormat(); + } + } + + /** @hidden @internal */ + public ngDoCheck(): void { + if (this._inputFormat !== this.inputFormat) { + this.updateInputFormat(); } } @@ -402,7 +406,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnCh } const format = this.displayFormat || this.inputFormat; if (format) { - this.inputValue = formatDate(this.value, format.replace('tt', 'aa'), this.locale); + this.inputValue = DatePickerUtil.formatDate(this.value, format.replace('tt', 'aa'), this.locale); } else { // TODO: formatter function? this.inputValue = this.value.toLocaleString(); @@ -421,6 +425,18 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnCh return mask; } + private updateInputFormat(): void { + const defPlaceholder = this.inputFormat || DatePickerUtil.getDefaultInputFormat(this.locale); + this._inputDateParts = DatePickerUtil.parseDateTimeFormat(this.inputFormat); + this.inputFormat = this._inputDateParts.map(p => p.format).join(''); + if (!this.nativeElement.placeholder || this._inputFormat !== this.inputFormat) { + this.renderer.setAttribute(this.nativeElement, 'placeholder', defPlaceholder); + } + // TODO: fill in partial dates? + this.updateMask(); + this._inputFormat = this.inputFormat; + } + // TODO: move isDate to utils private isDate(value: any): value is Date { return value instanceof Date && typeof value === 'object'; diff --git a/projects/igniteui-angular/src/lib/test-utils/controls-functions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/controls-functions.spec.ts index 3121d312686..b61279c3f66 100644 --- a/projects/igniteui-angular/src/lib/test-utils/controls-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/controls-functions.spec.ts @@ -17,68 +17,79 @@ export const BUTTON_DISABLED_CLASS = 'igx-button--disabled'; export class ControlsFunction { public static getChipRemoveButton(chip: HTMLElement): HTMLElement { - return chip.querySelector(CHIP_REMOVE_BUTTON); + return chip.querySelector(CHIP_REMOVE_BUTTON); } public static clickChipRemoveButton(chip: HTMLElement) { const removeButton = ControlsFunction.getChipRemoveButton(chip); removeButton.click(); - } + } - public static getDropDownSelectedItem(element: DebugElement): DebugElement { + public static getDropDownSelectedItem(element: DebugElement): DebugElement { return element.query(By.css(DROP_DOWN_SELECTED_ITEM_CLASS)); - } - - public static clickDropDownItem(fix: ComponentFixture, index: number) { - const list: HTMLElement = fix.nativeElement.querySelector(DROP_DOWN_SCROLL_CLASS); - const items = list.querySelectorAll(DROP_DOWN__ITEM_CLASS); - const item = items.item(index); - UIInteractions.simulateClickEvent(item); - fix.detectChanges(); - } - - public static verifyButtonIsSelected(element: HTMLElement, selected: boolean = true) { - expect(element.classList.contains(BUTTON_SELECTED_CLASS)).toEqual(selected); - } - - public static verifyButtonIsDisabled(element: HTMLElement, disabled: boolean = true) { - expect(element.classList.contains(BUTTON_DISABLED_CLASS)).toEqual(disabled); - } - - public static verifyCheckboxState(element: HTMLElement, checked: boolean = true, indeterminate = false) { - expect(element.classList.contains(CHECKBOX_CHECKED_CLASS)).toEqual(checked); - expect(element.classList.contains(CHECKBOX_IND_CLASS)).toEqual(indeterminate); - } - - public static getCheckboxElement(name: string, element: DebugElement, fix) { - const checkboxElements = element.queryAll(By.css('igx-checkbox')); - const chkElement = checkboxElements.find((el) => - (el.context as IgxCheckboxComponent).placeholderLabel.nativeElement.innerText === name); - - return chkElement; - } - - public static getCheckboxInput(name: string, element: DebugElement, fix) { - const checkboxEl = ControlsFunction.getCheckboxElement(name, element, fix); - const chkInput = checkboxEl.query(By.css('input')).nativeElement as HTMLInputElement; - - return chkInput; - } - - public static getCheckboxInputs(element: DebugElement): HTMLInputElement[] { - const checkboxElements = element.queryAll(By.css('igx-checkbox')); - const inputs = []; - checkboxElements.forEach((el) => { - inputs.push(el.query(By.css('input')).nativeElement as HTMLInputElement); - }); - - return inputs; - } - - public static verifyCheckbox(name: string, isChecked: boolean, isDisabled: boolean, element: DebugElement, fix) { - const chkInput = ControlsFunction.getCheckboxInput(name, element, fix); - expect(chkInput.type).toBe('checkbox'); - expect(chkInput.disabled).toBe(isDisabled); - expect(chkInput.checked).toBe(isChecked); - } + } + + public static clickDropDownItem(fix: ComponentFixture, index: number) { + const list: HTMLElement = fix.nativeElement.querySelector(DROP_DOWN_SCROLL_CLASS); + const items = list.querySelectorAll(DROP_DOWN__ITEM_CLASS); + const item = items.item(index); + UIInteractions.simulateClickEvent(item); + fix.detectChanges(); + } + + public static verifyButtonIsSelected(element: HTMLElement, selected: boolean = true) { + expect(element.classList.contains(BUTTON_SELECTED_CLASS)).toEqual(selected); + } + + public static verifyButtonIsDisabled(element: HTMLElement, disabled: boolean = true) { + expect(element.classList.contains(BUTTON_DISABLED_CLASS)).toEqual(disabled); + } + + public static verifyCheckboxState(element: HTMLElement, checked: boolean = true, indeterminate = false) { + expect(element.classList.contains(CHECKBOX_CHECKED_CLASS)).toEqual(checked); + expect(element.classList.contains(CHECKBOX_IND_CLASS)).toEqual(indeterminate); + } + + public static getCheckboxElement(name: string, element: DebugElement, fix) { + const checkboxElements = element.queryAll(By.css('igx-checkbox')); + const chkElement = checkboxElements.find((el) => + (el.context as IgxCheckboxComponent).placeholderLabel.nativeElement.innerText === name); + + return chkElement; + } + + public static getCheckboxInput(name: string, element: DebugElement, fix) { + const checkboxEl = ControlsFunction.getCheckboxElement(name, element, fix); + const chkInput = checkboxEl.query(By.css('input')).nativeElement as HTMLInputElement; + + return chkInput; + } + + public static getCheckboxInputs(element: DebugElement): HTMLInputElement[] { + const checkboxElements = element.queryAll(By.css('igx-checkbox')); + const inputs = []; + checkboxElements.forEach((el) => { + inputs.push(el.query(By.css('input')).nativeElement as HTMLInputElement); + }); + + return inputs; + } + + public static verifyCheckbox(name: string, isChecked: boolean, isDisabled: boolean, element: DebugElement, fix) { + const chkInput = ControlsFunction.getCheckboxInput(name, element, fix); + expect(chkInput.type).toBe('checkbox'); + expect(chkInput.disabled).toBe(isDisabled); + expect(chkInput.checked).toBe(isChecked); + } + + /** + * Formats a date according to the provided formatting options + * @param date Date to be formatted + * @param formatOptions DateTime formatting options + * @param locale Date language + */ + public static formatDate(date: Date, formatOptions: object, locale = 'en-US'): string { + const dateFormatter = new Intl.DateTimeFormat(locale, formatOptions); + return `${dateFormatter.format(date)}`; + } }