noxi雑記

.NET、Angularまわりの小ネタブログ

Angular CDK Component Harnessを実装する

前回は Angular CDK Component Harness を試しに使ってみました。今回は自作のコンポーネントに対して Component Harness を実装してみます。



環境

Angular CLI: 9.0.1
Node: 12.14.1
OS: win32 x64

Angular: 9.0.0
... animations, cdk, common, compiler, compiler-cli, core, forms
... language-service, material, platform-browser
... platform-browser-dynamic, router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.1
@angular-devkit/build-angular     0.900.1
@angular-devkit/build-optimizer   0.900.1
@angular-devkit/build-webpack     0.900.1
@angular-devkit/core              9.0.1
@angular-devkit/schematics        9.0.1
@angular/cli                      9.0.1
@ngtools/webpack                  9.0.1
@schematics/angular               9.0.1
@schematics/update                0.900.1
rxjs                              6.5.4
typescript                        3.7.5
webpack                           4.41.2

Component Harness を実装する

対象のコンポーネント作成

まずは実装対象のコンポーネントを作成します。 input とその値を削除するボタンがあるシンプルなコンポーネントです。

<!-- input.component.html -->
<input #input type="text" [value]="value" (input)="onValueChange(input.value)" [disabled]="disabled">
<button (click)="onValueChange('')" [disabled]="!canClear">Clear</button>
// input.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-input',
  templateUrl: './input.component.html',
  styleUrls: ['./input.component.scss'],
})
export class InputComponent {

  @Output() valueChange = new EventEmitter<string>();
  @Input() value: string = '';
  @Input() disabled: boolean = false;

  get canClear(): boolean {
    return !this.disabled && !!this.value;
  }

  onValueChange(value: string) {
    if (this.value !== value) {
      this.value = value;
      this.valueChange.emit(value);
    }
  }
}

Component Harness のベースを実装する

Component Harness を実装するにはまず ComponentHarness を継承したクラスを作成します。そして静的プロパティ hostSelector に Harness が対象とするコンポーネントセレクターを記述します。

import { ComponentHarness } from '@angular/cdk/testing';

export class InputComponentHarness extends ComponentHarness {

  /**
   * Component Harnessが対象とする要素のセレクター
   */
  static hostSelector = 'app-input';

}

次にこのコンポーネントが内包する要素へアクセスするためのクエリーを記述します。

private _input = this.locatorFor('input');
private _clearButton = this.locatorFor('button');

クエリーの引数は CSS セレクターや ComponentHarness のクラスが設定できます。 locatorFor locatorForOptional locatorForAll があるので使い分けましょう。

Component Harness の操作を実装する

次に実際に要素を操作するメソッドを実装します。このコンポーネントに対してユーザーが実行する操作とテスト時に取得したいこのコンポーネントの DOM の値にアクセスするものを書いていきましょう。

クエリーから取得される TestElement には clear blur focus hover といった状態を変更させるものや text getAttribute getProperty getCssValue hasClass の状態を取得するものが生えています。まんま Selenium の Element ライクに操作ができるのでとても楽に実装できます。

まずは操作するメソッドを実装します。今回は文字を入力する、クリアボタンを押す、の2つです。

/**
 * 文字を入力します。
 */
async input(...keys: Array<string | TestKey>): Promise<void> {
  const input = await this._input();
  return input.sendKeys(...keys);
}

/**
 * クリアボタンを押します。
 */
async clickClear(): Promise<void> {
  const button = await this._clearButton();
  return button.click();
}

次に状態を取得するメソッドです。入力された文字と input と button の非アクティブ状態を取得します。

/**
 * inputに入力されている文字を取得します。
 */
async getValue(): Promise<string> {
  const input = await this._input();
  return input.getProperty('value');
}

/**
 * inputの非アクティブ状態を取得します。
 */
async isInputDisabled(): Promise<boolean> {
  const input = await this._input();
  return input.getProperty('disabled');
}

/**
 * クリアボタンの非アクティブ状態を取得します。
 */
async isClearButtonDisabled(): Promise<boolean> {
  const button = await this._clearButton();
  return button.getProperty('disabled');
}

終わりに

凄く簡単にですが Component Harness を実装してみました。操作感は Selenium に近く、先にも書いたように Selenium による操作に慣れている人はとても実装しやすいと思います。