UNPKG

igniteui-angular-sovn

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

944 lines (821 loc) 85.2 kB
import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync, flush } from '@angular/core/testing'; import { Component, OnInit, ViewChild, DebugElement, ChangeDetectionStrategy } from '@angular/core'; import { IgxInputDirective, IgxInputState, IgxLabelDirective, IgxPrefixDirective, IgxSuffixDirective } from '../input-group/public_api'; import { PickerInteractionMode } from '../date-common/types'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, Validators } from '@angular/forms'; 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 '../test-utils/calendar-helper-utils'; import { CancelableEventArgs } from '../core/utils'; import { DateRange, IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent } from './date-range-picker-inputs.common'; import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api'; import { DateRangeType } from '../core/dates'; import { IgxDateRangePickerComponent, IgxDateRangeEndComponent } from './public_api'; import { AutoPositionStrategy, IgxOverlayService } from '../services/public_api'; import { AnimationMetadata, AnimationOptions } from '@angular/animations'; import { IgxCalendarContainerComponent } from '../date-common/calendar-container/calendar-container.component'; import { IgxCalendarComponent, WEEKDAYS } from '../calendar/public_api'; import { Subject } from 'rxjs'; import { AsyncPipe } from '@angular/common'; import { AnimationService } from '../services/animation/animation'; import { IgxAngularAnimationService } from '../services/animation/angular-animation-service'; import { IgxPickerToggleComponent } from '../date-common/picker-icons.common'; import { IgxIconComponent } from '../icon/icon.component'; // The number of milliseconds in one day const DEBOUNCE_TIME = 16; const DEFAULT_ICON_TEXT = 'date_range'; const DEFAULT_FORMAT_OPTIONS = { day: '2-digit', month: '2-digit', year: 'numeric' }; const CSS_CLASS_INPUT_BUNDLE = '.igx-input-group__bundle'; const CSS_CLASS_INPUT_START = '.igx-input-group__bundle-start' const CSS_CLASS_INPUT_END = '.igx-input-group__bundle-end' const CSS_CLASS_INPUT = '.igx-input-group__input'; const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required'; const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid'; const CSS_CLASS_CALENDAR = 'igx-calendar'; const CSS_CLASS_ICON = 'igx-icon'; const CSS_CLASS_DONE_BUTTON = 'igx-button--flat'; const CSS_CLASS_LABEL = 'igx-input-group__label'; const CSS_CLASS_OVERLAY_CONTENT = 'igx-overlay__content'; const CSS_CLASS_DATE_RANGE = 'igx-date-range-picker'; const CSS_CLASS_CALENDAR_DATE = 'igx-calendar__date'; const CSS_CLASS_INACTIVE_DATE = 'igx-calendar__date--inactive'; describe('IgxDateRangePicker', () => { describe('Unit tests: ', () => { let mockElement: any; let mockElementRef: any; let mockFactoryResolver: any; let mockApplicationRef: any; let mockAnimationBuilder: any; let mockDocument: any; let mockNgZone: any; let mockPlatformUtil: any; let overlay: IgxOverlayService; let mockInjector; let mockCalendar: IgxCalendarComponent; let mockDaysView: any; let mockAnimationService: AnimationService; const elementRef = { nativeElement: null }; const platform = {} as any; const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb', 'registerOnValidatorChangeCb']); /* eslint-disable @typescript-eslint/no-unused-vars */ beforeEach(() => { mockFactoryResolver = { resolveComponentFactory: (c: any) => ({ create: (i: any) => ({ hostView: '', location: mockElementRef, changeDetectorRef: { detectChanges: () => { } }, destroy: () => { }, instance: new IgxCalendarContainerComponent() }) }) }; mockElement = { style: { visibility: '', cursor: '', transitionDuration: '' }, classList: { add: () => { }, remove: () => { } }, appendChild: () => { }, removeChild: () => { }, addEventListener: (type: string, listener: (this: HTMLElement, ev: MouseEvent) => any) => { }, removeEventListener: (type: string, listener: (this: HTMLElement, ev: MouseEvent) => any) => { }, getBoundingClientRect: () => ({ width: 10, height: 10 }), insertBefore: (newChild: HTMLDivElement, refChild: Node) => { }, contains: () => { } }; mockElement.parent = mockElement; mockElement.parentElement = mockElement; mockElementRef = { nativeElement: mockElement }; // eslint-disable-next-line @typescript-eslint/no-unused-vars mockApplicationRef = { attachView: (h: any) => { }, detachView: (h: any) => { } }; mockInjector = jasmine.createSpyObj('Injector', { get: mockNgControl }); mockAnimationBuilder = { // eslint-disable-next-line @typescript-eslint/no-unused-vars build: (a: AnimationMetadata | AnimationMetadata[]) => ({ // eslint-disable-next-line @typescript-eslint/no-unused-vars create: (e: any, opt?: AnimationOptions) => ({ onDone: (fn: any) => { }, onStart: (fn: any) => { }, onDestroy: (fn: any) => { }, init: () => { }, hasStarted: () => true, play: () => { }, pause: () => { }, restart: () => { }, finish: () => { }, destroy: () => { }, rest: () => { }, setPosition: (p: any) => { }, getPosition: () => 0, parentPlayer: {}, totalTime: 0, beforeDestroy: () => { }, _renderer: { engine: { players: [ {} ] } } }) }) }; mockDocument = { body: mockElement, defaultView: mockElement, createElement: () => mockElement, appendChild: () => { }, // eslint-disable-next-line @typescript-eslint/no-unused-vars addEventListener: (type: string, listener: (this: HTMLElement, ev: MouseEvent) => any) => { }, // eslint-disable-next-line @typescript-eslint/no-unused-vars removeEventListener: (type: string, listener: (this: HTMLElement, ev: MouseEvent) => any) => { } }; mockNgZone = {}; mockPlatformUtil = { isIOS: false }; mockAnimationService = new IgxAngularAnimationService(mockAnimationBuilder); overlay = new IgxOverlayService( mockFactoryResolver, mockApplicationRef, mockInjector, mockDocument, mockNgZone, mockPlatformUtil, mockAnimationService); mockCalendar = new IgxCalendarComponent(platform, 'en'); mockDaysView = { focusActiveDate: jasmine.createSpy() } as any; mockCalendar.daysView = mockDaysView; }); /* eslint-enable @typescript-eslint/no-unused-vars */ it('should set range dates correctly through selectRange method', () => { const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, null, null, null, null); // dateRange.calendar = calendar; let startDate = new Date(2020, 3, 7); const endDate = new Date(2020, 6, 27); // select range dateRange.select(startDate, endDate); expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(endDate); // select startDate only startDate = new Date(2023, 2, 11); dateRange.select(startDate); expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(startDate); }); it('should emit valueChange on selection', () => { const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, null, null, null, null); // dateRange.calendar = calendar; spyOn(dateRange.valueChange, 'emit'); let startDate = new Date(2017, 4, 5); const endDate = new Date(2017, 11, 22); // select range dateRange.select(startDate, endDate); expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(endDate); expect(dateRange.valueChange.emit).toHaveBeenCalledTimes(1); expect(dateRange.valueChange.emit).toHaveBeenCalledWith({ start: startDate, end: endDate }); // select startDate only startDate = new Date(2024, 12, 15); dateRange.select(startDate); expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(startDate); expect(dateRange.valueChange.emit).toHaveBeenCalledTimes(2); expect(dateRange.valueChange.emit).toHaveBeenCalledWith({ start: startDate, end: startDate }); }); 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) }; // init const dateRangePicker = new IgxDateRangePickerComponent(null, 'en', platform, null, null, null, null); dateRangePicker.registerOnChange(mockNgControl.registerOnChangeCb); dateRangePicker.registerOnTouched(mockNgControl.registerOnTouchedCb); spyOn(dateRangePicker as any, 'handleSelection').and.callThrough(); // writeValue expect(dateRangePicker.value).toBeUndefined(); expect(mockNgControl.registerOnChangeCb).not.toHaveBeenCalled(); dateRangePicker.writeValue(range); expect(dateRangePicker.value).toBe(range); // set value & handleSelection call _onChangeCallback dateRangePicker.value = rangeUpdate; expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith(rangeUpdate); (dateRangePicker as any).handleSelection([range.start]); expect((dateRangePicker as any).handleSelection).toHaveBeenCalledWith([range.start]); expect((dateRangePicker as any).handleSelection).toHaveBeenCalledTimes(1); expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith({ start: range.start, end: range.start }); // 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 as any).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, 'en-US', platform, mockInjector, null, null, null); 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 UntypedFormControl(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', () => { const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, mockInjector, null, overlay); dateRange.ngOnInit(); spyOnProperty((dateRange as any), 'calendar').and.returnValue(mockCalendar); dateRange.minValue = new Date(2000, 10, 1); dateRange.maxValue = new Date(2000, 10, 20); dateRange.open({ closeOnOutsideClick: true, modal: false, target: dateRange.element.nativeElement, positionStrategy: new AutoPositionStrategy({ openAnimation: null, closeAnimation: null }) }); (dateRange as any).updateCalendar(); expect(mockCalendar.disabledDates.length).toEqual(2); expect(mockCalendar.disabledDates[0].type).toEqual(DateRangeType.Before); expect(mockCalendar.disabledDates[0].dateRange[0]).toEqual(dateRange.minValue); expect(mockCalendar.disabledDates[1].type).toEqual(DateRangeType.After); expect(mockCalendar.disabledDates[1].dateRange[0]).toEqual(dateRange.maxValue); expect(mockCalendar.daysView.focusActiveDate).toHaveBeenCalledTimes(1); }); it('should disable calendar dates when min and/or max values as strings are provided', fakeAsync(() => { const dateRange = new IgxDateRangePickerComponent(elementRef, 'en', platform, mockInjector, null, null, null); dateRange.ngOnInit(); spyOnProperty((dateRange as any), 'calendar').and.returnValue(mockCalendar); dateRange.minValue = '2000/10/1'; dateRange.maxValue = '2000/10/30'; spyOn((dateRange as any).calendar, 'deselectDate').and.returnValue(null); (dateRange as any).updateCalendar(); expect((dateRange as any).calendar.disabledDates.length).toEqual(2); expect((dateRange as any).calendar.disabledDates[0].type).toEqual(DateRangeType.Before); expect((dateRange as any).calendar.disabledDates[0].dateRange[0]).toEqual(new Date(dateRange.minValue)); expect((dateRange as any).calendar.disabledDates[1].type).toEqual(DateRangeType.After); expect((dateRange as any).calendar.disabledDates[1].dateRange[0]).toEqual(new Date(dateRange.maxValue)); })); }); describe('Integration tests', () => { let fixture: ComponentFixture<DateRangeTestComponent>; let dateRange: IgxDateRangePickerComponent; let startDate: Date; let endDate: Date; let calendar: DebugElement | Element; let calendarDays: DebugElement[] | HTMLCollectionOf<Element>; const selectDateRangeFromCalendar = (sDate: Date, eDate: Date) => { dateRange.open(); fixture.detectChanges(); calendarDays = document.getElementsByClassName(CSS_CLASS_CALENDAR_DATE); const nodesArray = Array.from(calendarDays); const findNodeIndex: (d: Date) => number = (d: Date) => nodesArray .findIndex( n => n.attributes['aria-label'].value === d.toDateString() && !n.classList.contains(CSS_CLASS_INACTIVE_DATE) ); const startIndex = findNodeIndex(sDate); const endIndex = findNodeIndex(eDate); if (startIndex === -1) { throw new Error('Start date not found in calendar. Aborting.'); } UIInteractions.simulateClickAndSelectEvent(calendarDays[startIndex]); if (endIndex !== -1 && endIndex !== startIndex) { // do not click same date twice UIInteractions.simulateClickAndSelectEvent(calendarDays[endIndex]); } fixture.detectChanges(); dateRange.close(); fixture.detectChanges(); }; describe('Single Input', () => { let singleInputElement: DebugElement; configureTestSuite(); beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, ReactiveFormsModule, DateRangeDefaultComponent, DateRangeDisabledComponent, DateRangeReactiveFormComponent ] }).compileComponents(); })); beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(DateRangeDefaultComponent); fixture.detectChanges(); dateRange = fixture.componentInstance.dateRange; singleInputElement = fixture.debugElement.query(By.css(CSS_CLASS_INPUT)); })); const verifyDateRangeInSingleInput = () => { expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(endDate); const inputStartDate = [startDate.getMonth() + 1, startDate.getDate(), startDate.getFullYear()].join('/'); const inputEndDate = endDate ? [endDate.getMonth() + 1, endDate.getDate(), endDate.getFullYear()].join('/') : ''; expect(singleInputElement.nativeElement.value).toEqual(`${inputStartDate} - ${inputEndDate}`); }; describe('Selection tests', () => { it('should assign range dates to the input when selecting a range from the calendar', () => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); const dayRange = 15; const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), 10, 0, 0, 0); endDate = new Date(startDate); endDate.setDate(endDate.getDate() + dayRange); selectDateRangeFromCalendar(startDate, endDate); verifyDateRangeInSingleInput(); }); it('should assign range values correctly when selecting dates in reversed order', () => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), 5, 0, 0, 0); endDate = new Date(today.getFullYear(), today.getMonth(), 10, 0, 0, 0); selectDateRangeFromCalendar(endDate, startDate); verifyDateRangeInSingleInput(); }); it('should set start and end dates on single date selection', () => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), 10, 0, 0, 0); endDate = startDate; selectDateRangeFromCalendar(startDate, endDate); verifyDateRangeInSingleInput(); }); 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); selectDateRangeFromCalendar(startDate, endDate); verifyDateRangeInSingleInput(); }); 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.select(startDate, endDate); fixture.detectChanges(); verifyDateRangeInSingleInput(); startDate = new Date(2006, 5, 18, 0, 0, 0); endDate = new Date(2006, 8, 18, 0, 0, 0); dateRange.select(startDate, endDate); fixture.detectChanges(); verifyDateRangeInSingleInput(); }); }); describe('Properties & events tests', () => { it('should display placeholder', () => { fixture.detectChanges(); expect(singleInputElement.nativeElement.placeholder).toEqual('MM/dd/yyyy - MM/dd/yyyy'); const placeholder = 'Some placeholder'; fixture.componentInstance.dateRange.placeholder = placeholder; fixture.detectChanges(); 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.select(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.select(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.select(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.select(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 = PickerInteractionMode.Dialog; fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); spyOn(dateRange.closing, 'emit').and.callThrough(); spyOn(dateRange.closed, 'emit').and.callThrough(); dateRange.open(); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeFalsy(); const doneBtn = document.getElementsByClassName(CSS_CLASS_DONE_BUTTON)[0]; UIInteractions.simulateClickAndSelectEvent(doneBtn); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeTrue(); expect(dateRange.closing.emit).toHaveBeenCalledTimes(1); expect(dateRange.closing.emit).toHaveBeenCalledWith({ owner: dateRange, cancel: false, event: undefined }); expect(dateRange.closed.emit).toHaveBeenCalledTimes(1); expect(dateRange.closed.emit).toHaveBeenCalledWith({ owner: dateRange }); })); it('should show the "Done" button only in dialog mode', fakeAsync(() => { fixture.componentInstance.mode = PickerInteractionMode.Dialog; fixture.detectChanges(); dateRange.open(); fixture.detectChanges(); let doneBtn = document.getElementsByClassName(CSS_CLASS_DONE_BUTTON)[0]; expect(doneBtn).not.toBe(null); dateRange.close(); tick(); fixture.detectChanges(); fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.detectChanges(); dateRange.open(); tick(); fixture.detectChanges(); doneBtn = document.getElementsByClassName(CSS_CLASS_DONE_BUTTON)[0]; expect(doneBtn).not.toBeDefined(); })); it('should be able to change the "Done" button text', fakeAsync(() => { fixture.componentInstance.mode = PickerInteractionMode.Dialog; fixture.detectChanges(); dateRange.toggle(); fixture.detectChanges(); let doneBtn = document.getElementsByClassName(CSS_CLASS_DONE_BUTTON)[0]; expect(doneBtn.textContent.trim()).toEqual('Done'); dateRange.toggle(); tick(); fixture.detectChanges(); dateRange.doneButtonText = 'Close'; fixture.detectChanges(); dateRange.toggle(); tick(); fixture.detectChanges(); doneBtn = document.getElementsByClassName(CSS_CLASS_DONE_BUTTON)[0]; expect(doneBtn.textContent.trim()).toEqual('Close'); })); it('should emit open/close events - open/close methods', fakeAsync(() => { fixture.componentInstance.dateRange.displayFormat = 'M/d/yyyy'; fixture.detectChanges(); spyOn(dateRange.opening, 'emit').and.callThrough(); spyOn(dateRange.opened, 'emit').and.callThrough(); spyOn(dateRange.closing, 'emit').and.callThrough(); spyOn(dateRange.closed, 'emit').and.callThrough(); dateRange.open(); tick(DEBOUNCE_TIME); fixture.detectChanges(); expect(dateRange.collapsed).toBeFalsy(); expect(dateRange.opening.emit).toHaveBeenCalledTimes(1); expect(dateRange.opening.emit).toHaveBeenCalledWith({ owner: dateRange, cancel: false, event: undefined }); expect(dateRange.opened.emit).toHaveBeenCalledTimes(1); expect(dateRange.opened.emit).toHaveBeenCalledWith({ owner: dateRange }); dateRange.close(); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeTruthy(); expect(dateRange.closing.emit).toHaveBeenCalledTimes(1); expect(dateRange.closing.emit).toHaveBeenCalledWith({ owner: dateRange, cancel: false, event: undefined }); expect(dateRange.closed.emit).toHaveBeenCalledTimes(1); expect(dateRange.closed.emit).toHaveBeenCalledWith({ owner: dateRange }); })); it('should emit open/close events - toggle method', fakeAsync(() => { spyOn(dateRange.opening, 'emit').and.callThrough(); spyOn(dateRange.opened, 'emit').and.callThrough(); spyOn(dateRange.closing, 'emit').and.callThrough(); spyOn(dateRange.closed, 'emit').and.callThrough(); dateRange.toggle(); tick(DEBOUNCE_TIME); fixture.detectChanges(); expect(dateRange.collapsed).toBeFalsy(); expect(dateRange.opening.emit).toHaveBeenCalledTimes(1); expect(dateRange.opening.emit).toHaveBeenCalledWith({ owner: dateRange, cancel: false, event: undefined }); expect(dateRange.opened.emit).toHaveBeenCalledTimes(1); expect(dateRange.opened.emit).toHaveBeenCalledWith({ owner: dateRange }); dateRange.toggle(); tick(); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeTruthy(); expect(dateRange.closing.emit).toHaveBeenCalledTimes(1); expect(dateRange.closing.emit).toHaveBeenCalledWith({ owner: dateRange, cancel: false, event: undefined }); expect(dateRange.closed.emit).toHaveBeenCalledTimes(1); expect(dateRange.closed.emit).toHaveBeenCalledWith({ owner: dateRange }); })); it('should not close calendar if closing event is canceled', fakeAsync(() => { spyOn(dateRange.closing, 'emit').and.callThrough(); spyOn(dateRange.closed, 'emit').and.callThrough(); dateRange.closing.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.select(startDate, endDate); dateRange.close(); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeFalsy(); expect(dateRange.closing.emit).toHaveBeenCalled(); expect(dateRange.closed.emit).not.toHaveBeenCalled(); })); }); describe('Keyboard navigation', () => { it('should toggle the calendar with ALT + DOWN/UP ARROW key', fakeAsync(() => { spyOn(dateRange.opening, 'emit').and.callThrough(); spyOn(dateRange.opened, 'emit').and.callThrough(); spyOn(dateRange.closing, 'emit').and.callThrough(); spyOn(dateRange.closed, 'emit').and.callThrough(); expect(dateRange.collapsed).toBeTruthy(); const range = fixture.debugElement.query(By.css(CSS_CLASS_DATE_RANGE)); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', range, true); tick(DEBOUNCE_TIME * 2); fixture.detectChanges(); expect(dateRange.collapsed).toBeFalsy(); expect(dateRange.opening.emit).toHaveBeenCalledTimes(1); expect(dateRange.opened.emit).toHaveBeenCalledTimes(1); calendar = document.getElementsByClassName(CSS_CLASS_CALENDAR)[0]; UIInteractions.triggerKeyDownEvtUponElem('ArrowUp', calendar, true, true); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeTruthy(); expect(dateRange.closing.emit).toHaveBeenCalledTimes(1); expect(dateRange.closed.emit).toHaveBeenCalledTimes(1); })); it('should close the calendar with ESC', fakeAsync(() => { spyOn(dateRange.closing, 'emit').and.callThrough(); spyOn(dateRange.closed, 'emit').and.callThrough(); expect(dateRange.collapsed).toBeTruthy(); dateRange.open(); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeFalsy(); calendar = document.getElementsByClassName(CSS_CLASS_CALENDAR)[0]; UIInteractions.triggerKeyDownEvtUponElem('Escape', calendar, true); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeTruthy(); expect(dateRange.closing.emit).toHaveBeenCalledTimes(1); expect(dateRange.closed.emit).toHaveBeenCalledTimes(1); })); it('should not open calendar with ALT + DOWN ARROW key if disabled is set to true', fakeAsync(() => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.componentInstance.disabled = true; fixture.detectChanges(); spyOn(dateRange.opening, 'emit').and.callThrough(); spyOn(dateRange.opened, 'emit').and.callThrough(); const input = document.getElementsByClassName('igx-input-group__input')[0]; UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', input, true, true); tick(); fixture.detectChanges(); expect(dateRange.collapsed).toBeTruthy(); expect(dateRange.opening.emit).toHaveBeenCalledTimes(0); expect(dateRange.opened.emit).toHaveBeenCalledTimes(0); })); }); it('should expand the calendar if the default icon is clicked', fakeAsync(() => { const input = fixture.debugElement.query(By.css('igx-input-group')); input.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); tick(); fixture.detectChanges(); expect(fixture.componentInstance.dateRange.collapsed).toBeFalsy(); })); it('should not expand the calendar if the default icon is clicked when disabled is set to true', fakeAsync(() => { fixture.componentInstance.disabled = true; fixture.detectChanges(); const input = fixture.debugElement.query(By.css('igx-input-group')); input.triggerEventHandler('click', UIInteractions.getMouseEvent('click')); tick(); fixture.detectChanges(); expect(fixture.componentInstance.dateRange.collapsed).toBeTruthy(); })); it('should properly set/update disabled when ChangeDetectionStrategy.OnPush is used', fakeAsync(() => { const testFixture = TestBed .createComponent(DateRangeDisabledComponent) as ComponentFixture<DateRangeDisabledComponent>; testFixture.detectChanges(); dateRange = testFixture.componentInstance.dateRange; const disabled$ = testFixture.componentInstance.disabled$; disabled$.next(true); testFixture.detectChanges(); expect(dateRange.inputDirective.disabled).toBeTrue(); disabled$.next(false); testFixture.detectChanges(); expect(dateRange.inputDirective.disabled).toBeFalse(); disabled$.next(true); testFixture.detectChanges(); expect(dateRange.inputDirective.disabled).toBeTrue(); disabled$.complete(); })); it('should update the calendar while it\'s open and the value has been updated', fakeAsync(() => { dateRange.open(); tick(); fixture.detectChanges(); const range = { start: new Date(), end: new Date(new Date().setDate(new Date().getDate() + 1)) }; dateRange.value = range; fixture.detectChanges(); expect((dateRange as any).calendar.selectedDates.length).toBeGreaterThan(0); // clean up test tick(350); })); it('should set initial validity state when the form group is disabled', () => { const fix = TestBed.createComponent(DateRangeReactiveFormComponent); fix.detectChanges(); const dateRangePicker = fix.componentInstance.dateRange; fix.componentInstance.markAsTouched(); fix.detectChanges(); expect(dateRangePicker.inputDirective.valid).toBe(IgxInputState.INVALID); fix.componentInstance.disableForm(); fix.detectChanges(); expect(dateRangePicker.inputDirective.valid).toBe(IgxInputState.INITIAL); }); it('should update validity state when programmatically setting errors on reactive form controls', fakeAsync(() => { const fix = TestBed.createComponent(DateRangeReactiveFormComponent); tick(500); fix.detectChanges(); const dateRangePicker = fix.componentInstance.dateRange; const form = fix.componentInstance.form; // the form control has validators form.markAllAsTouched(); form.get('range').setErrors({ error: true }); tick(); fix.detectChanges(); expect((dateRangePicker as any).inputDirective.valid).toBe(IgxInputState.INVALID); expect((dateRangePicker as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_INVALID)).toBe(true); expect((dateRangePicker as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(true); expect((dateRangePicker as any).required).toBe(true); // remove the validators and set errors form.controls['range'].clearValidators(); form.controls['range'].updateValueAndValidity(); form.markAllAsTouched(); form.get('range').setErrors({ error: true }); tick(500); fix.detectChanges(); tick(); expect((dateRangePicker as any).inputDirective.valid).toBe(IgxInputState.INVALID); expect((dateRangePicker as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_INVALID)).toBe(true); expect((dateRangePicker as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false); })); }); describe('Two Inputs', () => { let startInput: DebugElement; let endInput: DebugElement; configureTestSuite(); beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, ReactiveFormsModule, DateRangeTwoInputsTestComponent, DateRangeTwoInputsNgModelTestComponent, DateRangeDisabledComponent, DateRangeTwoInputsDisabledComponent, DateRangeReactiveFormComponent ] }).compileComponents(); })); beforeEach(async () => { fixture = TestBed.createComponent(DateRangeTwoInputsTestComponent); fixture.detectChanges(); dateRange = fixture.componentInstance.dateRange; dateRange.value = { start: new Date('2/2/2020'), end: new Date('3/3/2020') }; startInput = fixture.debugElement.query(By.css('input')); endInput = fixture.debugElement.queryAll(By.css('input'))[1]; calendar = fixture.debugElement.query(By.css(CSS_CLASS_CALENDAR)); calendarDays = fixture.debugElement.queryAll(By.css(HelperTestFunctions.CURRENT_MONTH_DATES)); }); const verifyDateRange = () => { expect(dateRange.value.start).toEqual(startDate); expect(dateRange.value.end).toEqual(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); }; describe('Selection tests', () => { it('should assign range values correctly when selecting dates from the calendar', () => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.detectChanges(); let dayRange = 15; const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), 10, 0, 0, 0); endDate = new Date(startDate); endDate.setDate(endDate.getDate() + dayRange); selectDateRangeFromCalendar(startDate, endDate); verifyDateRange(); dayRange = 13; startDate = new Date(today.getFullYear(), today.getMonth(), 6, 0, 0, 0); endDate = new Date(startDate); endDate.setDate(endDate.getDate() + dayRange); selectDateRangeFromCalendar(startDate, endDate); verifyDateRange(); }); it('should assign range values correctly when selecting dates in reversed order', () => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.detectChanges(); const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), 10, 0, 0, 0); endDate = new Date(today.getFullYear(), today.getMonth(), 20, 0, 0, 0); selectDateRangeFromCalendar(endDate, startDate); verifyDateRange(); }); it('should apply selection to start and end dates when single date is selected', () => { fixture.componentInstance.mode = PickerInteractionMode.DropDown; fixture.detectChanges(); const today = new Date(); startDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0); endDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0); // startDate; selectDateRangeFromCalendar(startDate, endDate); verifyDateRange(); }); it('should update inputs correctly on first and last date selection', () => { dateRange.hideOutsideDays = true; 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); selectDateRangeFromCalendar(startDate, endDate); verifyDateRange(); }); it('should assign range values correctly when selecting through API', () => { startDate = new Date(2020, 10, 8, 0, 0, 0); endDate = new Date(2020, 11, 8, 0, 0, 0); dateRange.select(startDate, endDate); fixture.detectChanges(); verifyDateRange(); startDate = new Date(2003, 5, 18, 0, 0, 0); endDate = new Date(2003, 8, 18, 0, 0, 0); dateRange.select(startDate, endDate); 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.disp