diff --git a/__tests__/react/type.js b/__tests__/react/type.js index 52d01464..f2b61552 100644 --- a/__tests__/react/type.js +++ b/__tests__/react/type.js @@ -135,4 +135,55 @@ describe("userEvent.type", () => { expect(getByTestId("input")).toHaveProperty("value", text); } ); + + it.each(["input", "textarea"])( + "should type text in <%s> up to maxLength if provided", + type => { + const onChange = jest.fn(); + const onKeyDown = jest.fn(); + const onKeyPress = jest.fn(); + const onKeyUp = jest.fn(); + const maxLength = 10; + + const { getByTestId } = render( + React.createElement(type, { + "data-testid": "input", + onChange, + onKeyDown, + onKeyPress, + onKeyUp, + maxLength + }) + ); + + const text = "superlongtext"; + const slicedText = text.slice(0, maxLength); + + const inputEl = getByTestId("input"); + + userEvent.type(inputEl, text); + + expect(inputEl).toHaveProperty("value", slicedText); + expect(onChange).toHaveBeenCalledTimes(slicedText.length); + expect(onKeyPress).toHaveBeenCalledTimes(text.length); + expect(onKeyDown).toHaveBeenCalledTimes(text.length); + expect(onKeyUp).toHaveBeenCalledTimes(text.length); + + inputEl.value = ""; + onChange.mockClear(); + onKeyPress.mockClear(); + onKeyDown.mockClear(); + onKeyUp.mockClear(); + + userEvent.type(inputEl, text, { + allAtOnce: true + }); + + expect(inputEl).toHaveProperty("value", slicedText); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onKeyPress).not.toHaveBeenCalled(); + expect(onKeyDown).not.toHaveBeenCalled(); + expect(onKeyUp).not.toHaveBeenCalled(); + } + ); }); diff --git a/__tests__/vue/type.js b/__tests__/vue/type.js index 04a88600..4ce3fe7f 100644 --- a/__tests__/vue/type.js +++ b/__tests__/vue/type.js @@ -145,4 +145,48 @@ describe("userEvent.type", () => { expect(input).toHaveBeenCalledTimes(1); } ); + + it.each(["input", "textarea"])( + "should type text in <%s> up to maxLength if provided", + type => { + const input = jest.fn(); + const keydown = jest.fn(); + const keypress = jest.fn(); + const keyup = jest.fn(); + const maxLength = 10; + + const { getByTestId } = renderComponent( + type, + { input, keydown, keypress, keyup }, + { maxLength } + ); + + const text = "superlongtext"; + const slicedText = text.slice(0, maxLength); + + const inputEl = getByTestId("input"); + + userEvent.type(inputEl, text); + expect(inputEl).toHaveProperty("value", slicedText); + expect(keydown).toHaveBeenCalledTimes(text.length); + expect(keypress).toHaveBeenCalledTimes(text.length); + expect(keyup).toHaveBeenCalledTimes(text.length); + + inputEl.value = ""; + input.mockClear(); + keydown.mockClear(); + keypress.mockClear(); + keyup.mockClear(); + + userEvent.type(inputEl, text, { + allAtOnce: true + }); + + expect(inputEl).toHaveProperty("value", slicedText); + expect(input).toHaveBeenCalledTimes(1); + expect(keydown).not.toHaveBeenCalled(); + expect(keypress).not.toHaveBeenCalled(); + expect(keyup).not.toHaveBeenCalled(); + } + ); }); diff --git a/src/index.js b/src/index.js index caf7e0a5..c6637987 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import { fireEvent } from "@testing-library/dom"; function wait(time) { - return new Promise(function (resolve) { + return new Promise(function(resolve) { setTimeout(() => resolve(), time); }); } @@ -171,9 +171,9 @@ const userEvent = { clickElement(element); const valArray = Array.isArray(values) ? values : [values]; - const selectedOptions = Array.from(element.querySelectorAll('option')).filter( - opt => valArray.includes(opt.value) - ); + const selectedOptions = Array.from( + element.querySelectorAll("option") + ).filter(opt => valArray.includes(opt.value)); if (selectedOptions.length > 0) { if (element.multiple) { @@ -193,9 +193,12 @@ const userEvent = { delay: 0 }; const opts = Object.assign(defaultOpts, userOpts); + + const computedText = text.slice(0, element.maxLength || text.length); + if (opts.allAtOnce) { if (element.readOnly) return; - fireEvent.input(element, { target: { value: text } }); + fireEvent.input(element, { target: { value: computedText } }); } else { let actuallyTyped = ""; for (let index = 0; index < text.length; index++) { @@ -210,13 +213,18 @@ const userEvent = { keyCode: keyCode, which: keyCode }); + if (downEvent) { const pressEvent = fireEvent.keyPress(element, { key: key, keyCode, charCode: keyCode }); - if (pressEvent) { + + const isTextPastThreshold = + (actuallyTyped + key).length > computedText.length; + + if (pressEvent && !isTextPastThreshold) { actuallyTyped += key; if (!element.readOnly) fireEvent.input(element, { @@ -244,9 +252,11 @@ const userEvent = { "input, button, select, textarea, a[href], [tabindex]" ); - let list = Array.prototype.filter.call(focusableElements, function (item) { - return item.getAttribute("tabindex") !== "-1"&& !item.disabled; - }).map((el, idx) => ({ el, idx })) + let list = Array.prototype.filter + .call(focusableElements, function(item) { + return item.getAttribute("tabindex") !== "-1" && !item.disabled; + }) + .map((el, idx) => ({ el, idx })) .sort((a, b) => { const tabIndexA = a.el.getAttribute("tabindex"); const tabIndexB = b.el.getAttribute("tabindex"); @@ -254,14 +264,14 @@ const userEvent = { const diff = tabIndexA - tabIndexB; return diff !== 0 ? diff : a.idx - b.idx; - }) + }); const index = list.findIndex(({ el }) => el === document.activeElement); let nextIndex = shift ? index - 1 : index + 1; let defaultIndex = shift ? list.length - 1 : 0; - const { el: next } = (list[nextIndex] || list[defaultIndex]); + const { el: next } = list[nextIndex] || list[defaultIndex]; if (next.getAttribute("tabindex") === null) { next.setAttribute("tabindex", "0"); // jsdom requires tabIndex=0 for an item to become 'document.activeElement' (the browser does not)