import {Direction, Directionality} from '@angular/cdk/bidi'; import { DOWN_ARROW, END, ENTER, ESCAPE, HOME, LEFT_ARROW, PAGE_DOWN, PAGE_UP, RIGHT_ARROW, SPACE, UP_ARROW, } from '@angular/cdk/keycodes'; import { createKeyboardEvent, dispatchEvent, dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent, } from '@angular/cdk/testing/private'; import {Component} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {MAT_DATE_FORMATS, MatNativeDateModule} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import {DEC, FEB, JAN, MAR, NOV} from '../testing'; import {MatCalendarBody, MatCalendarUserEvent} from './calendar-body'; import { DefaultMatCalendarRangeStrategy, MAT_DATE_RANGE_SELECTION_STRATEGY, } from './date-range-selection-strategy'; import {DateRange} from './date-selection-model'; import {MatMonthView} from './month-view'; describe('MatMonthView', () => { describe('standard providers', () => { let dir: {value: Direction}; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ MatNativeDateModule, MatCalendarBody, MatMonthView, // Test components. StandardMonthView, MonthViewWithDateFilter, MonthViewWithDateClass, ], providers: [ {provide: Directionality, useFactory: () => (dir = {value: 'ltr'})}, {provide: MAT_DATE_RANGE_SELECTION_STRATEGY, useClass: DefaultMatCalendarRangeStrategy}, ], }); })); describe('standard month view', () => { let fixture: ComponentFixture; let testComponent: StandardMonthView; let monthViewNativeElement: Element; beforeEach(() => { fixture = TestBed.createComponent(StandardMonthView); fixture.detectChanges(); let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView))!; monthViewNativeElement = monthViewDebugElement.nativeElement; testComponent = fixture.componentInstance; }); it('has correct month label', () => { let labelEl = monthViewNativeElement.querySelector('.mat-calendar-body-label')!; expect(labelEl.innerHTML.trim()).toBe('JAN'); }); it('has 31 days', () => { let cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell')!; expect(cellEls.length).toBe(31); }); it('shows selected date if in same month', () => { let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('10'); }); it('does not show selected date if in different month', () => { testComponent.selected = new Date(2017, MAR, 10); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected'); expect(selectedEl).toBeNull(); }); it('fires selected change event on cell clicked', () => { let cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); (cellEls[cellEls.length - 1] as HTMLElement).click(); fixture.detectChanges(); let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('31'); }); it('should mark active date', () => { let cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); expect((cellEls[4] as HTMLElement).innerText.trim()).toBe('5'); expect(cellEls[4].classList).toContain('mat-calendar-body-active'); }); describe('drag and drop with default range strategy', () => { const initialRange = new DateRange(new Date(2017, JAN, 10), new Date(2017, JAN, 13)); beforeEach(() => { testComponent.selected = initialRange; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); }); function getDaysMatching(selector: string) { return Array.from(monthViewNativeElement.querySelectorAll(selector)).map(elem => Number(elem.textContent!.trim()), ); } it('drags the range start', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cellEls[9], 'mousedown'); fixture.detectChanges(); // Grow range. dispatchMouseEvent(cellEls[8], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([9, 10, 11, 12, 13]); // Shrink range. dispatchMouseEvent(cellEls[10], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([11, 12, 13]); // Move range past end. dispatchMouseEvent(cellEls[13], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([14, 15, 16, 17]); // End drag. dispatchMouseEvent(cellEls[13], 'mouseup'); fixture.detectChanges(); expect(testComponent.selected).toEqual( new DateRange(new Date(2017, JAN, 14), new Date(2017, JAN, 17)), ); }); it('drags the range end', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cellEls[12], 'mousedown'); fixture.detectChanges(); // Grow range. dispatchMouseEvent(cellEls[13], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([10, 11, 12, 13, 14]); // Shrink range. dispatchMouseEvent(cellEls[11], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([10, 11, 12]); // Move range before start. dispatchMouseEvent(cellEls[8], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([6, 7, 8, 9]); // End drag. dispatchMouseEvent(cellEls[8], 'mouseup'); fixture.detectChanges(); expect(testComponent.selected).toEqual( new DateRange(new Date(2017, JAN, 6), new Date(2017, JAN, 9)), ); }); it('drags the range middle', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cellEls[11], 'mousedown'); fixture.detectChanges(); // Move range down. dispatchMouseEvent(cellEls[10], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([9, 10, 11, 12]); // Move range up. dispatchMouseEvent(cellEls[12], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([11, 12, 13, 14]); // End drag. dispatchMouseEvent(cellEls[12], 'mouseup'); fixture.detectChanges(); expect(testComponent.selected).toEqual( new DateRange(new Date(2017, JAN, 11), new Date(2017, JAN, 14)), ); }); it('does nothing when dragging outside range', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cellEls[8], 'mousedown'); fixture.detectChanges(); dispatchMouseEvent(cellEls[7], 'mouseenter'); fixture.detectChanges(); expect(getDaysMatching('.mat-calendar-body-in-preview')).toEqual([]); dispatchMouseEvent(cellEls[7], 'mouseup'); fixture.detectChanges(); expect(testComponent.selected).toEqual(initialRange); }); }); describe('a11y', () => { it('should set the correct role on the internal table node', () => { const table = monthViewNativeElement.querySelector('table')!; expect(table.getAttribute('role')).toBe('grid'); }); it('should set the correct scope on the table headers', () => { const nonDividerHeaders = monthViewNativeElement.querySelectorAll( '.mat-calendar-table-header th:not(.mat-calendar-table-header-divider)', ); const dividerHeader = monthViewNativeElement.querySelector( '.mat-calendar-table-header-divider', )!; expect( Array.from(nonDividerHeaders).every(header => { return header.getAttribute('scope') === 'col'; }), ).toBe(true); expect(dividerHeader.hasAttribute('scope')).toBe(false); }); describe('calendar body', () => { let calendarBodyEl: HTMLElement; let calendarInstance: StandardMonthView; 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 date on left arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 4)); calendarInstance.date = new Date(2017, JAN, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 31)); }); it('should increment date on left arrow press in rtl', () => { dir.value = 'rtl'; dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 6)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 7)); }); it('should increment date on right arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 6)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 7)); }); it('should decrement date on right arrow press in rtl', () => { dir.value = 'rtl'; dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 4)); calendarInstance.date = new Date(2017, JAN, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 31)); }); it('should go up a row on up arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 29)); calendarInstance.date = new Date(2017, JAN, 7); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 31)); }); it('should go down a row on down arrow press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 12)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 19)); }); it('should go to beginning of the month on home press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 1)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 1)); }); it('should go to end of the month on end press', () => { calendarInstance.date = new Date(2017, JAN, 10); fixture.changeDetectorRef.markForCheck(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 31)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', END); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 31)); }); it('should go back one month on page up press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, DEC, 5)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2016, NOV, 5)); }); it('should go forward one month on page down press', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, FEB, 5)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, MAR, 5)); }); it('should select active date on enter', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(testComponent.selected).toEqual(new Date(2017, JAN, 10)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keyup', ENTER); fixture.detectChanges(); expect(testComponent.selected).toEqual(new Date(2017, JAN, 4)); }); it('should select active date on space', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); expect(testComponent.selected).toEqual(new Date(2017, JAN, 10)); dispatchKeyboardEvent(calendarBodyEl, 'keydown', SPACE); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keyup', SPACE); fixture.detectChanges(); expect(testComponent.selected).toEqual(new Date(2017, JAN, 4)); }); it('should cancel the current range selection when pressing escape', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); testComponent.selected = new DateRange(new Date(2017, JAN, 10), null); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchMouseEvent(cellEls[15], 'mouseenter'); fixture.detectChanges(); // Note that here we only care that _some_ kind of range is rendered. There are // plenty of tests in the calendar body which assert that everything is correct. expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length, ).toBeGreaterThan(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length, ).toBeGreaterThan(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length, ).toBeGreaterThan(0); const event = createKeyboardEvent('keydown', ESCAPE, 'Escape'); spyOn(event, 'stopPropagation'); dispatchEvent(calendarBodyEl, event); fixture.detectChanges(); // Expect the range range to have been cleared. expect( monthViewNativeElement.querySelectorAll( [ '.mat-calendar-body-preview-start', '.mat-calendar-body-in-preview', '.mat-calendar-body-preview-end', ].join(','), ).length, ).toBe(0); expect(event.stopPropagation).toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); expect(testComponent.selected).toBeFalsy(); }); it( 'should not cancel the current range selection when pressing escape with a ' + 'modifier key', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); testComponent.selected = new DateRange(new Date(2017, JAN, 10), null); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchMouseEvent(cellEls[15], 'mouseenter'); fixture.detectChanges(); const rangeStarts = monthViewNativeElement.querySelectorAll( '.mat-calendar-body-preview-start', ).length; const rangeMids = monthViewNativeElement.querySelectorAll( '.mat-calendar-body-in-preview', ).length; const rangeEnds = monthViewNativeElement.querySelectorAll( '.mat-calendar-body-preview-end', ).length; // Note that here we only care that _some_ kind of range is rendered. There are // plenty of tests in the calendar body which assert that everything is correct. expect(rangeStarts).toBeGreaterThan(0); expect(rangeMids).toBeGreaterThan(0); expect(rangeEnds).toBeGreaterThan(0); const event = createKeyboardEvent('keydown', ESCAPE, 'Escape', {alt: true}); spyOn(event, 'stopPropagation'); dispatchEvent(calendarBodyEl, event); fixture.detectChanges(); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length, ).toBe(rangeStarts); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length, ).toBe(rangeMids); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length, ).toBe(rangeEnds); expect(event.stopPropagation).not.toHaveBeenCalled(); expect(event.defaultPrevented).toBe(false); expect(testComponent.selected).toBeTruthy(); }, ); it('cancels the active drag but not the selection on escape during an active drag', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); const selectedRange = new DateRange(new Date(2017, JAN, 10), new Date(2017, JAN, 17)); testComponent.selected = selectedRange; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchMouseEvent(cellEls[11], 'mousedown'); fixture.detectChanges(); dispatchMouseEvent(cellEls[4], 'mouseenter'); fixture.detectChanges(); const rangeStarts = monthViewNativeElement.querySelectorAll( '.mat-calendar-body-preview-start', ).length; const rangeMids = monthViewNativeElement.querySelectorAll( '.mat-calendar-body-in-preview', ).length; const rangeEnds = monthViewNativeElement.querySelectorAll( '.mat-calendar-body-preview-end', ).length; // Note that here we only care that _some_ kind of range is rendered. There are // plenty of tests in the calendar body which assert that everything is correct. expect(rangeStarts).toBeGreaterThan(0); expect(rangeMids).toBeGreaterThan(0); expect(rangeEnds).toBeGreaterThan(0); const event = createKeyboardEvent('keydown', ESCAPE, 'Escape'); spyOn(event, 'stopPropagation'); dispatchEvent(calendarBodyEl, event); fixture.detectChanges(); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length, ).toBe(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length, ).toBe(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length, ).toBe(0); expect(event.stopPropagation).toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); expect(testComponent.selected).toEqual(selectedRange); }); it('should clear the preview range when the user is done selecting', () => { const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); testComponent.selected = new DateRange(new Date(2017, JAN, 10), null); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); dispatchMouseEvent(cellEls[15], 'mouseenter'); fixture.detectChanges(); // Note that here we only care that _some_ kind of range is rendered. There are // plenty of tests in the calendar body which assert that everything is correct. expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length, ).toBeGreaterThan(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length, ).toBeGreaterThan(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length, ).toBeGreaterThan(0); cellEls[15].click(); fixture.detectChanges(); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length, ).toBe(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length, ).toBe(0); expect( monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length, ).toBe(0); }); it('should not clear the range when pressing escape while there is no preview', () => { const getRangeElements = () => monthViewNativeElement.querySelectorAll( [ '.mat-calendar-body-range-start', '.mat-calendar-body-in-range', '.mat-calendar-body-range-end', ].join(','), ); testComponent.selected = new DateRange( new Date(2017, JAN, 10), new Date(2017, JAN, 15), ); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(getRangeElements().length) .withContext('Expected range to be present on init.') .toBeGreaterThan(0); dispatchKeyboardEvent(calendarBodyEl, 'keydown', ESCAPE); fixture.detectChanges(); expect(getRangeElements().length) .withContext('Expected range to be present after pressing the escape key.') .toBeGreaterThan(0); }); it( 'should not fire the selected change event when clicking on an already-selected ' + 'date while selecting a single date', () => { testComponent.selected = new Date(2017, JAN, 10); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(fixture.componentInstance.selectedChangeSpy).not.toHaveBeenCalled(); const selectedCell = monthViewNativeElement.querySelector( '.mat-calendar-body-selected', ) as HTMLElement; selectedCell.click(); fixture.detectChanges(); expect(fixture.componentInstance.selectedChangeSpy).not.toHaveBeenCalled(); }, ); it( 'should fire the selected change event when clicking on an already-selected ' + 'date while selecting a range', () => { const selectedDate = new Date(2017, JAN, 10); testComponent.selected = new DateRange(selectedDate, null); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(fixture.componentInstance.selectedChangeSpy).not.toHaveBeenCalled(); const selectedCell = monthViewNativeElement.querySelector( '.mat-calendar-body-selected', ) as HTMLElement; selectedCell.click(); fixture.detectChanges(); expect(fixture.componentInstance.selectedChangeSpy).toHaveBeenCalledWith( selectedDate, ); }, ); it( 'should fire the _userSelection event with the correct value when clicking ' + 'on a selected date', () => { const date = new Date(2017, JAN, 10); testComponent.selected = date; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(fixture.componentInstance.userSelectionSpy).not.toHaveBeenCalled(); const selectedCell = monthViewNativeElement.querySelector( '.mat-calendar-body-selected', ) as HTMLElement; selectedCell.click(); fixture.detectChanges(); expect(fixture.componentInstance.userSelectionSpy).toHaveBeenCalledWith( jasmine.objectContaining({value: date}), ); }, ); it('should go to month that is focused', () => { const jan11Cell = fixture.debugElement.nativeElement.querySelector( '[data-mat-row="1"][data-mat-col="3"] button', ) as HTMLElement; dispatchFakeEvent(jan11Cell, 'focus'); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 11)); }); it('should not call `.focus()` when the active date is focused', () => { const jan5Cell = fixture.debugElement.nativeElement.querySelector( '[data-mat-row="0"][data-mat-col="4"] button', ) as HTMLElement; const focusSpy = (jan5Cell.focus = jasmine.createSpy('cellFocused')); dispatchFakeEvent(jan5Cell, 'focus'); fixture.detectChanges(); expect(calendarInstance.date).toEqual(new Date(2017, JAN, 5)); expect(focusSpy).not.toHaveBeenCalled(); }); }); }); }); describe('month view with date filter', () => { it('should disable filtered dates', () => { const fixture = TestBed.createComponent(MonthViewWithDateFilter); fixture.detectChanges(); let cells = fixture.nativeElement.querySelectorAll('.mat-calendar-body-cell'); expect(cells[0].classList).toContain('mat-calendar-body-disabled'); expect(cells[1].classList).not.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(MonthViewWithDateFilter); 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(MonthViewWithDateFilter); 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('month view with custom date classes', () => { let fixture: ComponentFixture; let monthViewNativeElement: Element; let dateClassSpy: jasmine.Spy; beforeEach(() => { fixture = TestBed.createComponent(MonthViewWithDateClass); dateClassSpy = spyOn(fixture.componentInstance, 'dateClass').and.callThrough(); fixture.detectChanges(); let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView))!; monthViewNativeElement = monthViewDebugElement.nativeElement; }); it('should be able to add a custom class to some dates', () => { let cells = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); expect(cells[0].classList).not.toContain('even'); expect(cells[1].classList).toContain('even'); }); it('should call dateClass with the correct view name', () => { expect(dateClassSpy).toHaveBeenCalledWith(jasmine.any(Date), 'month'); }); }); }); describe('month view with custom date formats', () => { let fixture: ComponentFixture; let monthViewNativeElement: Element; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ MatNativeDateModule, MatCalendarBody, MatMonthView, // Test components. StandardMonthView, MonthViewWithDateFilter, MonthViewWithDateClass, ], providers: [ {provide: Directionality, useFactory: () => ({value: 'ltr'})}, {provide: MAT_DATE_RANGE_SELECTION_STRATEGY, useClass: DefaultMatCalendarRangeStrategy}, { provide: MAT_DATE_FORMATS, useValue: { parse: { dateInput: null, }, display: { dateInput: {year: 'numeric', month: 'numeric', day: 'numeric'}, monthLabel: {year: 'numeric', month: 'short'}, monthYearLabel: {year: 'numeric', month: 'short'}, dateA11yLabel: {year: 'numeric', month: 'long', day: 'numeric'}, monthYearA11yLabel: {year: 'numeric', month: 'long'}, }, }, }, ], }); fixture = TestBed.createComponent(StandardMonthView); fixture.detectChanges(); let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView))!; monthViewNativeElement = monthViewDebugElement.nativeElement; })); it('has correct month label', () => { let labelEl = monthViewNativeElement.querySelector('.mat-calendar-body-label')!; expect(labelEl.innerHTML.trim()).toBe('Jan 2017'); }); }); }); @Component({ template: ` `, standalone: true, imports: [MatMonthView], }) class StandardMonthView { date = new Date(2017, JAN, 5); selected: Date | DateRange = new Date(2017, JAN, 10); selectedChangeSpy = jasmine.createSpy('selectedChange'); userSelectionSpy = jasmine.createSpy('userSelection'); activeDrag: MatCalendarUserEvent | null = null; dragStarted(event: MatCalendarUserEvent) { this.activeDrag = event; } dragEnded(event: MatCalendarUserEvent | null>) { if (!this.activeDrag) return; if (event.value) { this.selected = event.value; } this.activeDrag = null; } } @Component({ template: ` `, standalone: true, imports: [MatMonthView], }) class MonthViewWithDateFilter { activeDate = new Date(2017, JAN, 1); minDate: Date | null = null; maxDate: Date | null = null; dateFilter(date: Date) { return date.getDate() % 2 == 0; } } @Component({ template: ``, standalone: true, imports: [MatMonthView], }) class MonthViewWithDateClass { activeDate = new Date(2017, JAN, 1); dateClass(date: Date) { return date.getDate() % 2 == 0 ? 'even' : undefined; } }