Интегральное тестирование Angular2 +: как подделать пользователя, вводящего что-то во ввод, с помощью события keydown и события input - PullRequest
0 голосов
/ 04 января 2019

Я хочу проверить "реальную" типизацию пользователя для моего элемента ввода. Для модульного тестирования, если мой number.component в сочетании с моим number-only.directive принимает только числовые входы.
Проблема в том, что ngModel не обновляется при 'keydown' ( KeyboardEvent ), но необходим для запуска директивы.
Событие 'input' требует установить значение nativeElement перед его отправкой, что пропустит директиву.

Я уже экспериментировал с fakeAsync, tick и whenStable, но мне не удалось воссоздать поток фактического пользователя, набирающего текст в поле ввода.

number.component.html

<input numberOnly class="number-input ml-2 mr-2" type="text" [(ngModel)]="value">

номер-только-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 (не работает, просто чтобы понять, чего я хочу достичь)

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');
}));

1 Ответ

0 голосов
/ 04 января 2019

Я нашел решение.
Отсутствует тот факт, что событие должно быть отменено (спасибо this ).

После исправления это свойство event.defaultPrevented было правильно установлено для каждого KeyboardEvent, что привело к правильному тестированию:

describe('NumberComponent', () => {
    let component: NumberComponent;
    let fixture: ComponentFixture<NumberComponent>;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [NumberComponent, NumberOnlyDirective],
            imports: [FormsModule]
        })
            .compileComponents();
    }));

    beforeEach(async(() => {
        fixture = TestBed.createComponent(NumberComponent);
        component = fixture.componentInstance;

        fixture.detectChanges();
    }));

    it('should prohibit non-numeric input', () => {
        let numberDebug = fixture.debugElement.query(By.css('.number-input'));
        let numberInput = numberDebug.nativeElement as HTMLInputElement;

        fakeTyping('12abc34de', numberInput);

        expect(numberInput.value).toBe('1234');
    });

    function fakeTyping(value: string, inputEl: HTMLInputElement) {
        let result: string = '';
        for (let char of value) {
            let eventMock = createKeyDownEvent(char);
            inputEl.dispatchEvent(eventMock);
            if (eventMock.defaultPrevented) {
                // invalid char
            } else {
                result = result.concat(char);
            }
        }

        inputEl.value = result;
        inputEl.dispatchEvent(new Event('input'));
        fixture.detectChanges();
    }
});
...