ipsos-components
Version:
Material Design components for Angular
1,370 lines (1,060 loc) • 48 kB
text/typescript
import {ENTER, ESCAPE, RIGHT_ARROW} from '@angular/cdk/keycodes';
import {OverlayContainer} from '@angular/cdk/overlay';
import {
createKeyboardEvent,
dispatchEvent,
dispatchFakeEvent,
dispatchKeyboardEvent,
dispatchMouseEvent,
} from '@angular/cdk/testing';
import {Component, ViewChild} from '@angular/core';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {
DEC,
JAN,
JUL,
JUN,
MAT_DATE_LOCALE,
MatNativeDateModule,
NativeDateModule,
SEP,
} from '@angular/material/core';
import {MatFormFieldModule} from '@angular/material/form-field';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatInputModule} from '../input/index';
import {MatDatepicker} from './datepicker';
import {MatDatepickerInput} from './datepicker-input';
import {MatDatepickerToggle} from './datepicker-toggle';
import {MatDatepickerIntl, MatDatepickerModule} from './index';
describe('MatDatepicker', () => {
const SUPPORTS_INTL = typeof Intl != 'undefined';
afterEach(inject([OverlayContainer], (container: OverlayContainer) => {
container.getContainerElement().parentNode!.removeChild(container.getContainerElement());
}));
describe('with MatNativeDateModule', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
NoopAnimationsModule,
ReactiveFormsModule,
],
declarations: [
DatepickerWithChangeAndInputEvents,
DatepickerWithFilterAndValidation,
DatepickerWithFormControl,
DatepickerWithISOStrings,
DatepickerWithMinAndMaxValidation,
DatepickerWithNgModel,
DatepickerWithStartAt,
DatepickerWithStartView,
DatepickerWithToggle,
FormFieldDatepicker,
MultiInputDatepicker,
NoInputDatepicker,
StandardDatepicker,
DatepickerWithEvents,
],
});
TestBed.compileComponents();
}));
describe('standard datepicker', () => {
let fixture: ComponentFixture<StandardDatepicker>;
let testComponent: StandardDatepicker;
beforeEach(async(() => {
fixture = TestBed.createComponent(StandardDatepicker);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should initialize with correct value shown in input', () => {
if (SUPPORTS_INTL) {
expect(fixture.nativeElement.querySelector('input').value).toBe('1/1/2020');
}
});
it('open non-touch should open popup', () => {
expect(document.querySelector('.cdk-overlay-pane.mat-datepicker-popup')).toBeNull();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('.cdk-overlay-pane.mat-datepicker-popup')).not.toBeNull();
});
it('open touch should open dialog', () => {
testComponent.touch = true;
fixture.detectChanges();
expect(document.querySelector('.mat-datepicker-dialog mat-dialog-container')).toBeNull();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('.mat-datepicker-dialog mat-dialog-container'))
.not.toBeNull();
});
it('should open datepicker if opened input is set to true', () => {
testComponent.opened = true;
fixture.detectChanges();
expect(document.querySelector('.mat-datepicker-content')).not.toBeNull();
testComponent.opened = false;
fixture.detectChanges();
expect(document.querySelector('.mat-datepicker-content')).toBeNull();
});
it('open in disabled mode should not open the calendar', () => {
testComponent.disabled = true;
fixture.detectChanges();
expect(document.querySelector('.cdk-overlay-pane')).toBeNull();
expect(document.querySelector('mat-dialog-container')).toBeNull();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('.cdk-overlay-pane')).toBeNull();
expect(document.querySelector('mat-dialog-container')).toBeNull();
});
it('disabled datepicker input should open the calendar if datepicker is enabled', () => {
testComponent.datepicker.disabled = false;
testComponent.datepickerInput.disabled = true;
fixture.detectChanges();
expect(document.querySelector('.cdk-overlay-pane')).toBeNull();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull();
});
it('close should close popup', () => {
testComponent.datepicker.open();
fixture.detectChanges();
let popup = document.querySelector('.cdk-overlay-pane')!;
expect(popup).not.toBeNull();
expect(parseInt(getComputedStyle(popup).height as string)).not.toBe(0);
testComponent.datepicker.close();
fixture.detectChanges();
expect(parseInt(getComputedStyle(popup).height as string)).toBe(0);
});
it('should close the popup when pressing ESCAPE', () => {
testComponent.datepicker.open();
fixture.detectChanges();
let content = document.querySelector('.cdk-overlay-pane mat-datepicker-content')!;
expect(content).toBeTruthy('Expected datepicker to be open.');
const keyboardEvent = createKeyboardEvent('keydown', ESCAPE);
const stopPropagationSpy = spyOn(keyboardEvent, 'stopPropagation').and.callThrough();
dispatchEvent(content, keyboardEvent);
fixture.detectChanges();
content = document.querySelector('.cdk-overlay-pane mat-datepicker-content')!;
expect(content).toBeFalsy('Expected datepicker to be closed.');
expect(stopPropagationSpy).toHaveBeenCalled();
expect(keyboardEvent.defaultPrevented)
.toBe(true, 'Expected default ESCAPE action to be prevented.');
});
it('close should close dialog', async(() => {
testComponent.touch = true;
fixture.detectChanges();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
testComponent.datepicker.close();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(document.querySelector('mat-dialog-container')).toBeNull();
});
}));
it('setting selected via click should update input and close calendar', async(() => {
testComponent.touch = true;
fixture.detectChanges();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1));
let cells = document.querySelectorAll('.mat-calendar-body-cell');
dispatchMouseEvent(cells[1], 'click');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(document.querySelector('mat-dialog-container')).toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
});
}));
it('setting selected via enter press should update input and close calendar', () => {
testComponent.touch = true;
fixture.detectChanges();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1));
let calendarBodyEl = document.querySelector('.mat-calendar-content') as HTMLElement;
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(document.querySelector('mat-dialog-container')).toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
});
});
it('clicking the currently selected date should close the calendar ' +
'without firing selectedChanged', () => {
const selectedChangedSpy =
spyOn(testComponent.datepicker.selectedChanged, 'emit').and.callThrough();
for (let changeCount = 1; changeCount < 3; changeCount++) {
const currentDay = changeCount;
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('mat-datepicker-content')).not.toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, currentDay));
let cells = document.querySelectorAll('.mat-calendar-body-cell');
dispatchMouseEvent(cells[1], 'click');
fixture.detectChanges();
}
expect(selectedChangedSpy.calls.count()).toEqual(1);
expect(document.querySelector('mat-dialog-container')).toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
});
it('pressing enter on the currently selected date should close the calendar without ' +
'firing selectedChanged', () => {
const selectedChangedSpy =
spyOn(testComponent.datepicker.selectedChanged, 'emit').and.callThrough();
testComponent.datepicker.open();
fixture.detectChanges();
let calendarBodyEl = document.querySelector('.mat-calendar-content') as HTMLElement;
expect(calendarBodyEl).not.toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1));
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(selectedChangedSpy.calls.count()).toEqual(0);
expect(document.querySelector('mat-dialog-container')).toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1));
});
});
it('startAt should fallback to input value', () => {
expect(testComponent.datepicker.startAt).toEqual(new Date(2020, JAN, 1));
});
it('should attach popup to native input', () => {
let attachToRef = testComponent.datepickerInput.getPopupConnectionElementRef();
expect(attachToRef.nativeElement.tagName.toLowerCase())
.toBe('input', 'popup should be attached to native input');
});
it('input should aria-owns calendar after opened in non-touch mode', () => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.getAttribute('aria-owns')).toBeNull();
testComponent.datepicker.open();
fixture.detectChanges();
let ownedElementId = inputEl.getAttribute('aria-owns');
expect(ownedElementId).not.toBeNull();
let ownedElement = document.getElementById(ownedElementId);
expect(ownedElement).not.toBeNull();
expect((ownedElement as Element).tagName.toLowerCase()).toBe('mat-calendar');
});
it('input should aria-owns calendar after opened in touch mode', () => {
testComponent.touch = true;
fixture.detectChanges();
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.getAttribute('aria-owns')).toBeNull();
testComponent.datepicker.open();
fixture.detectChanges();
let ownedElementId = inputEl.getAttribute('aria-owns');
expect(ownedElementId).not.toBeNull();
let ownedElement = document.getElementById(ownedElementId);
expect(ownedElement).not.toBeNull();
expect((ownedElement as Element).tagName.toLowerCase()).toBe('mat-calendar');
});
it('should not throw when given wrong data type', () => {
testComponent.date = '1/1/2017' as any;
expect(() => fixture.detectChanges()).not.toThrow();
});
});
describe('datepicker with too many inputs', () => {
it('should throw when multiple inputs registered', async(() => {
let fixture = TestBed.createComponent(MultiInputDatepicker);
expect(() => fixture.detectChanges()).toThrow();
}));
});
describe('datepicker with no inputs', () => {
let fixture: ComponentFixture<NoInputDatepicker>;
let testComponent: NoInputDatepicker;
beforeEach(async(() => {
fixture = TestBed.createComponent(NoInputDatepicker);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should not throw when accessing disabled property', () => {
expect(() => testComponent.datepicker.disabled).not.toThrow();
});
it('should throw when opened with no registered inputs', async(() => {
expect(() => testComponent.datepicker.open()).toThrow();
}));
});
describe('datepicker with startAt', () => {
let fixture: ComponentFixture<DatepickerWithStartAt>;
let testComponent: DatepickerWithStartAt;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithStartAt);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('explicit startAt should override input value', () => {
expect(testComponent.datepicker.startAt).toEqual(new Date(2010, JAN, 1));
});
});
describe('datepicker with startView', () => {
let fixture: ComponentFixture<DatepickerWithStartView>;
let testComponent: DatepickerWithStartView;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithStartView);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should start at the specified view', () => {
testComponent.datepicker.open();
fixture.detectChanges();
const firstCalendarCell = document.querySelector('.mat-calendar-body-cell')!;
// When the calendar is in year view, the first cell should be for a month rather than
// for a date.
expect(firstCalendarCell.textContent)
.toBe('JAN', 'Expected the calendar to be in year-view');
});
});
describe('datepicker with ngModel', () => {
let fixture: ComponentFixture<DatepickerWithNgModel>;
let testComponent: DatepickerWithNgModel;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithNgModel);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
testComponent = fixture.componentInstance;
});
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should update datepicker when model changes', async(() => {
expect(testComponent.datepickerInput.value).toBeNull();
expect(testComponent.datepicker._selected).toBeNull();
let selected = new Date(2017, JAN, 1);
testComponent.selected = selected;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(testComponent.datepickerInput.value).toEqual(selected);
expect(testComponent.datepicker._selected).toEqual(selected);
});
}));
it('should update model when date is selected', async(() => {
expect(testComponent.selected).toBeNull();
expect(testComponent.datepickerInput.value).toBeNull();
let selected = new Date(2017, JAN, 1);
testComponent.datepicker._select(selected);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(testComponent.selected).toEqual(selected);
expect(testComponent.datepickerInput.value).toEqual(selected);
});
}));
it('should mark input dirty after input event', () => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.classList).toContain('ng-pristine');
dispatchFakeEvent(inputEl, 'input');
fixture.detectChanges();
expect(inputEl.classList).toContain('ng-dirty');
});
it('should mark input dirty after date selected', async(() => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.classList).toContain('ng-pristine');
testComponent.datepicker._select(new Date(2017, JAN, 1));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(inputEl.classList).toContain('ng-dirty');
});
}));
it('should not mark dirty after model change', async(() => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.classList).toContain('ng-pristine');
testComponent.selected = new Date(2017, JAN, 1);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(inputEl.classList).toContain('ng-pristine');
});
}));
it('should mark input touched on blur', () => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.classList).toContain('ng-untouched');
dispatchFakeEvent(inputEl, 'focus');
fixture.detectChanges();
expect(inputEl.classList).toContain('ng-untouched');
dispatchFakeEvent(inputEl, 'blur');
fixture.detectChanges();
expect(inputEl.classList).toContain('ng-touched');
});
it('should mark input touched on calendar selection', async(() => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.classList).toContain('ng-untouched');
testComponent.datepicker._select(new Date(2017, JAN, 1));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(inputEl.classList).toContain('ng-touched');
});
}));
});
describe('datepicker with formControl', () => {
let fixture: ComponentFixture<DatepickerWithFormControl>;
let testComponent: DatepickerWithFormControl;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithFormControl);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should update datepicker when formControl changes', () => {
expect(testComponent.datepickerInput.value).toBeNull();
expect(testComponent.datepicker._selected).toBeNull();
let selected = new Date(2017, JAN, 1);
testComponent.formControl.setValue(selected);
fixture.detectChanges();
expect(testComponent.datepickerInput.value).toEqual(selected);
expect(testComponent.datepicker._selected).toEqual(selected);
});
it('should update formControl when date is selected', () => {
expect(testComponent.formControl.value).toBeNull();
expect(testComponent.datepickerInput.value).toBeNull();
let selected = new Date(2017, JAN, 1);
testComponent.datepicker._select(selected);
fixture.detectChanges();
expect(testComponent.formControl.value).toEqual(selected);
expect(testComponent.datepickerInput.value).toEqual(selected);
});
it('should disable input when form control disabled', () => {
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
expect(inputEl.disabled).toBe(false);
testComponent.formControl.disable();
fixture.detectChanges();
expect(inputEl.disabled).toBe(true);
});
it('should disable toggle when form control disabled', () => {
expect(testComponent.datepickerToggle.disabled).toBe(false);
testComponent.formControl.disable();
fixture.detectChanges();
expect(testComponent.datepickerToggle.disabled).toBe(true);
});
});
describe('datepicker with mat-datepicker-toggle', () => {
let fixture: ComponentFixture<DatepickerWithToggle>;
let testComponent: DatepickerWithToggle;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithToggle);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should open calendar when toggle clicked', () => {
expect(document.querySelector('mat-dialog-container')).toBeNull();
let toggle = fixture.debugElement.query(By.css('button'));
dispatchMouseEvent(toggle.nativeElement, 'click');
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
});
it('should not open calendar when toggle clicked if datepicker is disabled', () => {
testComponent.datepicker.disabled = true;
fixture.detectChanges();
const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
expect(toggle.hasAttribute('disabled')).toBe(true);
expect(document.querySelector('mat-dialog-container')).toBeNull();
dispatchMouseEvent(toggle, 'click');
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).toBeNull();
});
it('should not open calendar when toggle clicked if input is disabled', () => {
expect(testComponent.datepicker.disabled).toBe(false);
testComponent.input.disabled = true;
fixture.detectChanges();
const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
expect(toggle.hasAttribute('disabled')).toBe(true);
expect(document.querySelector('mat-dialog-container')).toBeNull();
dispatchMouseEvent(toggle, 'click');
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).toBeNull();
});
it('should set the `button` type on the trigger to prevent form submissions', () => {
let toggle = fixture.debugElement.query(By.css('button')).nativeElement;
expect(toggle.getAttribute('type')).toBe('button');
});
it('should remove the underlying SVG icon from the tab order', () => {
const icon = fixture.debugElement.nativeElement.querySelector('svg');
expect(icon.getAttribute('focusable')).toBe('false');
});
it('should restore focus to the toggle after the calendar is closed', () => {
let toggle = fixture.debugElement.query(By.css('button')).nativeElement;
fixture.componentInstance.touchUI = false;
fixture.detectChanges();
toggle.focus();
expect(document.activeElement).toBe(toggle, 'Expected toggle to be focused.');
fixture.componentInstance.datepicker.open();
fixture.detectChanges();
let pane = document.querySelector('.cdk-overlay-pane')!;
expect(pane).toBeTruthy('Expected calendar to be open.');
expect(pane.contains(document.activeElement))
.toBe(true, 'Expected focus to be inside the calendar.');
fixture.componentInstance.datepicker.close();
fixture.detectChanges();
expect(document.activeElement).toBe(toggle, 'Expected focus to be restored to toggle.');
});
it('should re-render when the i18n labels change',
inject([MatDatepickerIntl], (intl: MatDatepickerIntl) => {
const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
intl.openCalendarLabel = 'Open the calendar, perhaps?';
intl.changes.next();
fixture.detectChanges();
expect(toggle.getAttribute('aria-label')).toBe('Open the calendar, perhaps?');
}));
});
describe('datepicker inside mat-form-field', () => {
let fixture: ComponentFixture<FormFieldDatepicker>;
let testComponent: FormFieldDatepicker;
beforeEach(async(() => {
fixture = TestBed.createComponent(FormFieldDatepicker);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should attach popup to mat-form-field underline', () => {
let attachToRef = testComponent.datepickerInput.getPopupConnectionElementRef();
expect(attachToRef.nativeElement.classList.contains('mat-form-field-underline'))
.toBe(true, 'popup should be attached to mat-form-field underline');
});
it('should float the placeholder when an invalid value is entered', () => {
testComponent.datepickerInput.value = 'totally-not-a-date' as any;
fixture.debugElement.nativeElement.querySelector('input').value = 'totally-not-a-date';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.querySelector('mat-form-field').classList)
.toContain('mat-form-field-should-float');
});
});
describe('datepicker with min and max dates and validation', () => {
let fixture: ComponentFixture<DatepickerWithMinAndMaxValidation>;
let testComponent: DatepickerWithMinAndMaxValidation;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithMinAndMaxValidation);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should use min and max dates specified by the input', () => {
expect(testComponent.datepicker._minDate).toEqual(new Date(2010, JAN, 1));
expect(testComponent.datepicker._maxDate).toEqual(new Date(2020, JAN, 1));
});
it('should mark invalid when value is before min', async(() => {
testComponent.date = new Date(2009, DEC, 31);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.toContain('ng-invalid');
});
}));
it('should mark invalid when value is after max', async(() => {
testComponent.date = new Date(2020, JAN, 2);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.toContain('ng-invalid');
});
}));
it('should not mark invalid when value equals min', async(() => {
testComponent.date = testComponent.datepicker._minDate;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.not.toContain('ng-invalid');
});
}));
it('should not mark invalid when value equals max', async(() => {
testComponent.date = testComponent.datepicker._maxDate;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.not.toContain('ng-invalid');
});
}));
it('should not mark invalid when value is between min and max', async(() => {
testComponent.date = new Date(2010, JAN, 2);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.not.toContain('ng-invalid');
});
}));
});
describe('datepicker with filter and validation', () => {
let fixture: ComponentFixture<DatepickerWithFilterAndValidation>;
let testComponent: DatepickerWithFilterAndValidation;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithFilterAndValidation);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should mark input invalid', async(() => {
testComponent.date = new Date(2017, JAN, 1);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.toContain('ng-invalid');
testComponent.date = new Date(2017, JAN, 2);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('input')).nativeElement.classList)
.not.toContain('ng-invalid');
});
});
}));
it('should disable filtered calendar cells', () => {
fixture.detectChanges();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
let cells = document.querySelectorAll('.mat-calendar-body-cell');
expect(cells[0].classList).toContain('mat-calendar-body-disabled');
expect(cells[1].classList).not.toContain('mat-calendar-body-disabled');
});
});
describe('datepicker with change and input events', () => {
let fixture: ComponentFixture<DatepickerWithChangeAndInputEvents>;
let testComponent: DatepickerWithChangeAndInputEvents;
let inputEl: HTMLInputElement;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithChangeAndInputEvents);
fixture.detectChanges();
testComponent = fixture.componentInstance;
inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
spyOn(testComponent, 'onChange');
spyOn(testComponent, 'onInput');
spyOn(testComponent, 'onDateChange');
spyOn(testComponent, 'onDateInput');
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should fire input and dateInput events when user types input', () => {
expect(testComponent.onChange).not.toHaveBeenCalled();
expect(testComponent.onDateChange).not.toHaveBeenCalled();
expect(testComponent.onInput).not.toHaveBeenCalled();
expect(testComponent.onDateInput).not.toHaveBeenCalled();
dispatchFakeEvent(inputEl, 'input');
fixture.detectChanges();
expect(testComponent.onChange).not.toHaveBeenCalled();
expect(testComponent.onDateChange).not.toHaveBeenCalled();
expect(testComponent.onInput).toHaveBeenCalled();
expect(testComponent.onDateInput).toHaveBeenCalled();
});
it('should fire change and dateChange events when user commits typed input', () => {
expect(testComponent.onChange).not.toHaveBeenCalled();
expect(testComponent.onDateChange).not.toHaveBeenCalled();
expect(testComponent.onInput).not.toHaveBeenCalled();
expect(testComponent.onDateInput).not.toHaveBeenCalled();
dispatchFakeEvent(inputEl, 'change');
fixture.detectChanges();
expect(testComponent.onChange).toHaveBeenCalled();
expect(testComponent.onDateChange).toHaveBeenCalled();
expect(testComponent.onInput).not.toHaveBeenCalled();
expect(testComponent.onDateInput).not.toHaveBeenCalled();
});
it('should fire dateChange and dateInput events when user selects calendar date', () => {
expect(testComponent.onChange).not.toHaveBeenCalled();
expect(testComponent.onDateChange).not.toHaveBeenCalled();
expect(testComponent.onInput).not.toHaveBeenCalled();
expect(testComponent.onDateInput).not.toHaveBeenCalled();
testComponent.datepicker.open();
fixture.detectChanges();
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
let cells = document.querySelectorAll('.mat-calendar-body-cell');
dispatchMouseEvent(cells[0], 'click');
fixture.detectChanges();
expect(testComponent.onChange).not.toHaveBeenCalled();
expect(testComponent.onDateChange).toHaveBeenCalled();
expect(testComponent.onInput).not.toHaveBeenCalled();
expect(testComponent.onDateInput).toHaveBeenCalled();
});
});
describe('with ISO 8601 strings as input', () => {
let fixture: ComponentFixture<DatepickerWithISOStrings>;
let testComponent: DatepickerWithISOStrings;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithISOStrings);
testComponent = fixture.componentInstance;
}));
afterEach(async(() => {
testComponent.datepicker.close();
fixture.detectChanges();
}));
it('should coerce ISO strings', async(() => {
expect(() => fixture.detectChanges()).not.toThrow();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(testComponent.datepicker.startAt).toEqual(new Date(2017, JUL, 1));
expect(testComponent.datepickerInput.value).toEqual(new Date(2017, JUN, 1));
expect(testComponent.datepickerInput.min).toEqual(new Date(2017, JAN, 1));
expect(testComponent.datepickerInput.max).toEqual(new Date(2017, DEC, 31));
});
}));
});
describe('with events', () => {
let fixture: ComponentFixture<DatepickerWithEvents>;
let testComponent: DatepickerWithEvents;
beforeEach(async(() => {
fixture = TestBed.createComponent(DatepickerWithEvents);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
it('should dispatch an event when a datepicker is opened', () => {
testComponent.datepicker.open();
fixture.detectChanges();
expect(testComponent.openedSpy).toHaveBeenCalled();
});
it('should dispatch an event when a datepicker is closed', () => {
testComponent.datepicker.open();
fixture.detectChanges();
testComponent.datepicker.close();
fixture.detectChanges();
expect(testComponent.closedSpy).toHaveBeenCalled();
});
});
});
describe('with missing DateAdapter and MAT_DATE_FORMATS', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
NoopAnimationsModule,
ReactiveFormsModule,
],
declarations: [StandardDatepicker],
});
TestBed.compileComponents();
}));
it('should throw when created', () => {
expect(() => TestBed.createComponent(StandardDatepicker))
.toThrowError(/MatDatepicker: No provider found for .*/);
});
});
describe('popup positioning', () => {
let fixture: ComponentFixture<StandardDatepicker>;
let testComponent: StandardDatepicker;
let input: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
NoopAnimationsModule
],
declarations: [StandardDatepicker],
}).compileComponents();
fixture = TestBed.createComponent(StandardDatepicker);
fixture.detectChanges();
testComponent = fixture.componentInstance;
input = fixture.debugElement.query(By.css('input')).nativeElement;
input.style.position = 'fixed';
}));
it('should be below and to the right when there is plenty of space', () => {
input.style.top = input.style.left = '20px';
testComponent.datepicker.open();
fixture.detectChanges();
const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect();
const inputRect = input.getBoundingClientRect();
expect(Math.floor(overlayRect.top))
.toBe(Math.floor(inputRect.bottom), 'Expected popup to align to input bottom.');
expect(Math.floor(overlayRect.left))
.toBe(Math.floor(inputRect.left), 'Expected popup to align to input left.');
});
it('should be above and to the right when there is no space below', () => {
input.style.bottom = input.style.left = '20px';
testComponent.datepicker.open();
fixture.detectChanges();
const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect();
const inputRect = input.getBoundingClientRect();
expect(Math.floor(overlayRect.bottom))
.toBe(Math.floor(inputRect.top), 'Expected popup to align to input top.');
expect(Math.floor(overlayRect.left))
.toBe(Math.floor(inputRect.left), 'Expected popup to align to input left.');
});
it('should be below and to the left when there is no space on the right', () => {
input.style.top = input.style.right = '20px';
testComponent.datepicker.open();
fixture.detectChanges();
const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect();
const inputRect = input.getBoundingClientRect();
expect(Math.floor(overlayRect.top))
.toBe(Math.floor(inputRect.bottom), 'Expected popup to align to input bottom.');
expect(Math.floor(overlayRect.right))
.toBe(Math.floor(inputRect.right), 'Expected popup to align to input right.');
});
it('should be above and to the left when there is no space on the bottom', () => {
input.style.bottom = input.style.right = '20px';
testComponent.datepicker.open();
fixture.detectChanges();
const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect();
const inputRect = input.getBoundingClientRect();
expect(Math.floor(overlayRect.bottom))
.toBe(Math.floor(inputRect.top), 'Expected popup to align to input top.');
expect(Math.floor(overlayRect.right))
.toBe(Math.floor(inputRect.right), 'Expected popup to align to input right.');
});
});
describe('internationalization', () => {
let fixture: ComponentFixture<DatepickerWithi18n>;
let testComponent: DatepickerWithi18n;
let input: HTMLInputElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
NoopAnimationsModule,
NativeDateModule,
FormsModule
],
providers: [{provide: MAT_DATE_LOCALE, useValue: 'de-DE'}],
declarations: [DatepickerWithi18n],
}).compileComponents();
fixture = TestBed.createComponent(DatepickerWithi18n);
fixture.detectChanges();
testComponent = fixture.componentInstance;
input = fixture.nativeElement.querySelector('input') as HTMLInputElement;
}));
it('should have the correct input value even when inverted date format', async(() => {
if (typeof Intl === 'undefined') {
// Skip this test if the internationalization API is not supported in the current
// browser. Browsers like Safari 9 do not support the "Intl" API.
return;
}
const selected = new Date(2017, SEP, 1);
testComponent.date = selected;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
// Normally the proper date format would 01.09.2017, but some browsers seem format the
// date without the leading zero. (e.g. 1.9.2017).
expect(input.value).toMatch(/0?1\.0?9\.2017/);
expect(testComponent.datepickerInput.value).toBe(selected);
});
}));
});
});
({
template: `
<input [matDatepicker]="d" [value]="date">
<mat-datepicker #d [touchUi]="touch" [disabled]="disabled" [opened]="opened"></mat-datepicker>
`,
})
class StandardDatepicker {
opened = false;
touch = false;
disabled = false;
date: Date | null = new Date(2020, JAN, 1);
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
({
template: `
<input [matDatepicker]="d"><input [matDatepicker]="d"><mat-datepicker #d></mat-datepicker>
`,
})
class MultiInputDatepicker {}
({
template: `<mat-datepicker #d></mat-datepicker>`,
})
class NoInputDatepicker {
('d') datepicker: MatDatepicker<Date>;
}
({
template: `
<input [matDatepicker]="d" [value]="date">
<mat-datepicker #d [startAt]="startDate"></mat-datepicker>
`,
})
class DatepickerWithStartAt {
date = new Date(2020, JAN, 1);
startDate = new Date(2010, JAN, 1);
('d') datepicker: MatDatepicker<Date>;
}
({
template: `
<input [matDatepicker]="d" [value]="date">
<mat-datepicker #d startView="year"></mat-datepicker>
`,
})
class DatepickerWithStartView {
date = new Date(2020, JAN, 1);
('d') datepicker: MatDatepicker<Date>;
}
({
template: `
<input [(ngModel)]="selected" [matDatepicker]="d">
<mat-datepicker #d></mat-datepicker>
`,
})
class DatepickerWithNgModel {
selected: Date | null = null;
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
({
template: `
<input [formControl]="formControl" [matDatepicker]="d">
<mat-datepicker-toggle [for]="d"></mat-datepicker-toggle>
<mat-datepicker #d></mat-datepicker>
`,
})
class DatepickerWithFormControl {
formControl = new FormControl();
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
(MatDatepickerToggle) datepickerToggle: MatDatepickerToggle<Date>;
}
({
template: `
<input [matDatepicker]="d">
<mat-datepicker-toggle [for]="d"></mat-datepicker-toggle>
<mat-datepicker #d [touchUi]="touchUI"></mat-datepicker>
`,
})
class DatepickerWithToggle {
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) input: MatDatepickerInput<Date>;
touchUI = true;
}
({
template: `
<mat-form-field>
<input matInput [matDatepicker]="d">
<mat-datepicker #d></mat-datepicker>
</mat-form-field>
`,
})
class FormFieldDatepicker {
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
({
template: `
<input [matDatepicker]="d" [(ngModel)]="date" [min]="minDate" [max]="maxDate">
<mat-datepicker-toggle [for]="d"></mat-datepicker-toggle>
<mat-datepicker #d></mat-datepicker>
`,
})
class DatepickerWithMinAndMaxValidation {
('d') datepicker: MatDatepicker<Date>;
date: Date | null;
minDate = new Date(2010, JAN, 1);
maxDate = new Date(2020, JAN, 1);
}
({
template: `
<input [matDatepicker]="d" [(ngModel)]="date" [matDatepickerFilter]="filter">
<mat-datepicker-toggle [for]="d"></mat-datepicker-toggle>
<mat-datepicker #d [touchUi]="true"></mat-datepicker>
`,
})
class DatepickerWithFilterAndValidation {
('d') datepicker: MatDatepicker<Date>;
date: Date;
filter = (date: Date) => date.getDate() != 1;
}
({
template: `
<input [matDatepicker]="d" (change)="onChange()" (input)="onInput()"
(dateChange)="onDateChange()" (dateInput)="onDateInput()">
<mat-datepicker #d [touchUi]="true"></mat-datepicker>
`
})
class DatepickerWithChangeAndInputEvents {
('d') datepicker: MatDatepicker<Date>;
onChange() {}
onInput() {}
onDateChange() {}
onDateInput() {}
}
({
template: `
<input [matDatepicker]="d" [(ngModel)]="date">
<mat-datepicker #d></mat-datepicker>
`
})
class DatepickerWithi18n {
date: Date | null = new Date(2010, JAN, 1);
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
({
template: `
<input [matDatepicker]="d" [(ngModel)]="value" [min]="min" [max]="max">
<mat-datepicker #d [startAt]="startAt"></mat-datepicker>
`
})
class DatepickerWithISOStrings {
value = new Date(2017, JUN, 1).toISOString();
min = new Date(2017, JAN, 1).toISOString();
max = new Date (2017, DEC, 31).toISOString();
startAt = new Date(2017, JUL, 1).toISOString();
('d') datepicker: MatDatepicker<Date>;
(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
({
template: `
<input [(ngModel)]="selected" [matDatepicker]="d">
<mat-datepicker (opened)="openedSpy()" (closed)="closedSpy()" #d></mat-datepicker>
`,
})
class DatepickerWithEvents {
selected: Date | null = null;
openedSpy = jasmine.createSpy('opened spy');
closedSpy = jasmine.createSpy('closed spy');
('d') datepicker: MatDatepicker<Date>;
}