Skip to content
Closed
2 changes: 2 additions & 0 deletions src/material/timepicker/testing/timepicker-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export class MatTimepickerHarness extends ComponentHarness {
throw Error(`Could not find a mat-option matching ${JSON.stringify(filters)}`);
}
await options[0].click();
// Wait for the timepicker to close after selection
await this.forceStabilize();
}

/** Gets the selector that can be used to find the timepicker's panel. */
Expand Down
45 changes: 40 additions & 5 deletions src/material/timepicker/timepicker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component, Injector, Provider, signal, ViewChild, ViewEncapsulation} from '@angular/core';
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
import {ComponentFixture, fakeAsync, flush, TestBed, flushMicrotasks} from '@angular/core/testing';
import {DateAdapter, MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../core';
import {
clearElement,
Expand Down Expand Up @@ -135,6 +135,39 @@ describe('MatTimepicker', () => {
}),
);
}));

it('should emit selected event after form control value is updated', fakeAsync(() => {
const fixture = TestBed.createComponent(TimepickerWithForms);
const control = fixture.componentInstance.control;
fixture.detectChanges();

let formControlValue: Date | null = null;
let eventValue: Date | null = null;

// Subscribe to form control changes
control.valueChanges.subscribe(value => {
formControlValue = value;
});

// Subscribe to selected event
fixture.componentInstance.input.timepicker().selected.subscribe(event => {
eventValue = event.value;
// At this point, form control should already be updated
expect(control.value).toBeTruthy();
expectSameTime(control.value, event.value);
});

getInput(fixture).click();
fixture.detectChanges();
getOptions()[3].click(); // Select 1:30 AM
fixture.detectChanges();
flush();

expect(formControlValue).toBeTruthy();
expect(eventValue).toBeTruthy();
expectSameTime(formControlValue, eventValue);
expectSameTime(control.value, createTime(1, 30));
}));
});

describe('input behavior', () => {
Expand Down Expand Up @@ -927,7 +960,7 @@ describe('MatTimepicker', () => {
expect(control.dirty).toBe(true);
});

it('should propagate value selected from the panel to the form control', () => {
it('should propagate value selected from the panel to the form control', fakeAsync(() => {
const fixture = TestBed.createComponent(TimepickerWithForms);
const control = fixture.componentInstance.control;
fixture.detectChanges();
Expand All @@ -938,10 +971,11 @@ describe('MatTimepicker', () => {
fixture.detectChanges();
getOptions()[5].click();
fixture.detectChanges();
flush();

expectSameTime(control.value, createTime(2, 30));
expect(control.dirty).toBe(true);
});
}));

it('should format values assigned to the input through the form control', () => {
const fixture = TestBed.createComponent(TimepickerWithForms);
Expand All @@ -964,7 +998,7 @@ describe('MatTimepicker', () => {
expect(input.value).toBe('10:10 AM');
});

it('should not change the control if the same value is selected from the dropdown', () => {
it('should not change the control if the same value is selected from the dropdown', fakeAsync(() => {
const fixture = TestBed.createComponent(TimepickerWithForms);
const control = fixture.componentInstance.control;
control.setValue(createTime(2, 30));
Expand All @@ -978,12 +1012,13 @@ describe('MatTimepicker', () => {
fixture.detectChanges();
getOptions()[5].click();
fixture.detectChanges();
flush();

expectSameTime(control.value, createTime(2, 30));
expect(control.dirty).toBe(false);
expect(spy).not.toHaveBeenCalled();
subscription.unsubscribe();
});
}));

it('should not propagate programmatic changes to the form control', () => {
const fixture = TestBed.createComponent(TimepickerWithForms);
Expand Down
5 changes: 4 additions & 1 deletion src/material/timepicker/timepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,10 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
current.deselect(false);
}
});
this.selected.emit({value: option.value, source: this});
// Emit the selected event after the current execution cycle to ensure the form control is updated first
setTimeout(() => {
this.selected.emit({value: option.value, source: this});
}, 0);
this._input()?.focus();
}

Expand Down
Loading