跳至主要內容

user-event v13

user-event 是一個 Testing Library 的配套函式庫,它提供了比內建的 fireEvent 方法更進階的瀏覽器互動模擬。

生命週期結束

本頁描述 user-event@13.5.0
此版本已不再維護。請改用 user-event@14,因為它包含重要的錯誤修復和新功能。

安裝

npm install --save-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}EnterN/A將插入新行字元 (僅限 <textarea />)。
{space}' 'N/A
{esc}EscapeN/A
{backspace}BackspaceN/A將刪除前一個字元(或 selectedRange 內的字元,請參閱以下範例)。
{del}DeleteN/A將刪除下一個字元(或 selectedRange 內的字元,請參閱以下範例)
{selectall}N/AN/A選取元素的所有文字。請注意,這僅適用於支援選取範圍的元素(因此,不包括 emailpasswordnumber 等)。
{arrowleft}ArrowLeftN/A
{arrowright}ArrowRightN/A
{arrowup}ArrowUpN/A
{arrowdown}ArrowDownN/A
{home}HomeN/A
{end}EndN/A
{shift}ShiftshiftKey不會將後面的字元大寫。
{ctrl}ControlctrlKey
{alt}AltaltKey
{meta}OSmetaKey
{capslock}CapsLockmodifierCapsLock使用時會觸發 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 會附加到現有的文字。若要將文字前置,請重設元素的選取範圍,並提供 initialSelectionStartinitialSelectionEnd 選項

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

keycode 的對應是由 預設按鍵對應 來執行的,該對應描述了「預設」美國鍵盤。您可以為每個選項提供您自己的本地鍵盤對應。

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')
})