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
注意:所有 userEvent 方法都是同步的,只有一個例外:當 delay
選項與如下所述的 userEvent.type
一起使用時。我們也不鼓勵在 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
會在點擊之前觸發 hover 事件。若要停用此功能,請將 skipHover
選項設定為 true
。
指標事件選項
嘗試點擊 pointer-events
設定為 "none"
(即不可點擊)的元素會擲回錯誤。如果您想停用此行為,您可以將 skipPointerEventsCheck
設定為 true
userEvent.click(elem, undefined, {skipPointerEventsCheck: true})
skipPointerEventsCheck
選項可以傳遞給任何與指標相關的 API,包括
dblClick(element, eventInit, options)
點擊 element
兩次,根據 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])
將 text
寫入 <input>
或 <textarea>
內。
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
是兩個字元輸入之間的毫秒數。預設為 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 | 選取元素的所有文字。請注意,這僅適用於支援選取範圍的元素(因此,不包括 email 、password 、number 等)。 |
{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(模擬使用者點擊「大小寫鎖定」按鈕以啟用大寫鎖定)。 |
關於修飾鍵的注意事項:修飾鍵(
{shift}
、{ctrl}
、{alt}
、{meta}
)將在其對應的事件修飾符號作用於輸入命令期間,或直到它們關閉(透過{/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
{
和[
用作特殊字元,可以透過將它們加倍來引用。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
修飾鍵/specialChar 修飾鍵(如{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 未啟用且引用
A
時,自動按下{Shift}
。如果您不希望這種行為,您可以在程式碼中使用userEvent.keyboard
時傳遞autoModify: false
。
upload(element, file, [{ clickInit, changeInit }], [options])
將檔案上傳到 <input>
。若要上傳多個檔案,請使用具有 multiple
屬性的 <input>
,並使用第二個 upload
引數作為陣列。也可以使用第三個引數來初始化點擊或變更事件。
如果 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})
觸發 tab 事件,以與瀏覽器相同的方式變更 document.activeElement。
選項
shift
(預設為false
) 可以是 true 或 false,以反轉 tab 方向。focusTrap
(預設為document
) 是限制在其中進行 tab 切換的容器元素。
關於 tab 的注意事項:jsdom 不支援 tab 切換,因此此功能是一種讓測試能夠從最終使用者的角度驗證 tab 切換的方法。但是,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')
})