1372 lines
47 KiB
TypeScript
1372 lines
47 KiB
TypeScript
import {FocusMonitor} from '@angular/cdk/a11y';
|
||
import {Directionality} from '@angular/cdk/bidi';
|
||
import {BACKSPACE, LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
|
||
import {OverlayContainer} from '@angular/cdk/overlay';
|
||
import {dispatchFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing/private';
|
||
import {Component, Directive, ElementRef, Provider, Type, ViewChild} from '@angular/core';
|
||
import {ComponentFixture, TestBed, fakeAsync, flush, inject, tick} from '@angular/core/testing';
|
||
import {
|
||
FormControl,
|
||
FormGroup,
|
||
FormsModule,
|
||
NG_VALIDATORS,
|
||
NgModel,
|
||
ReactiveFormsModule,
|
||
Validator,
|
||
Validators,
|
||
} from '@angular/forms';
|
||
import {ErrorStateMatcher, MatNativeDateModule} from '@angular/material/core';
|
||
import {MatFormField, MatFormFieldModule, MatLabel} from '@angular/material/form-field';
|
||
import {MatInputModule} from '@angular/material/input';
|
||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||
import {Subscription} from 'rxjs';
|
||
import {MatDateRangeInput} from './date-range-input';
|
||
import {MatEndDate, MatStartDate} from './date-range-input-parts';
|
||
import {MatDateRangePicker} from './date-range-picker';
|
||
import {MatDatepickerModule} from './datepicker-module';
|
||
|
||
describe('MatDateRangeInput', () => {
|
||
function createComponent<T>(component: Type<T>, providers: Provider[] = []): ComponentFixture<T> {
|
||
TestBed.configureTestingModule({
|
||
imports: [
|
||
FormsModule,
|
||
MatDatepickerModule,
|
||
MatFormFieldModule,
|
||
MatInputModule,
|
||
NoopAnimationsModule,
|
||
ReactiveFormsModule,
|
||
MatNativeDateModule,
|
||
component,
|
||
],
|
||
providers,
|
||
});
|
||
|
||
return TestBed.createComponent(component);
|
||
}
|
||
|
||
it('should mirror the input value from the start into the mirror element', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const mirror = fixture.nativeElement.querySelector('.mat-date-range-input-mirror');
|
||
const startInput = fixture.componentInstance.start.nativeElement;
|
||
|
||
expect(mirror.textContent).toBe('Start Date');
|
||
|
||
startInput.value = 'hello';
|
||
dispatchFakeEvent(startInput, 'input');
|
||
fixture.detectChanges();
|
||
expect(mirror.textContent).toBe('hello');
|
||
|
||
startInput.value = 'h';
|
||
dispatchFakeEvent(startInput, 'input');
|
||
fixture.detectChanges();
|
||
expect(mirror.textContent).toBe('h');
|
||
|
||
startInput.value = '';
|
||
dispatchFakeEvent(startInput, 'input');
|
||
fixture.detectChanges();
|
||
|
||
expect(mirror.textContent).toBe('Start Date');
|
||
});
|
||
|
||
it('should hide the mirror value from assistive technology', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const mirror = fixture.nativeElement.querySelector('.mat-date-range-input-mirror');
|
||
|
||
expect(mirror.getAttribute('aria-hidden')).toBe('true');
|
||
});
|
||
|
||
it('should be able to customize the separator', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const separator = fixture.nativeElement.querySelector('.mat-date-range-input-separator');
|
||
|
||
expect(separator.textContent).toBe('–');
|
||
|
||
fixture.componentInstance.separator = '/';
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
|
||
expect(separator.textContent).toBe('/');
|
||
});
|
||
|
||
it('should set the proper type on the input elements', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
|
||
expect(fixture.componentInstance.start.nativeElement.getAttribute('type')).toBe('text');
|
||
expect(fixture.componentInstance.end.nativeElement.getAttribute('type')).toBe('text');
|
||
});
|
||
|
||
it('should set the correct role on the range input', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const rangeInput = fixture.nativeElement.querySelector('.mat-date-range-input');
|
||
expect(rangeInput.getAttribute('role')).toBe('group');
|
||
});
|
||
|
||
it('should mark the entire range input as disabled if both inputs are disabled', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {rangeInput, range, start, end} = fixture.componentInstance;
|
||
|
||
expect(rangeInput.disabled).toBe(false);
|
||
expect(start.nativeElement.disabled).toBe(false);
|
||
expect(end.nativeElement.disabled).toBe(false);
|
||
|
||
range.controls.start.disable();
|
||
fixture.detectChanges();
|
||
expect(rangeInput.disabled).toBe(false);
|
||
expect(start.nativeElement.disabled).toBe(true);
|
||
expect(end.nativeElement.disabled).toBe(false);
|
||
|
||
range.controls.end.disable();
|
||
fixture.detectChanges();
|
||
expect(rangeInput.disabled).toBe(true);
|
||
expect(start.nativeElement.disabled).toBe(true);
|
||
expect(end.nativeElement.disabled).toBe(true);
|
||
});
|
||
|
||
it('should disable both inputs if the range is disabled', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
expect(start.nativeElement.disabled).toBe(false);
|
||
expect(end.nativeElement.disabled).toBe(false);
|
||
|
||
fixture.componentInstance.rangeDisabled = true;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(start.nativeElement.disabled).toBe(true);
|
||
expect(end.nativeElement.disabled).toBe(true);
|
||
});
|
||
|
||
it('should hide the placeholders once the start input has a value', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const hideClass = 'mat-date-range-input-hide-placeholders';
|
||
const rangeInput = fixture.nativeElement.querySelector('.mat-date-range-input');
|
||
const startInput = fixture.componentInstance.start.nativeElement;
|
||
|
||
expect(rangeInput.classList).not.toContain(hideClass);
|
||
|
||
startInput.value = 'hello';
|
||
dispatchFakeEvent(startInput, 'input');
|
||
fixture.detectChanges();
|
||
|
||
expect(rangeInput.classList).toContain(hideClass);
|
||
});
|
||
|
||
it('should point the range input aria-labelledby to the form field label', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const labelId = fixture.nativeElement.querySelector('label').id;
|
||
const rangeInput = fixture.nativeElement.querySelector('.mat-date-range-input');
|
||
|
||
expect(labelId).toBeTruthy();
|
||
expect(rangeInput.getAttribute('aria-labelledby')).toBe(labelId);
|
||
});
|
||
|
||
it('should point the range input aria-labelledby to the form field hint element', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const labelId = fixture.nativeElement.querySelector('.mat-mdc-form-field-hint').id;
|
||
const rangeInput = fixture.nativeElement.querySelector('.mat-date-range-input');
|
||
|
||
expect(labelId).toBeTruthy();
|
||
expect(rangeInput.getAttribute('aria-describedby')).toBe(labelId);
|
||
});
|
||
|
||
it('should not set aria-labelledby if the form field does not have a label', () => {
|
||
const fixture = createComponent(RangePickerNoLabel);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
expect(start.nativeElement.getAttribute('aria-labelledby')).toBeFalsy();
|
||
expect(end.nativeElement.getAttribute('aria-labelledby')).toBeFalsy();
|
||
});
|
||
|
||
it('should set aria-labelledby of the overlay to the form field label', fakeAsync(() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
|
||
const label: HTMLElement = fixture.nativeElement.querySelector('label');
|
||
expect(label).toBeTruthy();
|
||
expect(label.getAttribute('id')).toBeTruthy();
|
||
|
||
fixture.componentInstance.rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
const popup = document.querySelector('.cdk-overlay-pane .mat-datepicker-content-container')!;
|
||
expect(popup).toBeTruthy();
|
||
expect(popup.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));
|
||
}));
|
||
|
||
it('should float the form field label when either input is focused', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {rangeInput, end} = fixture.componentInstance;
|
||
let focusMonitor: FocusMonitor;
|
||
|
||
inject([FocusMonitor], (fm: FocusMonitor) => {
|
||
focusMonitor = fm;
|
||
})();
|
||
|
||
expect(rangeInput.shouldLabelFloat).toBe(false);
|
||
|
||
focusMonitor!.focusVia(end, 'keyboard');
|
||
fixture.detectChanges();
|
||
|
||
expect(rangeInput.shouldLabelFloat).toBe(true);
|
||
});
|
||
|
||
it('should float the form field label when either input has a value', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {rangeInput, end} = fixture.componentInstance;
|
||
|
||
expect(rangeInput.shouldLabelFloat).toBe(false);
|
||
|
||
end.nativeElement.value = 'hello';
|
||
dispatchFakeEvent(end.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
|
||
expect(rangeInput.shouldLabelFloat).toBe(true);
|
||
});
|
||
|
||
it('should consider the entire input as empty if both inputs are empty', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {rangeInput, end} = fixture.componentInstance;
|
||
|
||
expect(rangeInput.empty).toBe(true);
|
||
|
||
end.nativeElement.value = 'hello';
|
||
dispatchFakeEvent(end.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
|
||
expect(rangeInput.empty).toBe(false);
|
||
});
|
||
|
||
it('should mark the range controls as invalid if the start value is after the end value', fakeAsync(() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
tick();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
|
||
// The default error state matcher only checks if the controls have been touched.
|
||
// Set it manually here so we can assert `rangeInput.errorState` correctly.
|
||
fixture.componentInstance.range.markAllAsTouched();
|
||
expect(fixture.componentInstance.rangeInput.errorState).toBe(false);
|
||
expect(start.errors?.['matStartDateInvalid']).toBeFalsy();
|
||
expect(end.errors?.['matEndDateInvalid']).toBeFalsy();
|
||
|
||
start.setValue(new Date(2020, 2, 2));
|
||
end.setValue(new Date(2020, 1, 2));
|
||
fixture.detectChanges();
|
||
|
||
expect(fixture.componentInstance.rangeInput.errorState).toBe(true);
|
||
expect(start.errors?.['matStartDateInvalid']).toBeTruthy();
|
||
expect(end.errors?.['matEndDateInvalid']).toBeTruthy();
|
||
|
||
end.setValue(new Date(2020, 3, 2));
|
||
fixture.detectChanges();
|
||
|
||
expect(fixture.componentInstance.rangeInput.errorState).toBe(false);
|
||
expect(start.errors?.['matStartDateInvalid']).toBeFalsy();
|
||
expect(end.errors?.['matEndDateInvalid']).toBeFalsy();
|
||
}));
|
||
|
||
it('should pass the minimum date from the range input to the inner inputs', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.minDate = new Date(2020, 3, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
|
||
expect(start.errors?.['matDatepickerMin']).toBeFalsy();
|
||
expect(end.errors?.['matDatepickerMin']).toBeFalsy();
|
||
|
||
const date = new Date(2020, 2, 2);
|
||
start.setValue(date);
|
||
end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.errors?.['matDatepickerMin']).toBeTruthy();
|
||
expect(end.errors?.['matDatepickerMin']).toBeTruthy();
|
||
});
|
||
|
||
it('should pass the maximum date from the range input to the inner inputs', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.maxDate = new Date(2020, 1, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
|
||
expect(start.errors?.['matDatepickerMax']).toBeFalsy();
|
||
expect(end.errors?.['matDatepickerMax']).toBeFalsy();
|
||
|
||
const date = new Date(2020, 2, 2);
|
||
start.setValue(date);
|
||
end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.errors?.['matDatepickerMax']).toBeTruthy();
|
||
expect(end.errors?.['matDatepickerMax']).toBeTruthy();
|
||
});
|
||
|
||
it('should pass the date filter function from the range input to the inner inputs', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.dateFilter = () => false;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
|
||
expect(start.errors?.['matDatepickerFilter']).toBeFalsy();
|
||
expect(end.errors?.['matDatepickerFilter']).toBeFalsy();
|
||
|
||
const date = new Date(2020, 2, 2);
|
||
start.setValue(date);
|
||
end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.errors?.['matDatepickerFilter']).toBeTruthy();
|
||
expect(end.errors?.['matDatepickerFilter']).toBeTruthy();
|
||
});
|
||
|
||
it('should should revalidate when a new date filter function is assigned', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
const date = new Date(2020, 2, 2);
|
||
start.setValue(date);
|
||
end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
const spy = jasmine.createSpy('change spy');
|
||
const subscription = new Subscription();
|
||
subscription.add(start.valueChanges.subscribe(spy));
|
||
subscription.add(end.valueChanges.subscribe(spy));
|
||
|
||
fixture.componentInstance.dateFilter = () => false;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(2);
|
||
|
||
fixture.componentInstance.dateFilter = () => true;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(4);
|
||
|
||
subscription.unsubscribe();
|
||
});
|
||
|
||
it(
|
||
'should not dispatch the change event if a new filter function with the same result ' +
|
||
'is assigned',
|
||
() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
const date = new Date(2020, 2, 2);
|
||
start.setValue(date);
|
||
end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
const spy = jasmine.createSpy('change spy');
|
||
const subscription = new Subscription();
|
||
subscription.add(start.valueChanges.subscribe(spy));
|
||
subscription.add(end.valueChanges.subscribe(spy));
|
||
|
||
fixture.componentInstance.dateFilter = () => false;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(2);
|
||
|
||
fixture.componentInstance.dateFilter = () => false;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(2);
|
||
|
||
subscription.unsubscribe();
|
||
},
|
||
);
|
||
|
||
it('should throw if there is no start input', () => {
|
||
expect(() => {
|
||
const fixture = createComponent(RangePickerNoStart);
|
||
fixture.detectChanges();
|
||
}).toThrowError('mat-date-range-input must contain a matStartDate input');
|
||
});
|
||
|
||
it('should throw if there is no end input', () => {
|
||
expect(() => {
|
||
const fixture = createComponent(RangePickerNoEnd);
|
||
fixture.detectChanges();
|
||
}).toThrowError('mat-date-range-input must contain a matEndDate input');
|
||
});
|
||
|
||
it('should focus the start input when clicking on the form field', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const startInput = fixture.componentInstance.start.nativeElement;
|
||
const formFieldContainer = fixture.nativeElement.querySelector('.mat-mdc-text-field-wrapper');
|
||
|
||
spyOn(startInput, 'focus').and.callThrough();
|
||
|
||
formFieldContainer.click();
|
||
fixture.detectChanges();
|
||
|
||
expect(startInput.focus).toHaveBeenCalled();
|
||
});
|
||
|
||
it('should focus the end input when clicking on the form field when start has a value', fakeAsync(() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
tick();
|
||
const endInput = fixture.componentInstance.end.nativeElement;
|
||
const formFieldContainer = fixture.nativeElement.querySelector('.mat-mdc-text-field-wrapper');
|
||
|
||
spyOn(endInput, 'focus').and.callThrough();
|
||
|
||
fixture.componentInstance.range.controls.start.setValue(new Date());
|
||
fixture.detectChanges();
|
||
|
||
formFieldContainer.click();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
expect(endInput.focus).toHaveBeenCalled();
|
||
}));
|
||
|
||
it('should revalidate if a validation field changes', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.minDate = new Date(2020, 3, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
|
||
const date = new Date(2020, 2, 2);
|
||
start.setValue(date);
|
||
end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.errors?.['matDatepickerMin']).toBeTruthy();
|
||
expect(end.errors?.['matDatepickerMin']).toBeTruthy();
|
||
|
||
fixture.componentInstance.minDate = new Date(2019, 3, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
|
||
expect(start.errors?.['matDatepickerMin']).toBeFalsy();
|
||
expect(end.errors?.['matDatepickerMin']).toBeFalsy();
|
||
});
|
||
|
||
it('should set the formatted date value as the input value', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.minDate = new Date(2020, 3, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
const date = new Date(2020, 1, 2);
|
||
const {start, end, range} = fixture.componentInstance;
|
||
|
||
range.controls.start.setValue(date);
|
||
range.controls.end.setValue(date);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.nativeElement.value).toBe('2/2/2020');
|
||
expect(end.nativeElement.value).toBe('2/2/2020');
|
||
});
|
||
|
||
it('should parse the value typed into an input to a date', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const expectedDate = new Date(2020, 1, 2);
|
||
const {start, end, range} = fixture.componentInstance;
|
||
|
||
start.nativeElement.value = '2/2/2020';
|
||
dispatchFakeEvent(start.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
expect(range.controls.start.value).toEqual(expectedDate);
|
||
|
||
end.nativeElement.value = '2/2/2020';
|
||
dispatchFakeEvent(end.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
expect(range.controls.end.value).toEqual(expectedDate);
|
||
});
|
||
|
||
it('should set the min and max attributes on inputs based on the values from the wrapper', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.minDate = new Date(2020, 1, 2);
|
||
fixture.componentInstance.maxDate = new Date(2020, 1, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
// Use `toContain` for the asserts here, because different browsers format the value
|
||
// differently and we only care that some kind of date value made it to the attribute.
|
||
expect(start.nativeElement.getAttribute('min')).toContain('2020');
|
||
expect(start.nativeElement.getAttribute('max')).toContain('2020');
|
||
|
||
expect(end.nativeElement.getAttribute('min')).toContain('2020');
|
||
expect(end.nativeElement.getAttribute('max')).toContain('2020');
|
||
});
|
||
|
||
it('should pass the range input value through to the calendar', fakeAsync(() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
let overlayContainerElement: HTMLElement;
|
||
start.setValue(new Date(2020, 1, 2));
|
||
end.setValue(new Date(2020, 1, 5));
|
||
inject([OverlayContainer], (overlayContainer: OverlayContainer) => {
|
||
overlayContainerElement = overlayContainer.getContainerElement();
|
||
})();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
fixture.componentInstance.rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
const rangeTexts = Array.from(
|
||
overlayContainerElement!.querySelectorAll(
|
||
[
|
||
'.mat-calendar-body-range-start',
|
||
'.mat-calendar-body-in-range',
|
||
'.mat-calendar-body-range-end',
|
||
].join(','),
|
||
),
|
||
).map(cell => cell.textContent!.trim());
|
||
|
||
expect(rangeTexts).toEqual(['2', '3', '4', '5']);
|
||
}));
|
||
|
||
it("should have aria-desciredby on start and end date cells that point to the <input/>'s accessible name", fakeAsync(() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
let overlayContainerElement: HTMLElement;
|
||
start.setValue(new Date(2020, 1, 2));
|
||
end.setValue(new Date(2020, 1, 5));
|
||
inject([OverlayContainer], (overlayContainer: OverlayContainer) => {
|
||
overlayContainerElement = overlayContainer.getContainerElement();
|
||
})();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
fixture.componentInstance.rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
const rangeStart = overlayContainerElement!.querySelector('.mat-calendar-body-range-start');
|
||
const rangeEnd = overlayContainerElement!.querySelector('.mat-calendar-body-range-end');
|
||
|
||
// query for targets of `aria-describedby`. Query from document instead of fixture.nativeElement as calendar UI is rendered in an overlay.
|
||
const rangeStartDescriptions = Array.from(
|
||
document.querySelectorAll(
|
||
rangeStart!
|
||
.getAttribute('aria-describedby')!
|
||
.split(/\s+/g)
|
||
.map(x => `#${x}`)
|
||
.join(' '),
|
||
),
|
||
);
|
||
const rangeEndDescriptions = Array.from(
|
||
document.querySelectorAll(
|
||
rangeEnd!
|
||
.getAttribute('aria-describedby')!
|
||
.split(/\s+/g)
|
||
.map(x => `#${x}`)
|
||
.join(' '),
|
||
),
|
||
);
|
||
|
||
expect(rangeStartDescriptions)
|
||
.withContext('target of aria-descriedby should exist')
|
||
.not.toBeNull();
|
||
expect(rangeEndDescriptions)
|
||
.withContext('target of aria-descriedby should exist')
|
||
.not.toBeNull();
|
||
expect(
|
||
rangeStartDescriptions
|
||
.map(x => x.textContent)
|
||
.join(' ')
|
||
.trim(),
|
||
).toEqual('Start date');
|
||
expect(
|
||
rangeEndDescriptions
|
||
.map(x => x.textContent)
|
||
.join(' ')
|
||
.trim(),
|
||
).toEqual('End date');
|
||
}));
|
||
|
||
it('should pass the comparison range through to the calendar', fakeAsync(() => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
let overlayContainerElement: HTMLElement;
|
||
|
||
// Set startAt to guarantee that the calendar opens on the proper month.
|
||
fixture.componentInstance.comparisonStart = fixture.componentInstance.startAt = new Date(
|
||
2020,
|
||
1,
|
||
2,
|
||
);
|
||
fixture.componentInstance.comparisonEnd = new Date(2020, 1, 5);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
inject([OverlayContainer], (overlayContainer: OverlayContainer) => {
|
||
overlayContainerElement = overlayContainer.getContainerElement();
|
||
})();
|
||
fixture.detectChanges();
|
||
|
||
fixture.componentInstance.rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
const rangeTexts = Array.from(
|
||
overlayContainerElement!.querySelectorAll(
|
||
[
|
||
'.mat-calendar-body-comparison-start',
|
||
'.mat-calendar-body-in-comparison-range',
|
||
'.mat-calendar-body-comparison-end',
|
||
].join(','),
|
||
),
|
||
).map(cell => cell.textContent!.trim());
|
||
|
||
expect(rangeTexts).toEqual(['2', '3', '4', '5']);
|
||
}));
|
||
|
||
it('should preserve the preselected values when assigning through ngModel', fakeAsync(() => {
|
||
const start = new Date(2020, 1, 2);
|
||
const end = new Date(2020, 1, 2);
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
fixture.componentInstance.start = start;
|
||
fixture.componentInstance.end = end;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
tick();
|
||
fixture.detectChanges();
|
||
|
||
expect(fixture.componentInstance.start).toBe(start);
|
||
expect(fixture.componentInstance.end).toBe(end);
|
||
}));
|
||
|
||
it('should preserve the values when assigning both together through ngModel', fakeAsync(() => {
|
||
const assignAndAssert = (start: Date, end: Date) => {
|
||
fixture.componentInstance.start = start;
|
||
fixture.componentInstance.end = end;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
tick();
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.start).toBe(start);
|
||
expect(fixture.componentInstance.end).toBe(end);
|
||
};
|
||
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
fixture.detectChanges();
|
||
|
||
assignAndAssert(new Date(2020, 1, 2), new Date(2020, 1, 5));
|
||
assignAndAssert(new Date(2020, 2, 2), new Date(2020, 2, 5));
|
||
}));
|
||
|
||
it('should not be dirty on init when there is no value', fakeAsync(() => {
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
fixture.detectChanges();
|
||
flush();
|
||
const {startModel, endModel} = fixture.componentInstance;
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(startModel.touched).toBe(false);
|
||
expect(endModel.dirty).toBe(false);
|
||
expect(endModel.touched).toBe(false);
|
||
}));
|
||
|
||
it('should not be dirty on init when there is a value', fakeAsync(() => {
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
fixture.componentInstance.start = new Date(2020, 1, 2);
|
||
fixture.componentInstance.end = new Date(2020, 2, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
flush();
|
||
const {startModel, endModel} = fixture.componentInstance;
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(startModel.touched).toBe(false);
|
||
expect(endModel.dirty).toBe(false);
|
||
expect(endModel.touched).toBe(false);
|
||
}));
|
||
|
||
it('should mark the input as dirty once the user types in it', fakeAsync(() => {
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
fixture.componentInstance.start = new Date(2020, 1, 2);
|
||
fixture.componentInstance.end = new Date(2020, 2, 2);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
flush();
|
||
const {startModel, endModel, startInput, endInput} = fixture.componentInstance;
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(endModel.dirty).toBe(false);
|
||
|
||
endInput.nativeElement.value = '30/12/2020';
|
||
dispatchFakeEvent(endInput.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
flush();
|
||
fixture.detectChanges();
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(endModel.dirty).toBe(true);
|
||
|
||
startInput.nativeElement.value = '12/12/2020';
|
||
dispatchFakeEvent(startInput.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
flush();
|
||
fixture.detectChanges();
|
||
|
||
expect(startModel.dirty).toBe(true);
|
||
expect(endModel.dirty).toBe(true);
|
||
}));
|
||
|
||
it('should mark both inputs as touched when the range picker is closed', fakeAsync(() => {
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
fixture.detectChanges();
|
||
flush();
|
||
const {startModel, endModel, rangePicker} = fixture.componentInstance;
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(startModel.touched).toBe(false);
|
||
expect(endModel.dirty).toBe(false);
|
||
expect(endModel.touched).toBe(false);
|
||
|
||
rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
flush();
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(startModel.touched).toBe(false);
|
||
expect(endModel.dirty).toBe(false);
|
||
expect(endModel.touched).toBe(false);
|
||
|
||
rangePicker.close();
|
||
fixture.detectChanges();
|
||
flush();
|
||
|
||
expect(startModel.dirty).toBe(false);
|
||
expect(startModel.touched).toBe(true);
|
||
expect(endModel.dirty).toBe(false);
|
||
expect(endModel.touched).toBe(true);
|
||
}));
|
||
|
||
it('should move focus to the start input when pressing backspace on an empty end input', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
spyOn(start.nativeElement, 'focus').and.callThrough();
|
||
|
||
end.nativeElement.value = '';
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', BACKSPACE);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.nativeElement.focus).toHaveBeenCalled();
|
||
});
|
||
|
||
it('should move not move focus when pressing backspace if the end input has a value', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
spyOn(start.nativeElement, 'focus').and.callThrough();
|
||
|
||
end.nativeElement.value = '10/10/2020';
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', BACKSPACE);
|
||
fixture.detectChanges();
|
||
|
||
expect(start.nativeElement.focus).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('moves focus between fields with arrow keys when cursor is at edge (LTR)', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
start.nativeElement.value = '09/10/2020';
|
||
end.nativeElement.value = '10/10/2020';
|
||
|
||
start.nativeElement.focus();
|
||
start.nativeElement.setSelectionRange(9, 9);
|
||
dispatchKeyboardEvent(start.nativeElement, 'keydown', RIGHT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(start.nativeElement);
|
||
|
||
start.nativeElement.setSelectionRange(10, 10);
|
||
dispatchKeyboardEvent(start.nativeElement, 'keydown', LEFT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(start.nativeElement);
|
||
|
||
start.nativeElement.setSelectionRange(10, 10);
|
||
dispatchKeyboardEvent(start.nativeElement, 'keydown', RIGHT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(end.nativeElement);
|
||
|
||
end.nativeElement.setSelectionRange(1, 1);
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', LEFT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(end.nativeElement);
|
||
|
||
end.nativeElement.setSelectionRange(0, 0);
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', RIGHT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(end.nativeElement);
|
||
|
||
end.nativeElement.setSelectionRange(0, 0);
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', LEFT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(start.nativeElement);
|
||
});
|
||
|
||
it('moves focus between fields with arrow keys when cursor is at edge (RTL)', () => {
|
||
class RTL extends Directionality {
|
||
override readonly value = 'rtl';
|
||
}
|
||
const fixture = createComponent(StandardRangePicker, [
|
||
{
|
||
provide: Directionality,
|
||
useFactory: () => new RTL(null),
|
||
},
|
||
]);
|
||
fixture.detectChanges();
|
||
const {start, end} = fixture.componentInstance;
|
||
|
||
start.nativeElement.value = '09/10/2020';
|
||
end.nativeElement.value = '10/10/2020';
|
||
|
||
start.nativeElement.focus();
|
||
start.nativeElement.setSelectionRange(9, 9);
|
||
dispatchKeyboardEvent(start.nativeElement, 'keydown', LEFT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(start.nativeElement);
|
||
|
||
start.nativeElement.setSelectionRange(10, 10);
|
||
dispatchKeyboardEvent(start.nativeElement, 'keydown', RIGHT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(start.nativeElement);
|
||
|
||
start.nativeElement.setSelectionRange(10, 10);
|
||
dispatchKeyboardEvent(start.nativeElement, 'keydown', LEFT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(end.nativeElement);
|
||
|
||
end.nativeElement.setSelectionRange(1, 1);
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', RIGHT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(end.nativeElement);
|
||
|
||
end.nativeElement.setSelectionRange(0, 0);
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', LEFT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(end.nativeElement);
|
||
|
||
end.nativeElement.setSelectionRange(0, 0);
|
||
dispatchKeyboardEvent(end.nativeElement, 'keydown', RIGHT_ARROW);
|
||
fixture.detectChanges();
|
||
expect(document.activeElement).toBe(start.nativeElement);
|
||
});
|
||
|
||
it('should be able to get the input placeholder', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
expect(fixture.componentInstance.rangeInput.placeholder).toBe('Start Date – End Date');
|
||
});
|
||
|
||
it('should emit to the stateChanges stream when typing a value into an input', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {start, rangeInput} = fixture.componentInstance;
|
||
const spy = jasmine.createSpy('stateChanges spy');
|
||
const subscription = rangeInput.stateChanges.subscribe(spy);
|
||
|
||
start.nativeElement.value = '10/10/2020';
|
||
dispatchFakeEvent(start.nativeElement, 'input');
|
||
fixture.detectChanges();
|
||
|
||
expect(spy).toHaveBeenCalled();
|
||
subscription.unsubscribe();
|
||
});
|
||
|
||
it('should emit to the dateChange event only when typing in the relevant input', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {startInput, endInput, start, end} = fixture.componentInstance;
|
||
const startSpy = jasmine.createSpy('matStartDate spy');
|
||
const endSpy = jasmine.createSpy('matEndDate spy');
|
||
const startSubscription = startInput.dateChange.subscribe(startSpy);
|
||
const endSubscription = endInput.dateChange.subscribe(endSpy);
|
||
|
||
start.nativeElement.value = '10/10/2020';
|
||
dispatchFakeEvent(start.nativeElement, 'change');
|
||
fixture.detectChanges();
|
||
|
||
expect(startSpy).toHaveBeenCalledTimes(1);
|
||
expect(endSpy).not.toHaveBeenCalled();
|
||
|
||
start.nativeElement.value = '11/10/2020';
|
||
dispatchFakeEvent(start.nativeElement, 'change');
|
||
fixture.detectChanges();
|
||
|
||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||
expect(endSpy).not.toHaveBeenCalled();
|
||
|
||
end.nativeElement.value = '11/10/2020';
|
||
dispatchFakeEvent(end.nativeElement, 'change');
|
||
fixture.detectChanges();
|
||
|
||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||
expect(endSpy).toHaveBeenCalledTimes(1);
|
||
|
||
end.nativeElement.value = '12/10/2020';
|
||
dispatchFakeEvent(end.nativeElement, 'change');
|
||
fixture.detectChanges();
|
||
|
||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||
expect(endSpy).toHaveBeenCalledTimes(2);
|
||
|
||
startSubscription.unsubscribe();
|
||
endSubscription.unsubscribe();
|
||
});
|
||
|
||
it('should emit to the dateChange event when setting the value programmatically', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
const {startInput, endInput} = fixture.componentInstance;
|
||
const {start, end} = fixture.componentInstance.range.controls;
|
||
const startSpy = jasmine.createSpy('matStartDate spy');
|
||
const endSpy = jasmine.createSpy('matEndDate spy');
|
||
const startSubscription = startInput.dateChange.subscribe(startSpy);
|
||
const endSubscription = endInput.dateChange.subscribe(endSpy);
|
||
|
||
start.setValue(new Date(2020, 1, 2));
|
||
end.setValue(new Date(2020, 2, 2));
|
||
fixture.detectChanges();
|
||
|
||
expect(startSpy).not.toHaveBeenCalled();
|
||
expect(endSpy).not.toHaveBeenCalled();
|
||
|
||
start.setValue(new Date(2020, 3, 2));
|
||
end.setValue(new Date(2020, 4, 2));
|
||
fixture.detectChanges();
|
||
|
||
expect(startSpy).not.toHaveBeenCalled();
|
||
expect(endSpy).not.toHaveBeenCalled();
|
||
|
||
startSubscription.unsubscribe();
|
||
endSubscription.unsubscribe();
|
||
});
|
||
|
||
it('should not trigger validators if new date object for same date is set for `min`', () => {
|
||
const fixture = createComponent(RangePickerWithCustomValidator, [CustomValidator]);
|
||
fixture.detectChanges();
|
||
const minDate = new Date(2019, 0, 1);
|
||
const validator = fixture.componentInstance.validator;
|
||
|
||
validator.validate.calls.reset();
|
||
fixture.componentInstance.min = minDate;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(validator.validate).toHaveBeenCalledTimes(1);
|
||
|
||
fixture.componentInstance.min = new Date(minDate);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
|
||
expect(validator.validate).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
it('should not trigger validators if new date object for same date is set for `max`', () => {
|
||
const fixture = createComponent(RangePickerWithCustomValidator, [CustomValidator]);
|
||
fixture.detectChanges();
|
||
const maxDate = new Date(2120, 0, 1);
|
||
const validator = fixture.componentInstance.validator;
|
||
|
||
validator.validate.calls.reset();
|
||
fixture.componentInstance.max = maxDate;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(validator.validate).toHaveBeenCalledTimes(1);
|
||
|
||
fixture.componentInstance.max = new Date(maxDate);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
|
||
expect(validator.validate).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
it('should not emit to `stateChanges` if new date object for same date is set for `min`', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
|
||
const minDate = new Date(2019, 0, 1);
|
||
const spy = jasmine.createSpy('stateChanges spy');
|
||
const subscription = fixture.componentInstance.rangeInput.stateChanges.subscribe(spy);
|
||
|
||
fixture.componentInstance.minDate = minDate;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
|
||
fixture.componentInstance.minDate = new Date(minDate);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
|
||
subscription.unsubscribe();
|
||
});
|
||
|
||
it('should not emit to `stateChanges` if new date object for same date is set for `max`', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.detectChanges();
|
||
|
||
const maxDate = new Date(2120, 0, 1);
|
||
const spy = jasmine.createSpy('stateChanges spy');
|
||
const subscription = fixture.componentInstance.rangeInput.stateChanges.subscribe(spy);
|
||
|
||
fixture.componentInstance.maxDate = maxDate;
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
|
||
fixture.componentInstance.maxDate = new Date(maxDate);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
|
||
subscription.unsubscribe();
|
||
});
|
||
|
||
it('should be able to pass in a different error state matcher through an input', () => {
|
||
const fixture = createComponent(RangePickerErrorStateMatcher);
|
||
fixture.detectChanges();
|
||
const {startInput, endInput, matcher} = fixture.componentInstance;
|
||
|
||
expect(startInput.errorStateMatcher).toBe(matcher);
|
||
expect(endInput.errorStateMatcher).toBe(matcher);
|
||
});
|
||
|
||
it('should only update model for input that changed', fakeAsync(() => {
|
||
const fixture = createComponent(RangePickerNgModel);
|
||
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
expect(fixture.componentInstance.startDateModelChangeCount).toBe(0);
|
||
expect(fixture.componentInstance.endDateModelChangeCount).toBe(0);
|
||
|
||
fixture.componentInstance.rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
const fromDate = new Date(2020, 0, 1);
|
||
const toDate = new Date(2020, 0, 2);
|
||
fixture.componentInstance.rangePicker.select(fromDate);
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
expect(fixture.componentInstance.startDateModelChangeCount)
|
||
.withContext('Start Date set once')
|
||
.toBe(1);
|
||
expect(fixture.componentInstance.endDateModelChangeCount)
|
||
.withContext('End Date not set')
|
||
.toBe(0);
|
||
|
||
fixture.componentInstance.rangePicker.select(toDate);
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
expect(fixture.componentInstance.startDateModelChangeCount)
|
||
.withContext('Start Date unchanged (set once)')
|
||
.toBe(1);
|
||
expect(fixture.componentInstance.endDateModelChangeCount)
|
||
.withContext('End Date set once')
|
||
.toBe(1);
|
||
|
||
fixture.componentInstance.rangePicker.open();
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
const fromDate2 = new Date(2021, 0, 1);
|
||
const toDate2 = new Date(2021, 0, 2);
|
||
fixture.componentInstance.rangePicker.select(fromDate2);
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
expect(fixture.componentInstance.startDateModelChangeCount)
|
||
.withContext('Start Date set twice')
|
||
.toBe(2);
|
||
expect(fixture.componentInstance.endDateModelChangeCount)
|
||
.withContext('End Date set twice (nulled)')
|
||
.toBe(2);
|
||
|
||
fixture.componentInstance.rangePicker.select(toDate2);
|
||
fixture.detectChanges();
|
||
tick();
|
||
|
||
expect(fixture.componentInstance.startDateModelChangeCount)
|
||
.withContext('Start Date unchanged (set twice)')
|
||
.toBe(2);
|
||
expect(fixture.componentInstance.endDateModelChangeCount)
|
||
.withContext('End date set three times')
|
||
.toBe(3);
|
||
}));
|
||
|
||
it('should mark the range picker as required when the entire group has the required validator', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.range = new FormGroup(
|
||
{
|
||
start: new FormControl<Date | null>(null),
|
||
end: new FormControl<Date | null>(null),
|
||
},
|
||
Validators.required,
|
||
);
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
|
||
expect(fixture.componentInstance.rangeInput.required).toBe(true);
|
||
});
|
||
|
||
it('should mark the range picker as required when one part is required', () => {
|
||
const fixture = createComponent(StandardRangePicker);
|
||
fixture.componentInstance.range = new FormGroup({
|
||
start: new FormControl<Date | null>(null, Validators.required),
|
||
end: new FormControl<Date | null>(null),
|
||
});
|
||
fixture.changeDetectorRef.markForCheck();
|
||
fixture.detectChanges();
|
||
|
||
expect(fixture.componentInstance.rangeInput.required).toBe(true);
|
||
});
|
||
});
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field hintLabel="Pick between a start and an end">
|
||
<mat-label>Enter a date</mat-label>
|
||
<mat-date-range-input
|
||
[rangePicker]="rangePicker"
|
||
[formGroup]="range"
|
||
[disabled]="rangeDisabled"
|
||
[separator]="separator"
|
||
[min]="minDate"
|
||
[max]="maxDate"
|
||
[dateFilter]="dateFilter"
|
||
[comparisonStart]="comparisonStart"
|
||
[comparisonEnd]="comparisonEnd">
|
||
<input #start formControlName="start" matStartDate aria-label="Start date"
|
||
placeholder="Start Date"/>
|
||
<input #end formControlName="end" matEndDate aria-labelledby="end-date-label-1 end-date-label-2"
|
||
placeholder="End Date"/>
|
||
</mat-date-range-input>
|
||
<label id='end-date-label-1' class="cdk-visually-hidden">End</label>
|
||
<label id='end-date-label-2' class="cdk-visually-hidden">date</label>
|
||
|
||
<mat-date-range-picker
|
||
[startAt]="startAt"
|
||
#rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [
|
||
MatDateRangeInput,
|
||
MatStartDate,
|
||
MatEndDate,
|
||
MatFormField,
|
||
MatLabel,
|
||
MatDateRangePicker,
|
||
ReactiveFormsModule,
|
||
],
|
||
})
|
||
class StandardRangePicker {
|
||
@ViewChild('start') start: ElementRef<HTMLInputElement>;
|
||
@ViewChild('end') end: ElementRef<HTMLInputElement>;
|
||
@ViewChild(MatStartDate) startInput: MatStartDate<Date>;
|
||
@ViewChild(MatEndDate) endInput: MatEndDate<Date>;
|
||
@ViewChild(MatDateRangeInput) rangeInput: MatDateRangeInput<Date>;
|
||
@ViewChild(MatDateRangePicker) rangePicker: MatDateRangePicker<Date>;
|
||
separator = '–';
|
||
rangeDisabled = false;
|
||
minDate: Date | null = null;
|
||
maxDate: Date | null = null;
|
||
comparisonStart: Date | null = null;
|
||
comparisonEnd: Date | null = null;
|
||
startAt: Date | null = null;
|
||
dateFilter = () => true;
|
||
|
||
range = new FormGroup({
|
||
start: new FormControl<Date | null>(null),
|
||
end: new FormControl<Date | null>(null),
|
||
});
|
||
}
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field>
|
||
<mat-date-range-input [rangePicker]="rangePicker">
|
||
<input matEndDate/>
|
||
</mat-date-range-input>
|
||
|
||
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [MatDateRangeInput, MatStartDate, MatEndDate, MatFormField, MatDateRangePicker],
|
||
})
|
||
class RangePickerNoStart {}
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field>
|
||
<mat-date-range-input [rangePicker]="rangePicker">
|
||
<input matStartDate/>
|
||
</mat-date-range-input>
|
||
|
||
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [MatDateRangeInput, MatStartDate, MatEndDate, MatFormField, MatDateRangePicker],
|
||
})
|
||
class RangePickerNoEnd {}
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field>
|
||
<mat-date-range-input [rangePicker]="rangePicker">
|
||
<input matStartDate [(ngModel)]="start"/>
|
||
<input matEndDate [(ngModel)]="end"/>
|
||
</mat-date-range-input>
|
||
|
||
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [
|
||
MatDateRangeInput,
|
||
MatStartDate,
|
||
MatEndDate,
|
||
MatFormField,
|
||
MatDateRangePicker,
|
||
FormsModule,
|
||
],
|
||
})
|
||
class RangePickerNgModel {
|
||
@ViewChild(MatStartDate, {read: NgModel}) startModel: NgModel;
|
||
@ViewChild(MatEndDate, {read: NgModel}) endModel: NgModel;
|
||
@ViewChild(MatStartDate, {read: ElementRef}) startInput: ElementRef<HTMLInputElement>;
|
||
@ViewChild(MatEndDate, {read: ElementRef}) endInput: ElementRef<HTMLInputElement>;
|
||
@ViewChild(MatDateRangePicker) rangePicker: MatDateRangePicker<Date>;
|
||
private _start: Date | null = null;
|
||
get start(): Date | null {
|
||
return this._start;
|
||
}
|
||
set start(aStart: Date | null) {
|
||
this.startDateModelChangeCount++;
|
||
this._start = aStart;
|
||
}
|
||
private _end: Date | null = null;
|
||
get end(): Date | null {
|
||
return this._end;
|
||
}
|
||
set end(anEnd: Date | null) {
|
||
this.endDateModelChangeCount++;
|
||
this._end = anEnd;
|
||
}
|
||
startDateModelChangeCount = 0;
|
||
endDateModelChangeCount = 0;
|
||
}
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field>
|
||
<mat-date-range-input [rangePicker]="rangePicker">
|
||
<input #start matStartDate/>
|
||
<input #end matEndDate/>
|
||
</mat-date-range-input>
|
||
|
||
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [MatDateRangeInput, MatStartDate, MatEndDate, MatFormField, MatDateRangePicker],
|
||
})
|
||
class RangePickerNoLabel {
|
||
@ViewChild('start') start: ElementRef<HTMLInputElement>;
|
||
@ViewChild('end') end: ElementRef<HTMLInputElement>;
|
||
}
|
||
|
||
@Directive({
|
||
selector: '[customValidator]',
|
||
providers: [
|
||
{
|
||
provide: NG_VALIDATORS,
|
||
useExisting: CustomValidator,
|
||
multi: true,
|
||
},
|
||
],
|
||
standalone: true,
|
||
})
|
||
class CustomValidator implements Validator {
|
||
validate = jasmine.createSpy('validate spy').and.returnValue(null);
|
||
}
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field>
|
||
<mat-date-range-input [rangePicker]="rangePicker" [min]="min" [max]="max">
|
||
<input matStartDate [(ngModel)]="start" customValidator/>
|
||
<input matEndDate [(ngModel)]="end" customValidator/>
|
||
</mat-date-range-input>
|
||
|
||
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [
|
||
MatDateRangeInput,
|
||
MatStartDate,
|
||
MatEndDate,
|
||
MatFormField,
|
||
MatDateRangePicker,
|
||
CustomValidator,
|
||
FormsModule,
|
||
],
|
||
})
|
||
class RangePickerWithCustomValidator {
|
||
@ViewChild(CustomValidator) validator: CustomValidator;
|
||
start: Date | null = null;
|
||
end: Date | null = null;
|
||
min: Date;
|
||
max: Date;
|
||
}
|
||
|
||
@Component({
|
||
template: `
|
||
<mat-form-field>
|
||
<mat-date-range-input [rangePicker]="rangePicker">
|
||
<input matStartDate [errorStateMatcher]="matcher"/>
|
||
<input matEndDate [errorStateMatcher]="matcher"/>
|
||
</mat-date-range-input>
|
||
|
||
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
</mat-form-field>
|
||
`,
|
||
standalone: true,
|
||
imports: [MatDateRangeInput, MatStartDate, MatEndDate, MatFormField, MatDateRangePicker],
|
||
})
|
||
class RangePickerErrorStateMatcher {
|
||
@ViewChild(MatStartDate) startInput: MatStartDate<Date>;
|
||
@ViewChild(MatEndDate) endInput: MatEndDate<Date>;
|
||
matcher: ErrorStateMatcher = {isErrorState: () => false};
|
||
}
|