import {Direction, Directionality} from '@angular/cdk/bidi'; import { DOWN_ARROW, END, HOME, LEFT_ARROW, PAGE_DOWN, PAGE_UP, RIGHT_ARROW, UP_ARROW, } from '@angular/cdk/keycodes'; import {dispatchFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing/private'; import {Component, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {MatNativeDateModule} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import {AUG, DEC, FEB, JAN, JUL, JUN, MAR, MAY, NOV, OCT, SEP} from '../testing'; import {MatCalendarBody} from './calendar-body'; import {MatYearView} from './year-view'; describe('MatYearView', () => { let dir: {value: Direction}; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ MatNativeDateModule, MatCalendarBody, MatYearView, // Test components. StandardYearView, YearViewWithDateFilter, YearViewWithDateClass, ], providers: [{provide: Directionality, useFactory: () => (dir = {value: 'ltr'})}], }); })); describe('standard year view', () => { let fixture: ComponentFixture; let testComponent: StandardYearView; let yearViewNativeElement: Element; beforeEach(() => { fixture = TestBed.createComponent(StandardYearView); fixture.detectChanges(); let yearViewDebugElement = fixture.debugElement.query(By.directive(MatYearView))!; yearViewNativeElement = yearViewDebugElement.nativeElement; testComponent = fixture.componentInstance; }); it('has correct year label', () => { let labelEl = yearViewNativeElement.querySelector('.mat-calendar-body-label')!; expect(labelEl.innerHTML.trim()).toBe('2017'); }); it('has 12 months', () => { let cellEls = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell')!; expect(cellEls.length).toBe(12); }); it('shows selected month if in same year', () => { let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('MAR'); }); it('does not show selected month if in different year', () => { testComponent.selected = new Date(2016, MAR, 10); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected'); expect(selectedEl).toBeNull(); }); it('fires selected change event on cell clicked', () => { let cellEls = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); (cellEls[cellEls.length - 1] as HTMLElement).click(); fixture.detectChanges(); let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('DEC'); }); it('should emit the selected month on cell clicked', () => { let cellEls = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); (cellEls[cellEls.length - 1] as HTMLElement).click(); fixture.detectChanges(); const normalizedMonth: Date = fixture.componentInstance.selectedMonth; expect(normalizedMonth.getMonth()).toEqual(11); }); it('should mark active date', () => { let cellEls = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); expect((cellEls[0] as HTMLElement).innerText.trim()).toBe('JAN'); expect(cellEls[0].classList).toContain('mat-calendar-body-active'); }); it('should allow selection of month with less days than current active date', () => { testComponent.date = new Date(2017, JUL, 31); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); testComponent.yearView._monthSelected({value: JUN, event: null!}); fixture.detectChanges(); expect(testComponent.selected).toEqual(new Date(2017, JUN, 30)); }); describe('a11y', () => { it('should set the correct role on the internal table node', () => { const table = yearViewNativeElement.querySelector('table')!; expect(table.getAttribute('role')).toBe('grid'); }); describe('calendar body', () => { let calendarBodyEl: HTMLElement; let calendarInstance: StandardYearView; beforeEach(() => { calendarInstance = fixture.componentInstance; calendarBodyEl = fixture.debugElement.nativeElement.querySelector( '.mat-calendar-body', ) as HTMLElement; expect(calendarBodyEl).not.toBeNull(); dir.value = 'ltr'; fixture.componentInstance.date = new Date(2017, JAN, 5); fixture.changeDetectorRef.markForCheck(); dispatchFakeEvent(calendarBodyEl, 'focus'); fixture.detectChanges(); }); it('should decrement month on left arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 5)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, NOV, 5)); }); it('should increment month on left arrow press in rtl', () => { dir.value = 'rtl'; dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, FEB, 5)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, MAR, 5)); }); it('should increment month on right arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, FEB, 5)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, MAR, 5)); }); it('should decrement month on right arrow press in rtl', () => { dir.value = 'rtl'; dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 5)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, NOV, 5)); }); it('should go up a row on up arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, SEP, 5)); calendarInstance.date = new Date(2017, JUL, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, MAR, 1)); calendarInstance.date = new Date(2017, DEC, 10); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, AUG, 10)); }); it('should go down a row on down arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, MAY, 5)); calendarInstance.date = new Date(2017, JUN, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, OCT, 1)); calendarInstance.date = new Date(2017, SEP, 30); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2018, JAN, 30)); }); it('should go to first month of the year on home press', () => { calendarInstance.date = new Date(2017, SEP, 30); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 30)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 30)); }); it('should go to last month of the year on end press', () => { calendarInstance.date = new Date(2017, OCT, 31); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, DEC, 31)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, DEC, 31)); }); it('should go back one year on page up press', () => { calendarInstance.date = new Date(2016, FEB, 29); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2015, FEB, 28)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2014, FEB, 28)); }); it('should go forward one year on page down press', () => { calendarInstance.date = new Date(2016, FEB, 29); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, FEB, 28)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2018, FEB, 28)); }); it('should go to date that is focused', () => { const juneCell = fixture.debugElement.nativeElement.querySelector( '[data-mat-row="1"][data-mat-col="1"] button', ) as HTMLElement; dispatchFakeEvent(juneCell, 'focus'); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JUN, 5)); }); it('should not call `.focus()` when the active date is focused', () => { const janCell = fixture.debugElement.nativeElement.querySelector( '[data-mat-row="0"][data-mat-col="0"] button', ) as HTMLElement; const focusSpy = (janCell.focus = jasmine.createSpy('cellFocused')); dispatchFakeEvent(janCell, 'focus'); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 5)); expect(focusSpy).not.toHaveBeenCalled(); }); }); }); }); describe('year view with date filter', () => { it('should disable months with no enabled days', () => { const fixture = TestBed.createComponent(YearViewWithDateFilter); fixture.detectChanges(); const cells = fixture.nativeElement.querySelectorAll('.mat-calendar-body-cell'); expect(cells[0].classList).not.toContain('mat-calendar-body-disabled'); expect(cells[1].classList).toContain('mat-calendar-body-disabled'); }); it('should not call the date filter function if the date is before the min date', () => { const fixture = TestBed.createComponent(YearViewWithDateFilter); const activeDate = fixture.componentInstance.activeDate; const spy = spyOn(fixture.componentInstance, 'dateFilter').and.callThrough(); fixture.componentInstance.minDate = new Date( activeDate.getFullYear() + 1, activeDate.getMonth(), activeDate.getDate(), ); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(spy).not.toHaveBeenCalled(); }); it('should not call the date filter function if the date is after the max date', () => { const fixture = TestBed.createComponent(YearViewWithDateFilter); const activeDate = fixture.componentInstance.activeDate; const spy = spyOn(fixture.componentInstance, 'dateFilter').and.callThrough(); fixture.componentInstance.maxDate = new Date( activeDate.getFullYear() - 1, activeDate.getMonth(), activeDate.getDate(), ); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(spy).not.toHaveBeenCalled(); }); }); describe('year view with custom date classes', () => { let fixture: ComponentFixture; let yearViewNativeElement: Element; let dateClassSpy: jasmine.Spy; beforeEach(() => { fixture = TestBed.createComponent(YearViewWithDateClass); dateClassSpy = spyOn(fixture.componentInstance, 'dateClass').and.callThrough(); fixture.detectChanges(); let yearViewDebugElement = fixture.debugElement.query(By.directive(MatYearView))!; yearViewNativeElement = yearViewDebugElement.nativeElement; }); it('should be able to add a custom class to some month cells', () => { let cells = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); expect(cells[0].classList).toContain('even'); expect(cells[1].classList).not.toContain('even'); }); it('should call dateClass with the correct view name', () => { expect(dateClassSpy).toHaveBeenCalledWith(jasmine.any(Date), 'year'); }); }); }); @Component({ template: ` `, standalone: true, imports: [MatYearView], }) class StandardYearView { date = new Date(2017, JAN, 5); selected = new Date(2017, MAR, 10); selectedMonth: Date; @ViewChild(MatYearView) yearView: MatYearView; } @Component({ template: ` `, standalone: true, imports: [MatYearView], }) class YearViewWithDateFilter { activeDate = new Date(2017, JAN, 1); minDate: Date | null = null; maxDate: Date | null = null; dateFilter(date: Date) { if (date.getMonth() == JAN) { return date.getDate() == 10; } if (date.getMonth() == FEB) { return false; } return true; } } @Component({ template: ``, standalone: true, imports: [MatYearView], }) class YearViewWithDateClass { activeDate = new Date(2017, JAN, 1); dateClass(date: Date) { return date.getMonth() % 2 == 0 ? 'even' : undefined; } }