🐼

[Ant Design] DatePickerのTestの書き方

2024/03/24に公開

TL;DR

  • DatePickerを使ったcompoenntのUnit Testの書き方
  • change eventが動かないならユーザーの操作通り要素をclickで発火していく
  • sample repository
code
import { expect, it, describe, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { DatePickerSample } from './DatePickerSample'
import userEvent from '@testing-library/user-event'

describe('DatePickerSample', () => {
  it('should render 2024-03-20.', async () => {
    vi.setSystemTime(new Date('2024-03-01'))
    const user = userEvent.setup()
    render(<DatePickerSample />)

    const input = screen.getByPlaceholderText('YYYY-MM-DD')
    await user.click(input)
    const date = document.querySelector(
      '.ant-picker-dropdown:not(.ant-picker-dropdown-hidden) .ant-picker-cell-in-view[title="2024-03-20"]'
    )
    date && (await user.click(date))

    const result = screen.getByText('2024-03-20')
    expect(result).toBeDefined()
  })
})

よくある機能だからこそちゃんとunit test書かなきゃ

「日付を選ばせて、選択された日付の値をチェックし、結果に応じて処理する」
これはよくある機能で、そしてこういった機能を実装する時よくDatePickerのお世話になるのではないでしょうか。

<DatePicker />

ライブラリーにもよりますが、大体こんな感じのコードで可愛く便利なDatePickerが作られます。

そんな中、私が特に愛用しているのはAnt Designですが、一つ困ったことがありました。
それはなぜかchange eventが発火されず、Unit Testが書けなかったからです。

これまでは日付が選択されていなければ,この処理をするのcaseは書きますが、
x月x日より前/後の日付が選択されたら、この処理をする系のtest caseはそれっぽいことだけ書いて.skipしてましたw

まあずっと放置もよくないので、今回は調べた結果と私的にAnt DesignのDatePickerのTestの書き方を共有します〜

まずは普通にテストを書いてみる

まずはごくシンプルにテストを書きます。

component

test code

DatePicker要素を取得し、値を変更させます。変更された値が適切に要素として描画されるかを確認します。
もちろん、これは失敗に終わります。

理由は?

一言で言うと、Ant DesignのDatePickerの<input />にはonChangeが適用されておらず、値の変更は別のイベントで管理されているためです。

valueはcomponentのonChangeによって変わるものであり、<input />自身のonChangeではvalueは変わりません。
イメージは:

// ①:change eventが効く場合
<input type="text" value={data} onChange={() => setData(...)} />

// ②:change eventが効かない場合
<input type="text" value={data} />
<button onClick={() => setData(...)}>change value</button>

Ant DesignのDatePickerは②の挙動でした。

じゃあどう書くのか

結論:ユーザーの行為をそのまま書く。

この例がやっていることは:「DatePicker要素を取得し、値を変えさせる」。

ただし、ユーザーは「要素」を意識していなければ、「値」なんても当然頭の中にはありません。

ユーザーの操作をそのまま書くとこうなるはず:

  • 日付選択欄をクリックする
  • カレンダーが出てくるので、そこから日付を選んでクリックする

次はこの順番でtesを修正してみましょう。

日付選択欄をクリックする

ここはこれまでのコードと同じです。
placeholderでもtestidでもなんでもいい、要素さえ取得できればなんでもOK。
そして取得したら、onChangeを発火させようとせず、onClickを発火させます。

カレンダーから日付を選ぶ

実際の操作では、日付選択欄をクリックした後、カレンダーが出てきます。
ここからいくつポイントがありますが、まずはその①:カレンダー要素の取り方。

①:カレンダー要素の取り方

input.querySelector('...')のような書き方では取れません、なぜならカレンダー要素はdatePickerの子要素ではないからです。


datePicker要素はこれが全部でした、カレンダーらしきものは見当たりません。

じゃあどこにあるかというと、ここを注目:

一番下にもう一つdivがあります。
察しのいい人はもう気づいたかもしれないが、実はこのカレンダーはdatePickerと相対的な位置関係にあります。(常にくっついているからね)
なので、datePickerでは取れません。

ただし画面上にちゃんと描画されているということは、documentから取れます。

ここでポイント②です。

②:.ant-picker-dropdownは見えなくてもdocumentに存在している

この.ant-picker-dropdown要素自体はクリックしてもしなくてもdocumentに存在しています。
.ant-picker-dropdown-hiddenというclassで可視状態を制御しています。

そしてこの.ant-picker-dropdownはDatePickerの数分あります。

普通ならdocument.querySelector('.ant-picker-dropdown')から辿っていけばOKですが、
テスト対象内に複数DatePickerが存在する場合、「ちゃんと今開いているもの」を取りたいので、

.ant-picker-dropdown:not(.ant-picker-dropdown-hidden) .ant-picker-cell-in-view[title="2024-03-20"]

で指定することをオススメします。

そしてユーザーはカレンダーそのものを選んでいるわけではないので、ちゃんと日付のところを選びましょう。

一旦結果確認

今のところtestはこうなります:

そして現時点ではこのtestは通ります:

ここでポイント③です。

③:DatePickerのカレンダーの初期値はsystemTimeに依存している

DatePickerを開いた瞬間「今月のカレンダー」が開かれるので、上記に載せているtest codeは月が変わると勝手に失敗しちゃいます。
(4月になると4月のカレンダーが開かれて、title="2024-03-20"が取れなくなったりします)

月ごとにこのtest caseを修正すればいいが、もちろんそんなめんどくさいことはやりたくないので、systemTimeもちゃんと設定しましょう。

これで自由に値を設定することもできました〜

終わりに

「あれ?changeが発火されてなくない?」にハマり、一時は「DatePickerだけ別のライブラリーにしようかなぁ」とちょっと思いました。

MUIみたいに「DataPickerを使うなら別の@mui/x-date-pickersを入れる必要があります」ならまだ受け入れられるが、
Ant DesignはすでにDatePicker入ってるし、そこだけ(かつ機能的にも問題なく、Unit Testだけのために)別のライブラリーを入れるの結構抵抗感ありましたw

これからはDatePickerを使ったcomponentもちゃんとtestを書いていきましょう〜

PharmaXテックブログ

Discussion