[Ant Design] DatePickerのTestの書き方
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エンジニアチームのテックブログです。エンジニアメンバーが、PharmaXの事業を通じて得た技術的な知見や、チームマネジメントについての知見を共有します。 PharmaXエンジニアチームやメンバーの雰囲気が分かるような記事は、note(note.com/pharmax)もご覧ください。
Discussion