Angular2+ 集成单元测试:如何使用 'keydown'-event 和 'input'-event 伪造用户在输入中输入内容 [英] Angular2+ integration unit-testing: How to fake user typing something into input, with 'keydown'-event AND 'input'-event

查看:17
本文介绍了Angular2+ 集成单元测试:如何使用 'keydown'-event 和 'input'-event 伪造用户在输入中输入内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为我的输入元素测试用户的真实"输入.对我的 number.component 与 number-only.directive 结合是否只接受数字输入进行单元测试.
问题是 ngModel 未在keydown"(KeyboardEvent),但需要触发指令.
'input' 事件需要在调度 nativeElement 之前设置它的值,这将跳过指令.

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.

我已经尝试过 fakeAsync、tick 和 whenStable,但没有设法重新创建实际用户在输入字段中输入的流程.

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

number.component.html

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

number-only-directive.ts

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(不只是为了了解我想要实现的目标)

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

推荐答案

我找到了解决方案.
错过了事件应该可以取消的事实(感谢这个).

I found a solution.
Was missing the fact that the event should be cancelable (thanks to this).

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

After fixing this, the event.defaultPrevented property was correctly set for every KeyboardEvent, resulting in this correct functioning test:

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

export function createKeyDownEvent(value: string, cancelable = true) {  
    return new KeyboardEvent('keydown', { key: value, cancelable })  
}  

这篇关于Angular2+ 集成单元测试:如何使用 'keydown'-event 和 'input'-event 伪造用户在输入中输入内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