From a6f13262a7cf87b4e768ebc30a9ca2218d7aa4fd Mon Sep 17 00:00:00 2001 From: Kayleigh Ridd Date: Thu, 4 Jun 2020 16:42:59 +0100 Subject: [PATCH] fix(type): fire type events on active element (#299) Co-authored-by: Kent C. Dodds --- src/__tests__/type.js | 62 ++++++++++++++++++++++++++++++++++++++++++- src/index.js | 37 ++++++++++++++++---------- 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/__tests__/type.js b/src/__tests__/type.js index 66dbf67b..26652f36 100644 --- a/src/__tests__/type.js +++ b/src/__tests__/type.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, {Fragment} from 'react' import {render, screen} from '@testing-library/react' import userEvent from '../../src' @@ -256,3 +256,63 @@ test.each(['input', 'textarea'])( expect(onKeyUp).not.toHaveBeenCalled() }, ) + +test('should fire events on the currently focused element', async () => { + const changeFocusLimit = 7 + const onKeyDown = jest.fn(event => { + if (event.target.value.length === changeFocusLimit) { + screen.getByTestId('input2').focus() + } + }) + + render( + + + + , + ) + + const text = 'Hello, world!' + + const input1 = screen.getByTestId('input1') + const input2 = screen.getByTestId('input2') + + await userEvent.type(input1, text) + + expect(input1).toHaveValue(text.slice(0, changeFocusLimit)) + expect(input2).toHaveValue(text.slice(changeFocusLimit)) + expect(input2).toHaveFocus() +}) + +test('should enter text up to maxLength of the current element if provided', async () => { + const changeFocusLimit = 7 + const input2MaxLength = 2 + + const onKeyDown = jest.fn(event => { + if (event.target.value.length === changeFocusLimit) { + screen.getByTestId('input2').focus() + } + }) + + render( + <> + + + , + ) + + const text = 'Hello, world!' + const input2ExpectedValue = text.slice( + changeFocusLimit, + changeFocusLimit + input2MaxLength, + ) + + const input1 = screen.getByTestId('input') + const input2 = screen.getByTestId('input2') + + await userEvent.type(input1, text) + + expect(input1).toHaveValue(text.slice(0, changeFocusLimit)) + expect(input2).toHaveValue(input2ExpectedValue) + expect(input2).toHaveFocus() +}) diff --git a/src/index.js b/src/index.js index a0920dd4..b1a8a1bf 100644 --- a/src/index.js +++ b/src/index.js @@ -334,21 +334,30 @@ async function type(...args) { async function typeImpl(element, text, {allAtOnce = false, delay} = {}) { if (element.disabled) return - const previousText = element.value - const computedText = - element.maxLength > 0 - ? text.slice(0, Math.max(element.maxLength - previousText.length, 0)) + element.focus() + + // The focussed element could change between each event, so get the currently active element each time + const currentElement = () => element.ownerDocument.activeElement + const currentValue = () => element.ownerDocument.activeElement.value + + const computeText = () => + currentElement().maxLength > 0 + ? text.slice( + 0, + Math.max(currentElement().maxLength - currentValue().length, 0), + ) : text if (allAtOnce) { if (!element.readOnly) { + const previousText = element.value + fireEvent.input(element, { - target: {value: previousText + computedText}, + target: {value: previousText + computeText()}, }) } } else { - let actuallyTyped = previousText for (let index = 0; index < text.length; index++) { const char = text[index] 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} = {}) { // eslint-disable-next-line no-await-in-loop if (delay > 0) await wait(delay) - const downEvent = fireEvent.keyDown(element, { + if (currentElement().disabled) return + + const downEvent = fireEvent.keyDown(currentElement(), { key, keyCode, which: keyCode, }) if (downEvent) { - const pressEvent = fireEvent.keyPress(element, { + const pressEvent = fireEvent.keyPress(currentElement(), { key, keyCode, charCode: keyCode, }) - const isTextPastThreshold = - (actuallyTyped + key).length > (previousText + computedText).length + const isTextPastThreshold = !computeText().length if (pressEvent && !isTextPastThreshold) { - actuallyTyped += key if (!element.readOnly) { - fireEvent.input(element, { + fireEvent.input(currentElement(), { target: { - value: actuallyTyped, + value: currentValue() + key, }, bubbles: true, cancelable: true, @@ -387,7 +396,7 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) { } } - fireEvent.keyUp(element, { + fireEvent.keyUp(currentElement(), { key, keyCode, which: keyCode,