Skip to content

Commit

Permalink
fix(timepicker): switch to PM when hour > 12
Browse files Browse the repository at this point in the history
Fixes #1680

Closes #1684
  • Loading branch information
dmytroyarmak authored and pkozlowski-opensource committed Jul 25, 2017
1 parent 0c03671 commit 5cbb429
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 73 deletions.
238 changes: 166 additions & 72 deletions src/timepicker/timepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,169 +578,263 @@ describe('ngb-timepicker', () => {
expect(meridianButton.innerHTML).toBe('AM');
});
}));
});

describe('forms', () => {

it('should work with template-driven form validation', async(() => {
const html = `
<form>
<ngb-timepicker [(ngModel)]="model" name="control" required></ngb-timepicker>
</form>`;
it('should respect meridian when propagating model (PM)', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;
fixture.componentInstance.model = {hour: 14, minute: 30};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));

fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('3'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
expect(getTimepicker(compiled)).toHaveCssClass('ng-invalid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-valid');
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 15, minute: 30, second: 0}); });
}));

fixture.componentInstance.model = {hour: 12, minute: 0, second: 0};
fixture.detectChanges();
return fixture.whenStable();
})
it('should respect meridian when propagating model (AM)', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 9, minute: 30};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));

fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('10'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
expect(getTimepicker(compiled)).toHaveCssClass('ng-valid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-invalid');
});
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 10, minute: 30, second: 0}); });
}));

it('should work with model-driven form validation', async(() => {
const html = `
<form [formGroup]="form">
<ngb-timepicker formControlName="control" required></ngb-timepicker>
</form>`;
it('should interpret 12 as midnight (00:00) when meridian is set to AM', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;
fixture.componentInstance.model = {hour: 9, minute: 0};
fixture.detectChanges();
fixture.whenStable()
.then(() => {
const inputs = fixture.debugElement.queryAll(By.css('input'));

expect(getTimepicker(compiled)).toHaveCssClass('ng-invalid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-valid');
const inputs = fixture.debugElement.queryAll(By.css('input'));

fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('12'));
inputs[1].triggerEventHandler('change', createChangeEvent('15'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
expect(getTimepicker(compiled)).toHaveCssClass('ng-valid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-invalid');
});
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 0, minute: 0, second: 0}); });
}));

it('should propagate model changes only if valid - no seconds', () => {
const html = `<ngb-timepicker [(ngModel)]="model"></ngb-timepicker>`;
it('should interpret 12 as noon (12:00) when meridian is set to PM', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 12, minute: 0};
fixture.detectChanges();
const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 18, minute: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
inputs[0].triggerEventHandler('change', createChangeEvent('aa'));
fixture.detectChanges();
const inputs = fixture.debugElement.queryAll(By.css('input'));

expect(fixture.componentInstance.model).toBeNull();
});
fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('12'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 12, minute: 0, second: 0}); });
}));

it('should propagate model changes only if valid - with seconds', () => {
const html = `<ngb-timepicker [(ngModel)]="model" [seconds]="true"></ngb-timepicker>`;
it('should interpret hour more than 12 as 24h value (AM)', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 12, minute: 0, second: 0};
fixture.detectChanges();
const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 7, minute: 30, second: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
inputs[2].triggerEventHandler('change', createChangeEvent('aa'));
fixture.detectChanges();
const inputs = fixture.debugElement.queryAll(By.css('input'));
const meridianButton = <HTMLButtonElement>getMeridianButton(fixture.nativeElement);

expect(fixture.componentInstance.model).toBeNull();
});
fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('22'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '10:30');
expect(meridianButton.innerHTML).toBe('PM');
expect(fixture.componentInstance.model).toEqual({hour: 22, minute: 30, second: 0});
});
}));

it('should respect meridian when propagating model (PM)', async(() => {
it('should interpret hour more than 12 as 24h value (PM)', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 14, minute: 30};
fixture.componentInstance.model = {hour: 15, minute: 30, second: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
const meridianButton = <HTMLButtonElement>getMeridianButton(fixture.nativeElement);

fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('3'));
inputs[0].triggerEventHandler('change', createChangeEvent('22'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 15, minute: 30, second: 0}); });
.then(() => {
expectToDisplayTime(fixture.nativeElement, '10:30');
expect(meridianButton.innerHTML).toBe('PM');
expect(fixture.componentInstance.model).toEqual({hour: 22, minute: 30, second: 0});
});
}));

