Saltar al contenido principal

user-event v13

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

user-event es una biblioteca complementaria de Testing Library que ofrece una simulación más avanzada de interacciones del navegador que el método integrado fireEvent.

[Fin de vida]

Esta página describe user-event@13.5.0.
Esta versión ya no recibe mantenimiento. Por favor, utiliza user-event@14 en su lugar, ya que incluye correcciones de errores importantes y nuevas características.

Instalación

npm install --save-dev @testing-library/user-event @testing-library/dom

Ahora simplemente impórtala en tus pruebas:

import userEvent from '@testing-library/user-event'

// or

const {default: userEvent} = require('@testing-library/user-event')

API

Nota: Todos los métodos de userEvent son síncronos con una excepción: cuando se usa la opción delay con userEvent.type como se describe abajo. También desaconsejamos usar userEvent dentro de bloques before/after por razones importantes explicadas en "Evitar anidamiento en pruebas".

click(element, eventInit, options)

Hace clic en element. Dependiendo de qué element se hace clic, llamar a click() puede tener diferentes efectos secundarios.

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

También puedes hacer ctrlClick/shiftClick etc con

userEvent.click(elem, {ctrlKey: true, shiftKey: true})

Consulta la documentación del constructor MouseEvent para más opciones.

Ten en cuenta que click activará eventos hover antes del clic. Para desactivar esto, establece la opción skipHover en true.

Opciones de eventos de puntero

Intentar hacer clic en un elemento con pointer-events establecido en "none" (no clickeable) lanzará un error. Para deshabilitar este comportamiento, establece skipPointerEventsCheck en true:

userEvent.click(elem, undefined, {skipPointerEventsCheck: true})

La opción skipPointerEventsCheck puede pasarse a cualquier API relacionada con puntero, incluyendo:

dblClick(element, eventInit, options)

Hace doble clic en element. Dependiendo de lo que sea element, esto puede tener diferentes efectos secundarios.

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

Nota: options incluye opciones de eventos de puntero

type(element, text, [options])

Escribe text dentro de un <input> o <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 es el número de milisegundos entre la escritura de dos caracteres. Por defecto es 0. Puedes usar esta opción si tu componente tiene diferente comportamiento para usuarios rápidos o lentos. Si haces esto, ¡asegúrate de usar await!

type hará clic en el elemento antes de escribir. Para desactivar esto, establece la opción skipClick en true.

Caracteres especiales

Se admiten las siguientes cadenas de caracteres especiales:

Text stringKeyModifierNotes
{enter}EnterN/AWill insert a newline character (<textarea /> only).
{space}' 'N/A
{esc}EscapeN/A
{backspace}BackspaceN/AWill delete the previous character (or the characters within the selectedRange, see example below).
{del}DeleteN/AWill delete the next character (or the characters within the selectedRange, see example below)
{selectall}N/AN/ASelects all the text of the element. Note that this will only work for elements that support selection ranges (so, not email, password, number, among others)
{arrowleft}ArrowLeftN/A
{arrowright}ArrowRightN/A
{arrowup}ArrowUpN/A
{arrowdown}ArrowDownN/A
{home}HomeN/A
{end}EndN/A
{shift}ShiftshiftKeyDoes not capitalize following characters.
{ctrl}ControlctrlKey
{alt}AltaltKey
{meta}OSmetaKey
{capslock}CapsLockmodifierCapsLockFires both keydown and keyup when used (simulates a user clicking their "Caps Lock" button to enable caps lock).

Nota sobre modificadores: Las teclas modificadoras ({shift}, {ctrl}, {alt}, {meta}) activarán sus modificadores de evento correspondientes durante la ejecución del comando type o hasta que se cierren (vía {/shift}, {/ctrl}, etc.). Si no se cierran explícitamente, se dispararán eventos para cerrarlas automáticamente (para deshabilitar esto, establece skipAutoClose en true).

Adoptamos la misma postura que Cypress en que no simulamos el comportamiento de combinaciones de teclas modificadoras, ya que diferentes sistemas operativos funcionan distinto en este aspecto.

Un ejemplo de uso con un rango de selección:

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

Por defecto, type añade al texto existente. Para anteponer texto, restablece el rango de selección del elemento y proporciona las opciones initialSelectionStart e 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!')
})

Soporte para <input type="time" />

El siguiente es un ejemplo de uso de esta biblioteca con <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)

Simula los eventos de teclado descritos por text. Es similar a userEvent.type() pero sin hacer clic ni cambiar el rango de selección.

