Skip to content

Commit d802951

Browse files
PlamenaMitevaIvayloG
authored andcommitted
fix(date-picker): add validation when disabledDates is set #7800
1 parent 2012e24 commit d802951

File tree

2 files changed

+126
-89
lines changed

2 files changed

+126
-89
lines changed

projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts

Lines changed: 108 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, ViewChild, ElementRef, EventEmitter } from '@angular/core';
22
import { async, fakeAsync, TestBed, tick, flush, ComponentFixture } from '@angular/core/testing';
3-
import { FormsModule, FormGroup, FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3+
import { FormsModule, FormGroup, FormBuilder, ReactiveFormsModule, Validators, NgControl } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
66
import { IgxDatePickerComponent, IgxDatePickerModule } from './date-picker.component';
@@ -322,97 +322,97 @@ describe('IgxDatePicker', () => {
322322
});
323323

324324
it('When datepicker in "dropdown" mode is closed via outside click, the input should not receive focus',
325-
fakeAsync(() => {
326-
const fixture = TestBed.createComponent(IgxDatePickerDropdownButtonsComponent);
327-
fixture.detectChanges();
325+
fakeAsync(() => {
326+
const fixture = TestBed.createComponent(IgxDatePickerDropdownButtonsComponent);
327+
fixture.detectChanges();
328328

329-
const datePickerDom = fixture.debugElement.query(By.css('igx-date-picker'));
330-
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
331-
let overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
329+
const datePickerDom = fixture.debugElement.query(By.css('igx-date-picker'));
330+
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
331+
let overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
332332

333-
expect(overlayToggle.length).toEqual(0);
333+
expect(overlayToggle.length).toEqual(0);
334334

335-
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
336-
flush();
337-
fixture.detectChanges();
335+
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
336+
flush();
337+
fixture.detectChanges();
338338

339-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
340-
expect(overlayToggle[0]).not.toBeNull();
341-
expect(overlayToggle[0]).not.toBeUndefined();
339+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
340+
expect(overlayToggle[0]).not.toBeNull();
341+
expect(overlayToggle[0]).not.toBeUndefined();
342342

343-
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
344-
dummyInput.focus();
345-
dummyInput.click();
346-
tick();
347-
fixture.detectChanges();
343+
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
344+
dummyInput.focus();
345+
dummyInput.click();
346+
tick();
347+
fixture.detectChanges();
348348

349-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
350-
expect(overlayToggle[0]).toEqual(undefined);
351-
expect(input).not.toEqual(document.activeElement);
352-
expect(dummyInput).toEqual(document.activeElement);
353-
}));
349+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
350+
expect(overlayToggle[0]).toEqual(undefined);
351+
expect(input).not.toEqual(document.activeElement);
352+
expect(dummyInput).toEqual(document.activeElement);
353+
}));
354354

