14

我是 Angular 的新手,我正在尝试使用 Angular 5 构建一个具有自动完成功能的文本字段。

我在Angular Material docs中找到了这个例子:

https://stackblitz.com/angular/kopqvokeddbq?file=app%2Fautocomplete-overview-example.ts

我想知道如何编写一个单元测试来测试自动完成功能。我正在为输入元素设置一个值并触发“输入”事件并尝试选择 mat-option 元素,但看到它们都没有被创建:

我的组件 html 的相关部分:

<form>
  <mat-form-field class="input-with-icon">
    <div>
      <i ngClass="jf jf-search jf-lg md-primary icon"></i>
      <input #nameInput matInput class="input-field-with-icon" placeholder="Type name here"
             type="search" [matAutocomplete]="auto" [formControl]="userFormControl" [value]="inputField">
    </div>
  </mat-form-field>
</form>

<mat-autocomplete #auto="matAutocomplete">
  <mat-option *ngFor="let option of filteredOptions | async" [value]="option.name"
              (onSelectionChange)="onNameSelect(option)">
    {{ option.name }}
  </mat-option>
</mat-autocomplete>

规格文件:

it('should filter users based on input', fakeAsync(() => {
    const hostElement = fixture.nativeElement;

    sendInput('john').then(() => {
        fixture.detectChanges();
        expect(fixture.nativeElement.querySelectorAll('mat-option').length).toBe(1);

        expect(hostElement.textContent).toContain('John Rambo');
    });
}));
function sendInput(text: string) {
    let inputElement: HTMLInputElement;

    inputElement = fixture.nativeElement.querySelector('input');
    inputElement.focus();
    inputElement.value = text;
    inputElement.dispatchEvent(new Event('input'));
    fixture.detectChanges();
    return fixture.whenStable();
}

组件html:

userFormControl: FormControl = new FormControl();

ngOnInit() {
    this.filteredOptions = this.userFormControl.valueChanges
        .pipe(
            startWith(''),
            map(val => this.filter(val))
        );
}

filter(val: string): User[] {
    if (val.length >= 3) {
        console.log(' in filter');
        return this.users.filter(user =>
            user.name.toLowerCase().includes(val.toLowerCase()));
    }
}

在此之前,我意识到要让 FormControl 对象设置值,我必须先做一个 inputElement.focus(),这与使用角度材料的 mat 输入有关。我需要做些什么来触发打开 mat-options 窗格吗?

我如何使这个测试工作?

4

4 回答 4

10

@Adam对先前答案的评论使我进入了mat-autocomplete 组件自己的测试,特别是在这里。您可以在其中看到focusin打开“选项”的事件。

但它们实际上是在您的组件外部的叠加层中打开的,所以在我的测试fixture.nativeElement.querySelectorAll('mat-option').length中,0但如果我查询元素document.querySelectorAll('mat-option'),我得到了预期的选项数量。

总结一下:

    fixture.detectChanges();
    const inputElement = fixture.debugElement.query(By.css('input')); // Returns DebugElement
    inputElement.nativeElement.dispatchEvent(new Event('focusin'));
    inputElement.nativeElement.value = text;
    inputElement.nativeElement.dispatchEvent(new Event('input'));

    fixture.detectChanges();
    await fixture.whenStable();
    fixture.detectChanges();

    const matOptions = document.querySelectorAll('mat-option');
    expect(matOptions.length).toBe(3,
      'Expect to have less options after input text and filter');

额外的球:如果你想点击一个选项(我做了),你可以这样继续:

    const optionToClick = matOptions[0] as HTMLElement;
    optionToClick.click();
    fixture.detectChanges();

尽管我没有成功单击并将值输入输入。好吧,我不是专家测试人员,但可能这种行为应该包含在自己mat-autocomplete的测试中(实际上是)并依赖它?

于 2019-08-20T08:09:31.767 回答
9

您需要添加更多事件。我有或多或少和你一样的问题,它只有在我触发 focusin 事件时才有效。

我在我的代码中使用这些事件。不确定是否都需要。

inputElement.dispatchEvent(new Event('focus'));
inputElement.dispatchEvent(new Event('focusin'));
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('keydown'));

您需要将此添加到您的 sendInput 函数...

于 2018-05-29T01:05:07.717 回答
0

我正在进一步建立@David 在这里的回答。

提供正在测试@Output() selectedTimezone = new EventEmitter<string>();的组件,并且在组件模板 <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectTimezone($event.option.value)">中,用于捕获发出具有正确值的适当类型事件的单元测试如下

it('should emit selectedTimezone event on optionSelected', async() => { 
    // Note: 'selectedTimezone' is @Output event type as specified in component's signature
    spyOn(component.selectedTimezone, 'emit'); 

    const inputElement = fixture.debugElement.query(By.css('input'));
    inputElement.nativeElement.dispatchEvent(new Event('focusin'));

    /**
     * Note, mat-options in this case set up to have array of ['Africa/Accra (UTC
     * +01:00)', 'Africa/Addis_Ababa (UTC +01:00)', 'Africa/Algiers (UTC +01:00)',
     * 'Africa/Asmara (UTC +01:00)']. I am setting it up in 'beforeEach'
     */
    inputElement.nativeElement.value = 'Africa'; 
    inputElement.nativeElement.dispatchEvent(new Event('input'));

    await fixture.whenStable();

    const matOptions = document.querySelectorAll('mat-option');
    expect(matOptions.length).toBe(4);

    const optionToClick = matOptions[0] as HTMLElement;
    optionToClick.click();

    // With this expect statement we verify both, proper type of event and value in it being emitted
    expect(component.selectedTimezone.emit).toHaveBeenCalledWith('Africa/Accra');
  });
于 2019-09-09T12:43:35.890 回答
0

感谢@David 的回答,在选择选项部分之前,我一切正常。

为了使选项选择起作用,我必须这样做;

...
matOptions[0].dispatchEvent(new Event('click'));
...

而且您不必将类型matOptions[0]转换为HTMLElement

注意**我在 Angular 8.0.1 上,新版本中的解决方案可能会有所不同

于 2021-04-30T14:59:14.623 回答