web-dev-qa-db-ja.com

マットオートコンプリートを使用するコンポーネントの単体テストの記述

私はAngularを初めて使用します。Angular 5.を使用して、オートコンプリートでテキストフィールドを作成しようとしています。

私はこの例を Angular Material docs で見つけました:

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

オートコンプリート機能をテストするための単体テストを作成する方法を知りたいと思っていました。 input要素に値を設定し、「input」イベントをトリガーして、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()を実行する必要があることに気付きました。これは、angularマテリアルのマット入力を使用して行うものです。 mat-optionsペインを開くきっかけを作るために何かしなければならないことがありますか?

このテストを機能させるにはどうすればよいですか?

12

さらにイベントを追加する必要があります。私は多かれ少なかれあなたと同じ問題を抱えていて、それはfocusinイベントをトリガーしたときにのみ機能しました。

これらのイベントをコードで使用しています。すべてが必要かどうかわからない。

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

これをsendInput関数に追加する必要があります...

@ Adam の以前の回答へのコメントにより、 mat-autocompleteコンポーネントの独自のテスト 、特に here に導かれました。 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_のテスト(および実際にはそうです)でカバーされ、それに依存する必要がありますか?

2
David

ここで@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');
  });
0