Webコンポーネントのテスト


反応のテストは喜びです.ジェストと反応テストライブラリ(ここからのRTL)の間に、それは取得し、実行し、生産的な超簡単です.実際には、CRAの3リリースのように、RTLは、さらに迅速に、ボックスから含まれています.
しかし、最近、私が最近ハングアップした粘着性の点がありました、そして、それはウェブ構成要素を消費する反応成分をテストすることでした.
このポストでは、Webコンポーネントとテストで発生する問題をカバーし、それを回避するために使用する特定の方法を示します.

粘着点
アットIonic , すべてのUIコンポーネントはWebコンポーネントとしてStencilJS . イオン反応では、我々は、イベントを配線し、より良い開発者の経験を提供するためにLiteの反応ラッパーを使用してWebコンポーネントをラップします.したがって、イオン反応成分を使用します.
<IonLabel>Hello</IonLabel>
本質的になる
<ion-label class="sc-ion-label-ios-h sc-ion-label-ios-s ios hydrated">Hello</ion-label>
それがブラウザでレンダリングされるまでに.
イオン成分にはかなりの論理がある.
And <ion-label> 最も簡単なコンポーネントの一つです.
私はイオン反応アプリのためのいくつかのテストガイダンスに取り組んできました.そうすることで、Webコンポーネントをテストすることが独自の課題を持っていることがわかりました.
さて、これはイオン、反応、冗談、またはRTLの問題ではありません.問題は、JESTがデフォルトで使用して反応コンポーネントをレンダリングするエンジンがJSDOMであるということです.
したがって、レンダリングされたとき<IonLabel> 上のコンポーネントの例は、JSDOMがレンダリングされたときにこのようになります.
<ion-label>Hello</ion-label>
スタイル、ロジック、または機能を持たない空のHTMLタグです.ブラウザでレンダリングされるのと全く同じことではありません.
最初に、私はこれらのWebコンポーネントをアップロードして実行するように決定しました.したがって、RTLでの書き込みテストはブラウザでレンダリングされたものと可能な限り一致するでしょう.しかし、いくつかの要件がありました.私は反応アプリをエクスポートしたり、それを実行するためにいくつかの複雑な構成を行う必要はありませんでした.
それで、私はいくつかの異なるポリフィールといくつかの異なるJEST環境を試みました.
だから、Webコンポーネントがレンダリングされない場合は、Webコンポーネントに依存する反応アプリをテストする方法はどうですか?
しかし、それを少し考えた後、私は、おそらくWebコンポーネントは、あなたの反応アプリをテストするために動作する必要はないことに気づいた.

何をテストしようとしているのですか.
テストを書くときは、コンポーネントに対して何らかの影響を与える効果があることをテストします.ページを読み込み、データが表示されます.ボタンをクリックし、XHRリクエストを送信します.入力が変更され、ページのほかの場所でラベルを更新します.
これらのどれも、ウェブ構成要素の内部の作業と大いに関係がありません.
私がアイテムにリストをつけるとき、私はAになりたいです<IonList> , すべてのDIVS、ヘッダータグ、スロット、および影DOMが作成されるのを見る必要はありません.私が欲しいのはリストに現れるアイテムを見ることです.
そして、これはWebコンポーネントが動作していなくても、私が得るものです.
<IonList>
{people.map(p => <IonItem>{p.name}</IonItem)}
</IonList>
レンダリング
<ion-list>
    <ion-item>Ely</ion-item>
    <ion-item>Joe</ion-item>