it('should respect meridian when propagating model (AM)', async(() => {
it('should use remainder of division by 24 as a value in 24h format when hour > 24 (AM)', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 9, minute: 30};
fixture.componentInstance.model = {hour: 7, minute: 30, second: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
const meridianButton = <HTMLButtonElement>getMeridianButton(fixture.nativeElement);

fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('10'));
inputs[0].triggerEventHandler('change', createChangeEvent(`${24 + 9}`));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 10, minute: 30, second: 0}); });
.then(() => {
expectToDisplayTime(fixture.nativeElement, '09:30');
expect(meridianButton.innerHTML).toBe('AM');
expect(fixture.componentInstance.model).toEqual({hour: 9, minute: 30, second: 0});
});
}));

it('should interpret 12 as midnight (00:00) when meridian is set to AM', async(() => {
it('should use remainder of division by 24 as a value in 24h format when hour > 24 (PM)', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 9, minute: 0};
fixture.componentInstance.model = {hour: 15, minute: 30, second: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
const meridianButton = <HTMLButtonElement>getMeridianButton(fixture.nativeElement);

fixture.whenStable()
.then(() => {
inputs[0].triggerEventHandler('change', createChangeEvent('12'));
inputs[0].triggerEventHandler('change', createChangeEvent(`${24 + 9}`));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 0, minute: 0, second: 0}); });
.then(() => {
expectToDisplayTime(fixture.nativeElement, '09:30');
expect(meridianButton.innerHTML).toBe('AM');
expect(fixture.componentInstance.model).toEqual({hour: 9, minute: 30, second: 0});
});
}));

it('should interpret 12 as noon (12:00) when meridian is set to PM', async(() => {
const html = `<ngb-timepicker [(ngModel)]="model" [meridian]="true"></ngb-timepicker>`;
});

describe('forms', () => {

it('should work with template-driven form validation', async(() => {
const html = `
<form>
<ngb-timepicker [(ngModel)]="model" name="control" required></ngb-timepicker>
</form>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 18, minute: 0};
const compiled = fixture.nativeElement;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
expect(getTimepicker(compiled)).toHaveCssClass('ng-invalid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-valid');

const inputs = fixture.debugElement.queryAll(By.css('input'));
fixture.componentInstance.model = {hour: 12, minute: 0, second: 0};
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
expect(getTimepicker(compiled)).toHaveCssClass('ng-valid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-invalid');
});
}));

it('should work with model-driven form validation', async(() => {
const html = `
<form [formGroup]="form">
<ngb-timepicker formControlName="control" required></ngb-timepicker>
</form>`;

const fixture = createTestComponent(html);
const compiled = fixture.nativeElement;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
const inputs = fixture.debugElement.queryAll(By.css('input'));

expect(getTimepicker(compiled)).toHaveCssClass('ng-invalid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-valid');

inputs[0].triggerEventHandler('change', createChangeEvent('12'));
inputs[1].triggerEventHandler('change', createChangeEvent('15'));
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => { expect(fixture.componentInstance.model).toEqual({hour: 12, minute: 0, second: 0}); });
.then(() => {
expect(getTimepicker(compiled)).toHaveCssClass('ng-valid');
expect(getTimepicker(compiled)).not.toHaveCssClass('ng-invalid');
});
}));

it('should propagate model changes only if valid - no seconds', () => {
const html = `<ngb-timepicker [(ngModel)]="model"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 12, minute: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
inputs[0].triggerEventHandler('change', createChangeEvent('aa'));
fixture.detectChanges();

expect(fixture.componentInstance.model).toBeNull();
});

it('should propagate model changes only if valid - with seconds', () => {
const html = `<ngb-timepicker [(ngModel)]="model" [seconds]="true"></ngb-timepicker>`;

const fixture = createTestComponent(html);
fixture.componentInstance.model = {hour: 12, minute: 0, second: 0};
fixture.detectChanges();

const inputs = fixture.debugElement.queryAll(By.css('input'));
inputs[2].triggerEventHandler('change', createChangeEvent('aa'));
fixture.detectChanges();

expect(fixture.componentInstance.model).toBeNull();
});

it('should not submit form when spinners clicked', async(() => {
const html = `<form (ngSubmit)="onSubmit()">
<ngb-timepicker name="control" [(ngModel)]="model"></ngb-timepicker>
Expand Down
6 changes: 5 additions & 1 deletion src/timepicker/timepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,11 @@ export class NgbTimepicker implements ControlValueAccessor,
updateHour(newVal: string) {
const isPM = this.model.hour >= 12;
const enteredHour = toInteger(newVal);
this.model.updateHour((this.meridian ? enteredHour % 12 : enteredHour) + (this.meridian && isPM ? 12 : 0));
if (this.meridian && (isPM && enteredHour < 12 || !isPM && enteredHour === 12)) {
this.model.updateHour(enteredHour + 12);
} else {
this.model.updateHour(enteredHour);
}
this.propagateModelChange();
}

Expand Down

0 comments on commit 5cbb429

Please sign in to comment.