Debes usar userEvent.keyboard si solo quieres simular pulsaciones de teclas. Usa userEvent.type si solo quieres insertar texto convenientemente en un campo de entrada o área de texto.

Las pulsaciones se pueden describir:

  • Por carácter imprimible

    userEvent.keyboard('foo') // translates to: f, o, o

    Los corchetes { y [ son caracteres especiales y se referencian duplicándolos.

    userEvent.keyboard('{{a[[') // translates to: {, a, [
  • Por KeyboardEvent.key (solo admite valores alfanuméricos de key)

    userEvent.keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o

    Esto no mantiene teclas presionadas. Shift se liberará antes de pulsar f.

  • Por KeyboardEvent.code

    userEvent.keyboard('[ShiftLeft][KeyF][KeyO][KeyO]') // translates to: Shift, f, o, o
  • Por modificadores/caracteres especiales heredados de userEvent.type: Los modificadores como {shift} (en minúscula) se mantendrán presionados automáticamente. Puedes cancelar este comportamiento añadiendo / al descriptor.

    userEvent.keyboard('{shift}{ctrl/}a{/shift}') // translates to: Shift(down), Control(down+up), a, Shift(up)

Las teclas pueden mantenerse presionadas añadiendo > al descriptor, y liberarse añadiendo / al principio del descriptor:

userEvent.keyboard('{Shift>}A{/Shift}') // translates to: Shift(down), A, Shift(up)

userEvent.keyboard devuelve un estado de teclado que puede usarse para continuar operaciones.

const keyboardState = userEvent.keyboard('[ControlLeft>]') // keydown [ControlLeft]
// ... inspect some changes ...
userEvent.keyboard('a', {keyboardState}) // press [KeyA] with active ctrlKey modifier

El mapeo de key a code lo realiza un mapa de teclas predeterminado que representa un teclado US "por defecto". Puedes proporcionar tu propio mapeo local mediante opciones.

userEvent.keyboard('?', {keyboardMap: myOwnLocaleKeyboardMap})

Versiones futuras podrían interpolar los modificadores necesarios para teclas imprimibles (ej: pulsar {Shift} automáticamente cuando CapsLock está inactivo y se referencia A). Para desactivar este comportamiento, pasa autoModify: false al usar userEvent.keyboard.

upload(element, file, [{ clickInit, changeInit }], [options])

Sube archivos a un <input>. Para múltiples archivos, usa <input> con atributo multiple y pasa el segundo argumento upload como array. También puedes inicializar eventos click/change mediante un tercer argumento.

Si options.applyAccept es true y existe un atributo accept en el elemento, los archivos no coincidentes se descartarán.

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)

Selecciona y elimina el texto dentro de un <input> o <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)

Selecciona las opciones especificadas de un elemento <select> o <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)
})

El parámetro values puede ser un array de valores o un valor escalar singular.

También acepta nodos de opción:

userEvent.selectOptions(screen.getByTestId('select-multiple'), [
screen.getByText('A'),
screen.getByText('B'),
])

Nota: options incluye opciones de eventos de puntero

deselectOptions(element, values, options)

Elimina la selección de las opciones especificadas en un elemento <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'])
})

El parámetro values puede ser un array de valores o un valor escalar singular.

Nota: options incluye opciones de eventos de puntero

tab({shift, focusTrap})

Dispara un evento de tabulación cambiando el document.activeElement como lo haría el navegador.

Opciones:

  • shift (por defecto false) puede ser true o false para invertir la dirección de tabulación.

  • focusTrap (por defecto document) elemento contenedor para restringir la tabulación dentro de él.

Nota sobre tabulación:
jsdom no admite tabulación,
por lo que esta característica permite verificar la tabulación desde la perspectiva del usuario.
Sin embargo, esta limitación en jsdom implica que componentes como
focus-trap-react no funcionarán
con userEvent.tab() o jsdom. Por ello, la opción focusTrap existe para garantizar
que el usuario permanezca restringido dentro de una trampa de enfoque.

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)

Pasa el cursor sobre 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()
})

Nota: options incluye opciones de eventos de puntero

unhover(element, options)

Retira el cursor de element.

Ver más arriba para un ejemplo

Nota: options incluye opciones de eventos de puntero

paste(element, text, eventInit, options)

Permite simular que el usuario pega texto en un input.

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

Puedes usar eventInit si lo pegado requiere clipboardData (como files).

specialChars

Conjunto de caracteres especiales usados en el método type.

KeyCharacter
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' '

Ejemplo de uso:

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