</ion-list>
私はすべての私のpeepsがそのリストにあることを確認するためにRTLを使用することができます
await waitForElement(() => people.map(p => getByText(p.name));
私のWebコンポーネントがレンダリングされなかったため、テストロジックは変更されません.
レンダリングされた空の、生存していないHTMLタグも、DOMイベントにも反応しますので、<IonButton> . ボタンonClick イベントはまだ起動し、我々のテストの観点から、イベントがHTMLタグまたはIonButtonの内部の作業から来た場合、それは重要ではありません.
これが何になるのか、コードの外部の部分としてWebコンポーネントを考えることができますし、そのコードの実装の詳細は、我々のアプリに影響を及ぼすべきではありません.

論理による部品のテスト
しかし、もしWebコンポーネントは、我々は我々のアプリのために動作する必要があるロジックがある場合?たとえば、プロップを設定するときにモードを開いたり、トグルコンポーネントをクリックすると、OnChangeイベントが発生します.
イオンの成分に論理的な仕事をしていなかったのは、私にとって難しい粘着性のポイントでした.また、JESTとJSDOMで動作するようにWebコンポーネントをどのように入手するかについて、長い間検索しました.
少しの後、私は別の実現に来ました:Webコンポーネントは外部サービスのように考えられることができます、そして、それとしてそれを偽造するのは可能かもしれません.
あなたが反応でWeb構成要素を使用しているならば、チャンスはあなたがウェブコンポーネント小道具とイベント(イオン反応が中心的なイオンウェブコンポーネントをラップするように)にアクセスするためにあなたのウェブ構成要素のまわりで反応ラッパーを使用しているということです.
あなたがラッパーを使うならば、あなたはJestの優れたmocking能力を通してあなたが輸入する反応成分を模擬することができます.あなたがそうしないならば、あなたはあなたのJSXで生のウェブ構成要素を使います.多分それをラップする良い時間ですか?
クリックしたときにカスタムチェンジイベントを発生させるトグルWebコンポーネントを持っているとしましょう.変更イベントは、内部でコンポーネント内で発生するため、JSDOMの下で実行されたときには起動しません.バンマー!しかし、私たちがそのコンポーネントをモックアウトするならば、私たちは模擬的な出来事を模擬的に攻撃することができます.
Webコンポーネントの例を次に示します.
アプリケーションのどこかに定義されているWebコンポーネント
window.customElements.define('my-toggle', class extends HTMLElement {
  checked = false;
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const toggle = document.createElement('input');
    toggle.setAttribute('type', 'checkbox');
    toggle.addEventListener('click', (e: MouseEvent) => {
      this.checked = !this.checked;
      this.dispatchEvent(new CustomEvent('myChange', {
        detail: {
          checked: this.checked
        }
      }));
    });
    shadow.appendChild(toggle);
  }
});
反応ラッパ
interface MyToggleProps {
  onMyChange?: (e: CustomEvent) => void
}

const MyToggle: React.FC<MyToggleProps> = ({ onMyChange }) => {
  const ref = useRef<HTMLElement>();

  useEffect(() => {
    onMyChange && ref.current!.addEventListener('myChange', (e) => onMyChange(e as any));
  }, []);

  return (
    <my-toggle ref={ref}></my-toggle>
  );
};

export default MyToggle;
ここでは、ラッパーをどのように使うことができますか
<MyToggle data-testid="my-toggle" onMyChange={(e) => setShowText(e.detail.checked)}></MyToggle>
{showText && <div>More text!</div>}
テストは以下のように設定します.
test('when my toggle is clicked it should show more text', async () => {
  const {getByTestId, getByText } = render(<MyComponent />);
  const toggle = getByTestId('my-toggle');
  fireEvent.click(toggle);
  await waitForElement(() => getByText('More text!'))
});
ただし、テストを実行すると、トグルがクリックされたときにdiv要素は表示されません.なぜ?Webコンポーネントが実際に現在実行されていないため、クリックハンドラーでのカスタムイベントの発火はどちらもできません.
そこで、Webコンポーネント機能のスリムな実装を使用してMyTopgle Responseコンポーネントを模擬することを提案します.
そのためには、テストの設定でこのモックを追加します.
function mockMyToggle({ onMyChange, ...rest }: any) {
  return (
    <my-toggle>
      <input {...rest} type="checkbox" onClick={(e) => {
        onMyChange && onMyChange(new CustomEvent('myChange', {
          detail: {
            checked: e.currentTarget.checked
          }
        }));
      }} />
    </my-toggle>
  );
}

jest.mock('../components/MyToggle', () => mockMyToggle);

The jest.mock method mocks out the entire MyToggle module at that specific path, which is where our component imports it from. More info.


このモックでは、実際にそれを使用せずにWebコンポーネントをシミュレートするのに十分なロジックを設定しました.
今、我々のテストが実行されると、それは実際の反応コンポーネントの代わりに模擬を使用して、我々のテストパス.

思考
上記はダイジェストにはたくさんありますが、私の前提はJSDOMで働くためにWebコンポーネントを取得しようとしている問題を抱えているなら、たぶんあなたは必要ないということです.
私はまだこの考えをまだ持っていません、しかし、これまで、それは静かに働きました.私は上記のアプローチについてのあなたの考えを聞くのが好きであるか、あなたが他の方法で成功したならば.
そして、誰が知っているかもしれません、JSDOMはWebコンポーネントサポートを着陸させるでしょう.