Angular2 +集成单元测试:如何通过'keydown'事件和'input'事件伪造用户在输入中输入内容

时间:2019-01-04 11:16:33

标签: angular unit-testing angular-cli user-input

我想测试输入元素对用户的“真实”键入。要对我的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">

仅数字指令.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 :(得分:0)

我找到了解决方法。
遗漏了该事件可以取消的事实(感谢this)。

修复此问题后,已为每个KeyboardEvent正确设置了event.defaultPrevented属性,从而导致此正确的功能测试:

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