Skip to content

Commit a6f1326

Browse files
fix(type): fire type events on active element (testing-library#299)
Co-authored-by: Kent C. Dodds <[email protected]>
1 parent 5dec819 commit a6f1326

File tree

2 files changed

+84
-15
lines changed

2 files changed

+84
-15
lines changed

src/__tests__/type.js

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, {Fragment} from 'react'
22
import {render, screen} from '@testing-library/react'
33
import userEvent from '../../src'
44

@@ -256,3 +256,63 @@ test.each(['input', 'textarea'])(
256256
expect(onKeyUp).not.toHaveBeenCalled()
257257
},
258258
)
259+
260+
test('should fire events on the currently focused element', async () => {
261+
const changeFocusLimit = 7
262+
const onKeyDown = jest.fn(event => {
263+
if (event.target.value.length === changeFocusLimit) {
264+
screen.getByTestId('input2').focus()
265+
}
266+
})
267+
268+
render(
269+
<Fragment>
270+
<input data-testid="input1" onKeyDown={onKeyDown} />
271+
<input data-testid="input2" />
272+
</Fragment>,
273+
)
274+
275+
const text = 'Hello, world!'
276+
277+
const input1 = screen.getByTestId('input1')
278+
const input2 = screen.getByTestId('input2')
279+
280+
await userEvent.type(input1, text)
281+
282+
expect(input1).toHaveValue(text.slice(0, changeFocusLimit))
283+
expect(input2).toHaveValue(text.slice(changeFocusLimit))
284+
expect(input2).toHaveFocus()
285+
})
286+
287+
test('should enter text up to maxLength of the current element if provided', async () => {
288+
const changeFocusLimit = 7
289+
const input2MaxLength = 2
290+
291+
const onKeyDown = jest.fn(event => {
292+
if (event.target.value.length === changeFocusLimit) {
293+
screen.getByTestId('input2').focus()
294+
}
295+
})
296+
297+
render(
298+
<>
299+
<input data-testid="input" onKeyDown={onKeyDown} />
300+
<input data-testid="input2" maxLength={input2MaxLength} />
301+
</>,
302+
)
303+
304+
const text = 'Hello, world!'
305+
const input2ExpectedValue = text.slice(
306+
changeFocusLimit,
307+
changeFocusLimit + input2MaxLength,
308+
)
309+
310+
const input1 = screen.getByTestId('input')
311+
const input2 = screen.getByTestId('input2')
312+
313+
await userEvent.type(input1, text)
314+
315+
expect(input1).toHaveValue(text.slice(0, changeFocusLimit))
316+
expect(input2).toHaveValue(input2ExpectedValue)
317+
expect(input2).toHaveFocus()
318+
})

src/index.js

+23-14
Original file line numberDiff line numberDiff line change
@@ -334,21 +334,30 @@ async function type(...args) {
334334

335335
async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
336336
if (element.disabled) return
337-
const previousText = element.value
338337

339-
const computedText =
340-
element.maxLength > 0
341-
? text.slice(0, Math.max(element.maxLength - previousText.length, 0))
338+
element.focus()
339+
340+
// The focussed element could change between each event, so get the currently active element each time
341+
const currentElement = () => element.ownerDocument.activeElement
342+
const currentValue = () => element.ownerDocument.activeElement.value
343+
344+
const computeText = () =>
345+
currentElement().maxLength > 0
346+
? text.slice(
347+
0,
348+
Math.max(currentElement().maxLength - currentValue().length, 0),
349+
)
342350
: text
343351

344352
if (allAtOnce) {
345353
if (!element.readOnly) {
354+
const previousText = element.value
355+
346356
fireEvent.input(element, {
347-
target: {value: previousText + computedText},
357+
target: {value: previousText + computeText()},
348358
})
349359
}
350360
} else {
351-
let actuallyTyped = previousText
352361
for (let index = 0; index < text.length; index++) {
353362
const char = text[index]
354363
const key = char // TODO: check if this also valid for characters with diacritic markers e.g. úé etc
@@ -357,28 +366,28 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
357366
// eslint-disable-next-line no-await-in-loop
358367
if (delay > 0) await wait(delay)
359368

360-
const downEvent = fireEvent.keyDown(element, {
369+
if (currentElement().disabled) return
370+
371+
const downEvent = fireEvent.keyDown(currentElement(), {
361372
key,
362373
keyCode,
363374
which: keyCode,
364375
})
365376

366377
if (downEvent) {
367-
const pressEvent = fireEvent.keyPress(element, {
378+
const pressEvent = fireEvent.keyPress(currentElement(), {
368379
key,
369380
keyCode,
370381
charCode: keyCode,
371382
})
372383

373-
const isTextPastThreshold =
374-
(actuallyTyped + key).length > (previousText + computedText).length
384+
const isTextPastThreshold = !computeText().length
375385

376386
if (pressEvent && !isTextPastThreshold) {
377-
actuallyTyped += key
378387
if (!element.readOnly) {
379-
fireEvent.input(element, {
388+
fireEvent.input(currentElement(), {
380389
target: {
381-
value: actuallyTyped,
390+
value: currentValue() + key,
382391
},
383392
bubbles: true,
384393
cancelable: true,
@@ -387,7 +396,7 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
387396
}
388397
}
389398

390-
fireEvent.keyUp(element, {
399+
fireEvent.keyUp(currentElement(), {
391400
key,
392401
keyCode,
393402
which: keyCode,

0 commit comments

Comments
 (0)