I want to test "real" typing of an user for my input element. To unit-test if my number.component in combination with my number-only.directive only accepts numeric inputs.
The problem is the ngModel isn't updated on 'keydown' (KeyboardEvent), but is needed so the directive is triggered.
The 'input' event requires to set the value of the nativeElement before dispatching it, which would skip the directive.
I already experimented with fakeAsync, tick and whenStable, but didn't manage to recreate the flow of an actual user typing into the input field.
number.component.html
<input numberOnly class="number-input ml-2 mr-2" type="text" [(ngModel)]="value">
number-only-directive.ts
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[NumberOnly]'
})
export class NumberOnlyDirective {
// Allow decimal numbers. The \. is only allowed once to occur
private regex: RegExp = new RegExp(/^[0-9]+(\.[0-9]*){0,1}$/g);
// Allow key codes for special events. Reflect :
// Backspace, tab, end, home
private specialKeys: Array<string> = ['Backspace', 'Tab', 'End', 'Home'];
constructor(private el: ElementRef) {
}
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
// Allow Backspace, tab, end, and home keys
if (this.specialKeys.indexOf(event.key) !== -1) {
return;
}
// Do not use event.keycode this is deprecated.
// See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
const current: string = this.el.nativeElement.value;
// We need this because the current value on the DOM element
// is not yet updated with the value from this event
const next: string = current.concat(event.key);
if (next && !String(next).match(this.regex)) {
event.preventDefault();
}
}
}
number.component.spec.ts (not working just to get an idea what I want to achieve)
it('should prohibit non-numeric input and keep the value 1', fakeAsync(() => {
const numberDebug = fixture.debugElement.query(By.css('.number-input'));
const numberInput = numberDebug.nativeElement as HTMLInputElement;
numberDebug.triggerEventHandler('keydown', { bubbles: true, key: '1' });
// numberInput.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: '1' }));
tick();
fixture.detectChanges();
expect(component.value).toEqual(1);
expect(numberInput.value).toEqual('1');
const eventMock = new KeyboardEvent('keydown', { key: 'a' });
numberInput.dispatchEvent(eventMock);
tick();
// somehow check if event passed the directive
// if so fire 'input' event
fixture.detectChanges();
expect(component.value).toEqual(1);
expect(numberInput.value).toEqual('1');
}));