user-event v13
user-event
は、Testing Libraryの補足ライブラリであり、ビルトインのfireEvent
メソッドよりも高度なブラウザインタラクションのシミュレーションを提供します。
このページでは、user-event@13.5.0
について説明しています。
このバージョンは、もはやメンテナンスされていません。user-event@14
を代わりに使用してください。重要なバグ修正と新機能が含まれています。
インストール
- npm
- Yarn
npm install --save-dev @testing-library/user-event @testing-library/dom
yarn add --dev @testing-library/user-event @testing-library/dom
テストでインポートするだけです。
import userEvent from '@testing-library/user-event'
// or
const {default: userEvent} = require('@testing-library/user-event')
API
注:以下の説明にあるように、delay
オプションをuserEvent.type
で使用する場合を除き、すべてのuserEventメソッドは同期処理です。また、"Avoid Nesting When You're Testing"で説明されている重要な理由から、before/after
ブロック内でuserEvent
を使用することは避けるべきです。
click(element, eventInit, options)
element
をクリックします。クリックされたelement
の種類に応じて、click()
にはさまざまな副作用があります。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('click', () => {
render(
<div>
<label htmlFor="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>,
)
userEvent.click(screen.getByText('Check'))
expect(screen.getByLabelText('Check')).toBeChecked()
})
ctrlClick/shiftClickなども使用できます。
userEvent.click(elem, {ctrlKey: true, shiftKey: true})
詳細については、MouseEvent
コンストラクタのドキュメントを参照してください。
注:click
は、クリックする前にホバーイベントをトリガーします。これを無効にするには、skipHover
オプションをtrue
に設定します。
ポインターイベントオプション
pointer-events
が"none"
(つまり、クリック不可)に設定されている要素をクリックしようとすると、エラーが発生します。この動作を無効にするには、skipPointerEventsCheck
をtrue
に設定できます。
userEvent.click(elem, undefined, {skipPointerEventsCheck: true})
skipPointerEventsCheck
オプションは、以下のポインター関連APIに渡すことができます。
dblClick(element, eventInit, options)
element
を2回クリックします。element
の種類に応じて、さまざまな副作用があります。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('double click', () => {
const onChange = jest.fn()
render(<input type="checkbox" onChange={onChange} />)
const checkbox = screen.getByRole('checkbox')
userEvent.dblClick(checkbox)
expect(onChange).toHaveBeenCalledTimes(2)
expect(checkbox).not.toBeChecked()
})
注:options
にはポインターイベントオプションが含まれています。
type(element, text, [options])
<input>
または<textarea>
にtext
を入力します。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('type', () => {
render(<textarea />)
userEvent.type(screen.getByRole('textbox'), 'Hello,{enter}World!')
expect(screen.getByRole('textbox')).toHaveValue('Hello,\nWorld!')
})
options.delay
は、2つの文字の入力間に経過するミリ秒数です。デフォルトは0です。コンポーネントが高速ユーザーと低速ユーザーで異なる動作をする場合、このオプションを使用できます。このオプションを使用する場合は、必ずawait
を使用してください!
type
は、入力する前に要素をクリックします。これを無効にするには、skipClick
オプションをtrue
に設定します。
特殊文字
以下の特殊文字列がサポートされています。
テキスト文字列 | キー | 修飾子 | 備考 |
---|---|---|---|
{enter} | Enter | N/A | 改行文字(<textarea /> のみ)を挿入します。 |
{space} | ' ' | N/A | |
{esc} | Escape | N/A | |
{backspace} | Backspace | N/A | 前の文字(またはselectedRange 内の文字、下記の例を参照)を削除します。 |
{del} | Delete | N/A | 次の文字(またはselectedRange 内の文字、下記の例を参照)を削除します。 |
{selectall} | N/A | N/A | 要素のすべてのテキストを選択します。これは、選択範囲をサポートする要素(メール、パスワード、番号などではない)でのみ機能することに注意してください。 |
{arrowleft} | ArrowLeft | N/A | |
{arrowright} | ArrowRight | N/A | |
{arrowup} | ArrowUp | N/A | |
{arrowdown} | ArrowDown | N/A | |
{home} | Home | N/A | |
{end} | End | N/A | |
{shift} | Shift | shiftKey | 後続の文字を大文字にしません。 |
{ctrl} | Control | ctrlKey | |
{alt} | Alt | altKey | |
{meta} | OS | metaKey | |
{capslock} | CapsLock | modifierCapsLock | 使用時にkeydownとkeyupの両方を発生させます(ユーザーが「Caps Lock」ボタンをクリックしてCaps Lockを有効にすることをシミュレートします)。 |
**修飾子に関する注記:** 修飾キー(
{shift}
、{ctrl}
、{alt}
、{meta}
)は、typeコマンドの間、または閉じられるまで({/shift}
、{/ctrl}
などを使用)、対応するイベント修飾子をアクティブにします。明示的に閉じられない場合、イベントは自動的に閉じられます(これを無効にするには、skipAutoClose
オプションをtrue
に設定します)。
異なるオペレーティングシステムではこの点で動作が異なるため、修飾キーの組み合わせで発生する動作をシミュレートしないという点で、Cypressと同じスタンスを取っています。
選択範囲を使用した使用例
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('delete characters within the selectedRange', () => {
render(
<div>
<label htmlFor="my-input">Example:</label>
<input id="my-input" type="text" value="This is a bad example" />
</div>,
)
const input = screen.getByLabelText(/example/i)
input.setSelectionRange(10, 13)
userEvent.type(input, '{backspace}good')
expect(input).toHaveValue('This is a good example')
デフォルトでは、type
は既存のテキストに追加します。テキストの先頭に挿入するには、要素の選択範囲をリセットし、initialSelectionStart
とinitialSelectionEnd
オプションを指定します。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('prepend text', () => {
render(<input defaultValue="World!" />)
const element = screen.getByRole('textbox')
// Prepend text
element.setSelectionRange(0, 0)
userEvent.type(element, 'Hello, ', {
initialSelectionStart: 0,
initialSelectionEnd: 0,
})
expect(element).toHaveValue('Hello, World!')
})
<input type="time" />
のサポート
これは、<input type="time" />
を使用したこのライブラリの使用例です。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('types into the input', () => {
render(
<>
<label for="time">Enter a time</label>
<input type="time" id="time" />
</>,
)
const input = screen.getByLabelText(/enter a time/i)
userEvent.type(input, '13:58')
expect(input.value).toBe('13:58')
})
keyboard(text, options)
text
で記述されたキーボードイベントをシミュレートします。これはuserEvent.type()
に似ていますが、クリックや選択範囲の変更は行いません。
単にキーボードのボタンを押すことをシミュレートしたい場合は
userEvent.keyboard
を使用し、入力フィールドまたはテキストエリアにテキストを簡単に挿入したい場合はuserEvent.type
を使用する必要があります。
キーストロークは次のように記述できます。
- 印刷可能な文字ごと括弧
userEvent.keyboard('foo') // translates to: f, o, o
{
と[
は特殊文字として使用され、2回参照することで参照できます。userEvent.keyboard('{{a[[') // translates to: {, a, [
- KeyboardEvent.keyごと(
key
の英数字値のみサポート)これは、押されたキーを保持しません。そのため、userEvent.keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o
Shift
はf
を押す前に解除されます。 - KeyboardEvent.codeごと
userEvent.keyboard('[ShiftLeft][KeyF][KeyO][KeyO]') // translates to: Shift, f, o, o
- 従来の
userEvent.type
修飾子/特殊文字ごと{shift}
(小文字に注意)などの修飾子は、以前と同様に自動的に押されたままになります。この動作をキャンセルするには、記述子の最後に/
を追加します。userEvent.keyboard('{shift}{ctrl/}a{/shift}') // translates to: Shift(down), Control(down+up), a, Shift(up)
キーは、記述子の最後に>
を追加することで押されたままにすることができ、記述子の先頭に/
を追加することで解除できます。
userEvent.keyboard('{Shift>}A{/Shift}') // translates to: Shift(down), A, Shift(up)
userEvent.keyboard
は、キーボード操作を続けるために使用できるキーボードの状態を返します。
const keyboardState = userEvent.keyboard('[ControlLeft>]') // keydown [ControlLeft]
// ... inspect some changes ...
userEvent.keyboard('a', {keyboardState}) // press [KeyA] with active ctrlKey modifier
key
からcode
へのマッピングは、「デフォルト」の米国キーボードを表すデフォルトのキーマップによって実行されます。オプションごとに独自のローカルキーボードマッピングを提供できます。
userEvent.keyboard('?', {keyboardMap: myOwnLocaleKeyboardMap})
今後のバージョンでは、キーボードで印刷可能なキーに到達するために必要な修飾子を補間しようとします。例:CapsLockがアクティブでない場合に
{Shift}
を自動的に押し、A
を参照します。この動作を望まない場合は、コードでuserEvent.keyboard
を使用する際にautoModify: false
を渡すことができます。
upload(element, file, [{ clickInit, changeInit }], [options])
<input>
にファイルをアップロードします。複数のファイルをアップロードするには、multiple
属性を持つ<input>
と、2番目のupload
引数を配列として使用します。3番目の引数を使用して、クリックイベントまたは変更イベントを初期化することも可能です。
options.applyAccept
がtrue
に設定され、要素にaccept
属性がある場合、一致しないファイルは破棄されます。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('upload file', () => {
const file = new File(['hello'], 'hello.png', {type: 'image/png'})
render(
<div>
<label htmlFor="file-uploader">Upload file:</label>
<input id="file-uploader" type="file" />
</div>,
)
const input = screen.getByLabelText(/upload file/i)
userEvent.upload(input, file)
expect(input.files[0]).toStrictEqual(file)
expect(input.files.item(0)).toStrictEqual(file)
expect(input.files).toHaveLength(1)
})
test('upload multiple files', () => {
const files = [
new File(['hello'], 'hello.png', {type: 'image/png'}),
new File(['there'], 'there.png', {type: 'image/png'}),
]
render(
<div>
<label htmlFor="file-uploader">Upload file:</label>
<input id="file-uploader" type="file" multiple />
</div>,
)
const input = screen.getByLabelText(/upload file/i)
userEvent.upload(input, files)
expect(input.files).toHaveLength(2)
expect(input.files[0]).toStrictEqual(files[0])
expect(input.files[1]).toStrictEqual(files[1])
})
clear(element)
<input>
または<textarea>
内のテキストを選択して削除します。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('clear', () => {
render(<textarea defaultValue="Hello, World!" />)
userEvent.clear(screen.getByRole('textbox'))
expect(screen.getByRole('textbox')).toHaveValue('')
})
selectOptions(element, values, options)
<select>
または<select multiple>
要素の指定されたオプションを選択します。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('selectOptions', () => {
render(
<select multiple>
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
</select>,
)
userEvent.selectOptions(screen.getByRole('listbox'), ['1', '3'])
expect(screen.getByRole('option', {name: 'A'}).selected).toBe(true)
expect(screen.getByRole('option', {name: 'B'}).selected).toBe(false)
expect(screen.getByRole('option', {name: 'C'}).selected).toBe(true)
})
values
パラメータは、値の配列または単一の値にすることができます。
オプションノードも受け付けます。
userEvent.selectOptions(screen.getByTestId('select-multiple'), [
screen.getByText('A'),
screen.getByText('B'),
])
注:options
にはポインターイベントオプションが含まれています。
deselectOptions(element, values, options)
<select multiple>
要素の指定されたオプションの選択を解除します。
import * as React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('deselectOptions', () => {
render(
<select multiple>
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
</select>,
)
userEvent.selectOptions(screen.getByRole('listbox'), '2')
expect(screen.getByText('B').selected).toBe(true)
userEvent.deselectOptions(screen.getByRole('listbox'), '2')
expect(screen.getByText('B').selected).toBe(false)
// can do multiple at once as well:
// userEvent.deselectOptions(screen.getByRole('listbox'), ['1', '2'])
})
values
パラメータは、値の配列または単一の値にすることができます。
注:options
にはポインターイベントオプションが含まれています。
tab({shift, focusTrap})
ブラウザと同じように、document.activeElementを変更するタブイベントを発生させます。
オプション
shift
(デフォルトはfalse
)は、タブの方向を反転するためにtrueまたはfalseにすることができます。focusTrap
(デフォルトはdocument
)は、タブ操作を制限するコンテナ要素です。
**タブに関する注記:** jsdomはタブ操作をサポートしていません。そのため、この機能は、エンドユーザーの視点からタブ操作を検証するための方法です。ただし、jsdomのこの制限により、focus-trap-reactなどのコンポーネントは
userEvent.tab()
またはjsdomでは機能しません。そのため、ユーザーがフォーカス・トラップ内に制限されるように、focusTrap
オプションが用意されています。
import React from 'react'
import {render, screen} from '@testing-library/react'
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'
it('should cycle elements in document tab order', () => {
render(
<div>
<input data-testid="element" type="checkbox" />
<input data-testid="element" type="radio" />
<input data-testid="element" type="number" />
</div>,
)
const [checkbox, radio, number] = screen.getAllByTestId('element')
expect(document.body).toHaveFocus()
userEvent.tab()
expect(checkbox).toHaveFocus()
userEvent.tab()
expect(radio).toHaveFocus()
userEvent.tab()
expect(number).toHaveFocus()
userEvent.tab()
// cycle goes back to the body element
expect(document.body).toHaveFocus()
userEvent.tab()
expect(checkbox).toHaveFocus()
})
hover(element, options)
element
の上にカーソルを置きます。
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Tooltip from '../tooltip'
test('hover', () => {
const messageText = 'Hello'
render(
<Tooltip messageText={messageText}>
<TrashIcon aria-label="Delete" />
</Tooltip>,
)
userEvent.hover(screen.getByLabelText(/delete/i))
expect(screen.getByText(messageText)).toBeInTheDocument()
userEvent.unhover(screen.getByLabelText(/delete/i))
expect(screen.queryByText(messageText)).not.toBeInTheDocument()
})
注:options
にはポインターイベントオプションが含まれています。
unhover(element, options)
element
からカーソルを離します。
例については上記を参照してください。
注:options
にはポインターイベントオプションが含まれています。
paste(element, text, eventInit, options)
ユーザーが入力にテキストを貼り付けることをシミュレートできます。
test('should paste text in input', () => {
render(<MyInput />)
const text = 'Hello, world!'
const element = getByRole('textbox', {name: /paste your greeting/i})
userEvent.paste(element, text)
expect(element).toHaveValue(text)
})
貼り付けるものがclipboardData
(files
など)を持つ必要がある場合は、eventInit
を使用できます。
specialChars
typeメソッドで使用されるいくつかの特殊文字。
キー | 文字 |
---|---|
arrowLeft | {arrowleft} |
arrowRight | {arrowright} |
arrowDown | {arrowdown} |
arrowUp | {arrowup} |
home | {home} |
end | {end} |
enter | {enter} |
escape | {esc} |
delete | {del} |
backspace | {backspace} |
selectAll | {selectall} |
space | {space} |
whitespace | ' ' |
使用例
import React, {useState} from 'react'
import {render, screen} from '@testing-library/react'
import userEvent, {specialChars} from '@testing-library/user-event'
const InputElement = () => {
const [currentValue, setCurrentValue] = useState('This is a bad example')
return (
<div>
<label htmlFor="my-input">Example:</label>
<input
id="my-input"
type="text"
value={currentValue}
onChange={e => setCurrentValue(e.target.value)}
/>
</div>
)
}
test('delete characters within the selectedRange', () => {
render(<InputElement />)
const input = screen.getByLabelText(/example/i)
input.setSelectionRange(10, 13)
userEvent.type(input, `${specialChars.backspace}good`)
expect(input).toHaveValue('This is a good example')
})