355355
it('When datepicker in "dropdown" mode, should focus input on user interaction with Today btn, Cancel btn, Enter Key, Escape key',
356-
fakeAsync(() => {
357-
const fixture = TestBed.createComponent(IgxDatePickerDropdownButtonsComponent);
358-
fixture.detectChanges();
359-
const datePickerDom = fixture.debugElement.query(By.css('igx-date-picker'));
360-
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
361-
let overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
362-
expect(overlayToggle.length).toEqual(0);
356+
fakeAsync(() => {
357+
const fixture = TestBed.createComponent(IgxDatePickerDropdownButtonsComponent);
358+
fixture.detectChanges();
359+
const datePickerDom = fixture.debugElement.query(By.css('igx-date-picker'));
360+
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
361+
let overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
362+
expect(overlayToggle.length).toEqual(0);
363363

364-
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
365-
flush();
366-
fixture.detectChanges();
367-
const buttons = document.getElementsByClassName('igx-button--flat');
368-
expect(buttons.length).toEqual(2);
364+
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
365+
flush();
366+
fixture.detectChanges();
367+
const buttons = document.getElementsByClassName('igx-button--flat');
368+
expect(buttons.length).toEqual(2);
369369

370-
// Today btn
371-
const todayBtn = buttons[1] as HTMLElement;
372-
expect(todayBtn.innerText).toBe('Today');
373-
todayBtn.click();
374-
tick();
375-
fixture.detectChanges();
376-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
377-
expect(overlayToggle[0]).toEqual(undefined);
378-
expect(input).toEqual(document.activeElement);
370+
// Today btn
371+
const todayBtn = buttons[1] as HTMLElement;
372+
expect(todayBtn.innerText).toBe('Today');
373+
todayBtn.click();
374+
tick();
375+
fixture.detectChanges();
376+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
377+
expect(overlayToggle[0]).toEqual(undefined);
378+
expect(input).toEqual(document.activeElement);
379379

380-
// Cancel btn
381-
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
382-
flush();
383-
fixture.detectChanges();
384-
const cancelBtn = buttons[0] as HTMLElement;
385-
expect(cancelBtn.innerText).toBe('Cancel');
386-
cancelBtn.click();
387-
tick();
388-
fixture.detectChanges();
389-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
390-
expect(overlayToggle[0]).toEqual(undefined);
391-
expect(input).toEqual(document.activeElement);
380+
// Cancel btn
381+
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
382+
flush();
383+
fixture.detectChanges();
384+
const cancelBtn = buttons[0] as HTMLElement;
385+
expect(cancelBtn.innerText).toBe('Cancel');
386+
cancelBtn.click();
387+
tick();
388+
fixture.detectChanges();
389+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
390+
expect(overlayToggle[0]).toEqual(undefined);
391+
expect(input).toEqual(document.activeElement);
392392

393-
// Enter key
394-
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
395-
flush();
396-
fixture.detectChanges();
397-
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
398-
tick();
399-
fixture.detectChanges();
400-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
401-
expect(overlayToggle[0]).toEqual(undefined);
402-
expect(input).toEqual(document.activeElement);
393+
// Enter key
394+
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
395+
flush();
396+
fixture.detectChanges();
397+
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
398+
tick();
399+
fixture.detectChanges();
400+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
401+
expect(overlayToggle[0]).toEqual(undefined);
402+
expect(input).toEqual(document.activeElement);
403403

404-
// Esc key
405-
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
406-
flush();
407-
fixture.detectChanges();
408-
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
409-
tick();
410-
fixture.detectChanges();
404+
// Esc key
405+
UIInteractions.triggerKeyDownEvtUponElem('space', datePickerDom.nativeElement, false);
406+
flush();
407+
fixture.detectChanges();
408+
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
409+
tick();
410+
fixture.detectChanges();
411411

412-
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
413-
expect(overlayToggle[0]).toEqual(undefined);
414-
expect(input).toEqual(document.activeElement);
415-
}));
412+
overlayToggle = document.getElementsByClassName('igx-overlay__wrapper');
413+
expect(overlayToggle[0]).toEqual(undefined);
414+
expect(input).toEqual(document.activeElement);
415+
}));
416416

