Skip to content

Commit 10888f3

Browse files
crisbetommalerba
authored andcommitted
fix(datepicker): not revalidating after value is changed through the calendar (#19695)
Fixes an issue where the datepicker wouldn't revalidate, if the user typed in something invalid and then selected a value through the calendar. This seems to have regressed after things were moved around to accommodate the date range picker.
1 parent 613606d commit 10888f3

File tree

2 files changed

+48
-29
lines changed

2 files changed

+48
-29
lines changed

src/material/datepicker/datepicker-input-base.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
7171
}
7272
set value(value: D | null) {
7373
value = this._dateAdapter.deserialize(value);
74-
this._lastValueValid = !value || this._dateAdapter.isValid(value);
74+
this._lastValueValid = this._isValidValue(value);
7575
value = this._getValidDateOrNull(value);
7676
const oldDate = this.value;
7777
this._assignValue(value);
@@ -194,6 +194,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
194194
this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => {
195195
if (event.source !== this) {
196196
const value = this._getValueFromModel(event.selection);
197+
this._lastValueValid = this._isValidValue(value);
197198
this._cvaOnChange(value);
198199
this._onTouched();
199200
this._formatValue(value);
@@ -298,7 +299,7 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
298299
_onInput(value: string) {
299300
const lastValueWasValid = this._lastValueValid;
300301
let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
301-
this._lastValueValid = !date || this._dateAdapter.isValid(date);
302+
this._lastValueValid = this._isValidValue(date);
302303
date = this._getValidDateOrNull(date);
303304

304305
if (!this._dateAdapter.sameDate(date, this.value)) {
@@ -351,6 +352,11 @@ export abstract class MatDatepickerInputBase<S, D = ExtractDateTypeFromSelection
351352
}
352353
}
353354

355+
/** Whether a value is considered valid. */
356+
private _isValidValue(value: D | null): boolean {
357+
return !value || this._dateAdapter.isValid(value);
358+
}
359+
354360
/**
355361
* Checks whether a parent control is disabled. This is in place so that it can be overridden
356362
* by inputs extending this one which can be placed inside of a group that can be disabled.

src/material/datepicker/datepicker.spec.ts

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,12 @@ describe('MatDatepicker', () => {
12411241
testComponent = fixture.componentInstance;
12421242
}));
12431243

1244+
function revalidate() {
1245+
fixture.detectChanges();
1246+
flush();
1247+
fixture.detectChanges();
1248+
}
1249+
12441250
afterEach(fakeAsync(() => {
12451251
testComponent.datepicker.close();
12461252
fixture.detectChanges();
@@ -1253,50 +1259,39 @@ describe('MatDatepicker', () => {
12531259

12541260
it('should mark invalid when value is before min', fakeAsync(() => {
12551261
testComponent.date = new Date(2009, DEC, 31);
1256-
fixture.detectChanges();
1257-
flush();
1258-
fixture.detectChanges();
1262+
revalidate();
12591263

12601264
expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList)
12611265
.toContain('ng-invalid');
12621266
}));
12631267

12641268
it('should mark invalid when value is after max', fakeAsync(() => {
12651269
testComponent.date = new Date(2020, JAN, 2);
1266-
fixture.detectChanges();
1267-
flush();
1268-
1269-
fixture.detectChanges();
1270+
revalidate();
12701271

12711272
expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList)
12721273
.toContain('ng-invalid');
12731274
}));
12741275

12751276
it('should not mark invalid when value equals min', fakeAsync(() => {
12761277
testComponent.date = testComponent.datepicker._minDate;
1277-
fixture.detectChanges();
1278-
flush();
1279-
fixture.detectChanges();
1278+
revalidate();
12801279

12811280
expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList)
12821281
.not.toContain('ng-invalid');
12831282
}));
12841283

12851284
it('should not mark invalid when value equals max', fakeAsync(() => {
12861285
testComponent.date = testComponent.datepicker._maxDate;
1287-
fixture.detectChanges();
1288-
flush();
1289-
fixture.detectChanges();
1286+
revalidate();
12901287

12911288
expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList)
12921289
.not.toContain('ng-invalid');
12931290
}));
12941291

12951292
it('should not mark invalid when value is between min and max', fakeAsync(() => {
12961293
testComponent.date = new Date(2010, JAN, 2);
1297-
fixture.detectChanges();
1298-
flush();
1299-
fixture.detectChanges();
1294+
revalidate();
13001295

13011296
expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList)
13021297
.not.toContain('ng-invalid');
@@ -1306,31 +1301,49 @@ describe('MatDatepicker', () => {
13061301
const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
13071302
inputEl.value = '';
13081303
dispatchFakeEvent(inputEl, 'input');
1309-
1310-
fixture.detectChanges();
1311-
flush();
1312-
fixture.detectChanges();
1304+
revalidate();
13131305

13141306
expect(testComponent.model.valid).toBe(true);
13151307

13161308
inputEl.value = 'abcdefg';
13171309
dispatchFakeEvent(inputEl, 'input');
1318-
1319-
fixture.detectChanges();
1320-
flush();
1321-
fixture.detectChanges();
1310+
revalidate();
13221311

13231312
expect(testComponent.model.valid).toBe(false);
13241313

13251314
inputEl.value = '';
13261315
dispatchFakeEvent(inputEl, 'input');
1316+
revalidate();
13271317

1328-
fixture.detectChanges();
1329-
flush();
1330-
fixture.detectChanges();
1318+
expect(testComponent.model.valid).toBe(true);
1319+
}));
1320+
1321+
it('should update validity when a value is assigned', fakeAsync(() => {
1322+
const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
1323+
inputEl.value = '';
1324+
dispatchFakeEvent(inputEl, 'input');
1325+
revalidate();
1326+
1327+
expect(testComponent.model.valid).toBe(true);
1328+
1329+
inputEl.value = 'abcdefg';
1330+
dispatchFakeEvent(inputEl, 'input');
1331+
revalidate();
1332+
1333+
expect(testComponent.model.valid).toBe(false);
1334+
1335+
const validDate = new Date(2010, JAN, 2);
1336+
1337+
// Assigning through the selection model simulates the user doing it via the calendar.
1338+
const model = fixture.debugElement.query(By.directive(MatDatepicker))
1339+
.injector.get<MatDateSelectionModel<Date>>(MatDateSelectionModel);
1340+
model.updateSelection(validDate, null);
1341+
revalidate();
13311342

13321343
expect(testComponent.model.valid).toBe(true);
1344+
expect(testComponent.date).toBe(validDate);
13331345
}));
1346+
13341347
});
13351348

13361349
describe('datepicker with filter and validation', () => {

0 commit comments

Comments
 (0)