OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

react-select:React Testing Library で optionのリストを拾う

React Testing LibraryReact Select をどうやってテストするんだってのはFAQらしく、Stack Overflowではサードパーティの部品までテストすんなMock使えというのがベストアンサーだったりして、まあそうかもねと思いつつも、ほじくってテスト書いたのでメモ。

React Select ではドロップダウンが閉じた状態ではoption要素がDOMに存在しないので、わざわざ↓キー入力してドロップダウンを開いてからクエリをかけるようなことをやりました。

option に test id を付与

まあこんなのがあるとして。

//import Select from "react-select";

<Select
  options={[
    { label: "aaa", value: 1 },
    { label: "bbb", value: 2 },
    { label: "abc", value: 3 }
  ]}
/>

準備として、option要素に data-testid を付与しておく。

詳しくは先日書いたreact-select:インクリメンタル検索で一致部分を強調表示を参照。

//import Select, { components } from "react-select";

<Select
  options={[
    { label: "aaa", value: 1 },
    { label: "bbb", value: 2 },
    { label: "abc", value: 3 }
  ]}
  components={{
    Option: (p) => (
      <components.Option {...p}>
        <span data-testid="opt">{p.children}</span>
      </components.Option>
    )
  }}
/>

input 要素にダウンキーを入力

React Select でドロップダウンを開くためには、input要素に対して↓キーとかを入力してやる。

input要素は getByRole("textbox") でも拾ってこれるはずかな。
ここでは label が紐づいている想定として、 getByLabelText でとってくる形で実装。

<label htmlFor="input1">選択</label>
<Select
  inputId="input1"
  options={[
    { label: "aaa", value: 1 },
    { label: "bbb", value: 2 },
    { label: "abc", value: 3 }
  ]}
  components={{
    Option: (p) => (
      <components.Option {...p}>
        <span data-testid="opt">{p.children}</span>
      </components.Option>
    )
  }}
/>
//import { fireEvent, screen } from "@testing-library/react";
const DOWN_ARROW = { keyCode: 40 };

const input = screen.getByLabelText("選択");
fireEvent.keyDown(input, DOWN_ARROW);

option 要素を取得

↓キーを入力してから、ドロップダウンが開くまでに一瞬の待機が必要。
イケてないが雑に setTimeout かけるものとする。

各option要素から表示文字列を集めるときはこんな感じ。

const DOWN_ARROW = { keyCode: 40 };
const ESC = { keyCode: 27 };

const input = screen.getByLabelText("選択");
fireEvent.keyDown(input, DOWN_ARROW);

//簡易的にちょっとwait
await new Promise((res) => setTimeout(res, 200));

const res = screen.queryAllByTestId("opt").map((o) => o.textContent);

//必要あれば閉じる
fireEvent.keyDown(input, ESC);

以上

コード全体はこちら