417417
it('Datepicker week start day (Monday)', () => {
418418
const fixture = TestBed.createComponent(IgxDatePickerWithWeekStartComponent);
@@ -1325,12 +1325,33 @@ describe('IgxDatePicker', () => {
13251325
fixture.detectChanges();
13261326
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
13271327

1328-
inputDirectiveElement.triggerEventHandler('blur', { target: { value: ''}});
1328+
inputDirectiveElement.triggerEventHandler('blur', { target: { value: '' } });
13291329
fixture.detectChanges();
13301330

13311331
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
13321332
}));
13331333

1334+
it('Should set date picker status to invalid when date is disabled', fakeAsync(() => {
1335+
datePickerOnChangeComponent.disabledDates = [{ type: DateRangeType.Before, dateRange: [new Date()] }];
1336+
const inputGroupsElements = fixture.debugElement.queryAll(By.directive(IgxInputDirective));
1337+
const inputGroupElement = inputGroupsElements.find(d => d.componentInstance === datePickerOnChangeComponent);
1338+
const inputDirective = inputGroupElement.injector.get(IgxInputDirective) as IgxInputDirective;
1339+
1340+
const today = new Date();
1341+
datePickerOnChangeComponent.value = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);
1342+
fixture.detectChanges();
1343+
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
1344+
1345+
datePickerOnChangeComponent.value = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
1346+
fixture.detectChanges();
1347+
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
1348+
1349+
datePickerOnChangeComponent.disabledDates = [{ type: DateRangeType.Before,
1350+
dateRange: [new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2)] }];
1351+
fixture.detectChanges();
1352+
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
1353+
}));
1354+
13341355
// Bug #6025 Date picker does not disable in reactive form
13351356
it('Should disable when form is disabled', () => {
13361357
const formGroup: FormGroup = fixture.componentInstance.reactiveForm;
@@ -1368,10 +1389,10 @@ describe('IgxDatePicker', () => {
13681389
};
13691390
element = {};
13701391
cdr = {
1371-
markForCheck: () => {},
1372-
detectChanges: () => {},
1373-
detach: () => {},
1374-
reattach: () => {}
1392+
markForCheck: () => { },
1393+
detectChanges: () => { },
1394+
detach: () => { },
1395+
reattach: () => { }
13751396
};
13761397
moduleRef = {};
13771398
injector = { get: () => ngModel };
@@ -1381,7 +1402,7 @@ describe('IgxDatePicker', () => {
13811402
it('should initialize date picker with required correctly', () => {
13821403
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
13831404
datePicker['inputGroup'] = inputGroup;
1384-
ngModel.control.validator = () => ({ required: true});
1405+
ngModel.control.validator = () => ({ required: true });
13851406
datePicker.ngOnInit();
13861407
datePicker.ngAfterViewInit();
13871408
datePicker.ngAfterViewChecked();
@@ -1400,11 +1421,11 @@ describe('IgxDatePicker', () => {
14001421
expect(datePicker).toBeDefined();
14011422
expect(inputGroup.isRequired).toBeFalsy();
14021423

1403-
ngModel.control.validator = () => ({ required: true});
1424+
ngModel.control.validator = () => ({ required: true });
14041425
ngModel.statusChanges.emit();
14051426
expect(inputGroup.isRequired).toBeTruthy();
14061427

1407-
ngModel.control.validator = () => ({ required: false});
1428+
ngModel.control.validator = () => ({ required: false });
14081429
ngModel.statusChanges.emit();
14091430
expect(inputGroup.isRequired).toBeFalsy();
14101431
});
@@ -1576,7 +1597,7 @@ class IgxDatePickerReactiveFormComponent {
15761597
const date = new Date(2000, 10, 15);
15771598
this.reactiveForm = fb.group({
15781599
datePickerOnChange: [date, Validators.required],
1579-
datePickerOnBlur: [date, { updateOn: 'blur', validators: Validators.required}]
1600+
datePickerOnBlur: [date, { updateOn: 'blur', validators: Validators.required }]
15801601
});
15811602
}
15821603
}

projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
Injector,
2121
AfterViewChecked
2222
} from '@angular/core';
23-
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms';
23+
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
2424
import {
2525
IgxCalendarComponent,
2626
IgxCalendarHeaderTemplateDirective,
@@ -133,6 +133,11 @@ const noop = () => { };
133133
provide: NG_VALUE_ACCESSOR,
134134
useExisting: IgxDatePickerComponent,
135135
multi: true
136+
},
137+
{
138+
provide: NG_VALIDATORS,
139+
useExisting: IgxDatePickerComponent,
140+
multi: true
136141
}],
137142
// tslint:disable-next-line:component-selector
138143
selector: 'igx-date-picker',
@@ -299,6 +304,10 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
299304
}
300305
public set disabledDates(value: DateRangeDescriptor[]) {
301306
this._disabledDates = value;
307+
if (this._ngControl && this._value && this._disabledDates) {
308+
this._ngControl.control.validator({} as AbstractControl);
309+
this._onChangeCallback(this._value);
310+
}
302311
}
303312

304313
/**
@@ -732,6 +741,13 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
732741
/** @hidden @internal */
733742
public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; }
734743

744+
/** @hidden @internal */
745+
public validate(control: AbstractControl): ValidationErrors | null {
746+
if (this.value && this.disabledDates && isDateInRanges(this.value, this.disabledDates)) {
747+
return { dateIsDisabled: true };
748+
}
749+
return null;
750+
}
735751
//#endregion
736752

737753
/**
@@ -864,7 +880,7 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
864880

865881
protected onStatusChanged() {
866882
if ((this._ngControl.control.touched || this._ngControl.control.dirty) &&
867-
(this._ngControl.control.validator || this._ngControl.control.asyncValidator)) {
883+
(this._ngControl.control.validator || this._ngControl.control.asyncValidator) && this.inputGroup) {
868884
if (this.inputGroup.isFocused) {
869885
this._inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
870886
} else {

0 commit comments

Comments
 (0)