user-event v13
user-event
是测试库的配套库,它提供比内置的 fireEvent
方法更高级的浏览器交互模拟。
本页介绍 [email protected]
。
此版本不再维护。请改用 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
将在单击之前触发悬停事件。要禁用此功能,请将 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])
在 <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
是两个字符之间的键入时间(以毫秒为单位)。默认情况下,它为 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(模拟用户单击其“Caps Lock”按钮以启用大小写锁定)。 |
关于修饰符的说明: 修饰符键(
{shift}
、{ctrl}
、{alt}
、{meta}
)将为 type 命令的持续时间激活其相应的事件修饰符,直到它们被关闭(通过{/shift}
、{/ctrl}
等)。如果它们没有被明确关闭,则会触发事件来自动关闭它们(要禁用此功能,请将skipAutoClose
选项设置为true
)。
使用选择范围的示例
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
修饰符/特殊字符 像{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} |
空白符 | ' ' |
使用示例
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')
})