Skip to content

Commit

Permalink
feat(datepicker): expose validators minDate, maxDate, invalidDate (ng…
Browse files Browse the repository at this point in the history
  • Loading branch information
03byron authored and maxokorokov committed May 29, 2019
1 parent ec8a129 commit 509bba3
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 189 deletions.
142 changes: 1 addition & 141 deletions src/datepicker/datepicker-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {By} from '@angular/platform-browser';
import {createGenericTestComponent} from '../test/common';

import {Component, Injectable} from '@angular/core';
import {FormsModule, NgForm} from '@angular/forms';
import {FormsModule} from '@angular/forms';

import {NgbDateAdapter, NgbDatepickerModule} from './datepicker.module';
import {NgbInputDatepicker} from './datepicker-input';
Expand Down Expand Up @@ -356,146 +356,6 @@ describe('NgbInputDatepicker', () => {

});

describe('validation', () => {

describe('values set from model', () => {

it('should not return errors for valid model', fakeAsync(() => {
const fixture = createTestCmpt(
`<form><input ngbDatepicker [ngModel]="{year: 2017, month: 04, day: 04}" name="dp"></form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();
expect(form.control.hasError('ngbDate', ['dp'])).toBeFalsy();
}));

it('should not return errors for empty model', fakeAsync(() => {
const fixture = createTestCmpt(`<form><input ngbDatepicker [ngModel]="date" name="dp"></form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();
}));

it('should return "invalid" errors for invalid model', fakeAsync(() => {
const fixture = createTestCmpt(`<form><input ngbDatepicker [ngModel]="5" name="dp"></form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();
expect(form.control.getError('ngbDate', ['dp']).invalid).toBe(5);
}));

it('should return "requiredBefore" errors for dates before minimal date', fakeAsync(() => {
const fixture = createTestCmpt(`<form>
<input ngbDatepicker [ngModel]="{year: 2017, month: 04, day: 04}" [minDate]="{year: 2017, month: 6, day: 4}" name="dp">
</form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();
expect(form.control.getError('ngbDate', ['dp']).requiredBefore).toEqual({year: 2017, month: 6, day: 4});
}));

it('should return "requiredAfter" errors for dates after maximal date', fakeAsync(() => {
const fixture = createTestCmpt(`<form>
<input ngbDatepicker [ngModel]="{year: 2017, month: 04, day: 04}" [maxDate]="{year: 2017, month: 2, day: 4}" name="dp">
</form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();
expect(form.control.getError('ngbDate', ['dp']).requiredAfter).toEqual({year: 2017, month: 2, day: 4});
}));

it('should update validity status when model changes', fakeAsync(() => {
const fixture = createTestCmpt(`<form><input ngbDatepicker [ngModel]="date" name="dp"></form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.componentRef.instance.date = <any>'invalid';
fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();

fixture.componentRef.instance.date = {year: 2015, month: 7, day: 3};
fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();
}));

it('should update validity status when minDate changes', fakeAsync(() => {
const fixture = createTestCmpt(`<form>
<input ngbDatepicker [ngModel]="{year: 2017, month: 2, day: 4}" [minDate]="date" name="dp">
</form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();

fixture.componentRef.instance.date = {year: 2018, month: 7, day: 3};
fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();
}));

it('should update validity status when maxDate changes', fakeAsync(() => {
const fixture = createTestCmpt(`<form>
<input ngbDatepicker [ngModel]="{year: 2017, month: 2, day: 4}" [maxDate]="date" name="dp">
</form>`);
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();

fixture.componentRef.instance.date = {year: 2015, month: 7, day: 3};
fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();
}));

it('should update validity for manually entered dates', fakeAsync(() => {
const fixture = createTestCmpt(`<form><input ngbDatepicker [(ngModel)]="date" name="dp"></form>`);
const inputDebugEl = fixture.debugElement.query(By.css('input'));
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

inputDebugEl.triggerEventHandler('input', {target: {value: '2016-09-10'}});
fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();

inputDebugEl.triggerEventHandler('input', {target: {value: 'invalid'}});
fixture.detectChanges();
tick();
expect(form.control.invalid).toBeTruthy();
}));

it('should consider empty strings as valid', fakeAsync(() => {
const fixture = createTestCmpt(`<form><input ngbDatepicker [(ngModel)]="date" name="dp"></form>`);
const inputDebugEl = fixture.debugElement.query(By.css('input'));
const form = fixture.debugElement.query(By.directive(NgForm)).injector.get(NgForm);

inputDebugEl.triggerEventHandler('change', {target: {value: '2016-09-10'}});
fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();

inputDebugEl.triggerEventHandler('change', {target: {value: ''}});
fixture.detectChanges();
tick();
expect(form.control.valid).toBeTruthy();
}));
});

});

describe('options', () => {

it('should propagate the "dayTemplate" option', () => {
Expand Down
49 changes: 4 additions & 45 deletions src/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ import {
Inject,
Input,
NgZone,
OnChanges,
OnDestroy,
Output,
Renderer2,
SimpleChanges,
TemplateRef,
ViewContainerRef
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator} from '@angular/forms';
import {Subject} from 'rxjs';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

import {ngbAutoClose} from '../util/autoclose';
import {ngbFocusTrap} from '../util/focus-trap';
Expand All @@ -40,12 +37,6 @@ const NGB_DATEPICKER_VALUE_ACCESSOR = {
multi: true
};

const NGB_DATEPICKER_VALIDATOR = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => NgbInputDatepicker),
multi: true
};

/**
* A directive that allows to stick a datepicker popup to an input field.
*
Expand All @@ -60,10 +51,10 @@ const NGB_DATEPICKER_VALIDATOR = {
'(blur)': 'onBlur()',
'[disabled]': 'disabled'
},
providers: [NGB_DATEPICKER_VALUE_ACCESSOR, NGB_DATEPICKER_VALIDATOR, NgbDatepickerService]
providers: [NGB_DATEPICKER_VALUE_ACCESSOR, NgbDatepickerService]
})
export class NgbInputDatepicker implements OnChanges,
OnDestroy, ControlValueAccessor, Validator {
export class NgbInputDatepicker implements OnDestroy,
ControlValueAccessor {
private _cRef: ComponentRef<NgbDatepicker> = null;
private _disabled = false;
private _model: NgbDate;
Expand Down Expand Up @@ -247,8 +238,6 @@ export class NgbInputDatepicker implements OnChanges,

private _onChange = (_: any) => {};
private _onTouched = () => {};
private _validatorChange = () => {};


constructor(
private _parserFormatter: NgbDateParserFormatter, private _elRef: ElementRef<HTMLInputElement>,
Expand All @@ -263,32 +252,8 @@ export class NgbInputDatepicker implements OnChanges,

registerOnTouched(fn: () => any): void { this._onTouched = fn; }

registerOnValidatorChange(fn: () => void): void { this._validatorChange = fn; }

setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; }

validate(c: AbstractControl): {[key: string]: any} {
const value = c.value;

if (value === null || value === undefined) {
return null;
}

const ngbDate = this._fromDateStruct(this._dateAdapter.fromModel(value));

if (!this._calendar.isValid(ngbDate)) {
return {'ngbDate': {invalid: c.value}};
}

if (this.minDate && ngbDate.before(NgbDate.from(this.minDate))) {
return {'ngbDate': {requiredBefore: this.minDate}};
}

if (this.maxDate && ngbDate.after(NgbDate.from(this.maxDate))) {
return {'ngbDate': {requiredAfter: this.maxDate}};
}
}

writeValue(value) {
this._model = this._fromDateStruct(this._dateAdapter.fromModel(value));
this._writeModelValue(this._model);
Expand Down Expand Up @@ -390,12 +355,6 @@ export class NgbInputDatepicker implements OnChanges,

onBlur() { this._onTouched(); }

ngOnChanges(changes: SimpleChanges) {
if (changes['minDate'] || changes['maxDate']) {
this._validatorChange();
}
}

ngOnDestroy() {
this.close();
this._zoneSubscription.unsubscribe();
Expand Down
Loading

0 comments on commit 509bba3

Please sign in to comment.