Skip to content

Commit

Permalink
fix(keyboard): handle invalid or empty maxlength (#595)
Browse files Browse the repository at this point in the history
`maxlength` must be an integer value 0 or higher. If no maxlength is specified, or an invalid value is specified, the input or textarea has no maximum length.

* Support empty string for maxlength

An input's maxlength can be an empty string, which should NOT limit typing.

* fix: sanitize maxLength attribute

* test: mv tests

Co-authored-by: Philipp Fritsche <ph.fritsche@gmail.com>
  • Loading branch information
Aprillion and ph-fritsche authored Mar 18, 2021
1 parent 0ffb5f3 commit 2180430
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 111 deletions.
106 changes: 0 additions & 106 deletions src/__tests__/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,112 +255,6 @@ test('should delay the typing when opts.delay is not 0', async () => {
}
})

test('honors maxlength', () => {
const {element, getEventSnapshot} = setup('<input maxlength="2" />')
userEvent.type(element, '123')

// NOTE: no input event when typing "3"
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: input[value="12"]
input[value=""] - pointerover
input[value=""] - pointerenter
input[value=""] - mouseover: Left (0)
input[value=""] - mouseenter: Left (0)
input[value=""] - pointermove
input[value=""] - mousemove: Left (0)
input[value=""] - pointerdown
input[value=""] - mousedown: Left (0)
input[value=""] - focus
input[value=""] - focusin
input[value=""] - pointerup
input[value=""] - mouseup: Left (0)
input[value=""] - click: Left (0)
input[value=""] - keydown: 1 (49)
input[value=""] - keypress: 1 (49)
input[value="1"] - input
"{CURSOR}" -> "1{CURSOR}"
input[value="1"] - keyup: 1 (49)
input[value="1"] - keydown: 2 (50)
input[value="1"] - keypress: 2 (50)
input[value="12"] - input
"1{CURSOR}" -> "12{CURSOR}"
input[value="12"] - keyup: 2 (50)
input[value="12"] - keydown: 3 (51)
input[value="12"] - keypress: 3 (51)
input[value="12"] - keyup: 3 (51)
`)
})

test('honors maxlength with existing text', () => {
const {element, getEventSnapshot} = setup(
'<input value="12" maxlength="2" />',
)
userEvent.type(element, '3')

// NOTE: no input event when typing "3"
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: input[value="12"]
input[value="12"] - pointerover
input[value="12"] - pointerenter
input[value="12"] - mouseover: Left (0)
input[value="12"] - mouseenter: Left (0)
input[value="12"] - pointermove
input[value="12"] - mousemove: Left (0)
input[value="12"] - pointerdown
input[value="12"] - mousedown: Left (0)
input[value="12"] - focus
input[value="12"] - focusin
input[value="12"] - pointerup
input[value="12"] - mouseup: Left (0)
input[value="12"] - click: Left (0)
input[value="12"] - select
input[value="12"] - keydown: 3 (51)
input[value="12"] - keypress: 3 (51)
input[value="12"] - keyup: 3 (51)
`)
})

test('honors maxlength on textarea', () => {
const {element, getEventSnapshot} = setup(
'<textarea maxlength="2">12</textarea>',
)

userEvent.type(element, '3')

expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: textarea[value="12"]
textarea[value="12"] - pointerover
textarea[value="12"] - pointerenter
textarea[value="12"] - mouseover: Left (0)
textarea[value="12"] - mouseenter: Left (0)
textarea[value="12"] - pointermove
textarea[value="12"] - mousemove: Left (0)
textarea[value="12"] - pointerdown
textarea[value="12"] - mousedown: Left (0)
textarea[value="12"] - focus
textarea[value="12"] - focusin
textarea[value="12"] - pointerup
textarea[value="12"] - mouseup: Left (0)
textarea[value="12"] - click: Left (0)
textarea[value="12"] - select
textarea[value="12"] - keydown: 3 (51)
textarea[value="12"] - keypress: 3 (51)
textarea[value="12"] - keyup: 3 (51)
`)
})

// https://github.com/testing-library/user-event/issues/418
test('ignores maxlength on input[type=number]', () => {
const {element} = setup(`<input maxlength="2" type="number" value="12" />`)

userEvent.type(element, '3')

expect(element).toHaveValue(123)
})

test('should fire events on the currently focused element', () => {
const {element} = setup(`<div><input /><input /></div>`, {
eventHandlers: {keyDown: handleKeyDown},
Expand Down
148 changes: 148 additions & 0 deletions src/__tests__/utils/edit/calculateNewValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import userEvent from 'index'
import {setup} from '__tests__/helpers/utils'

// TODO: focus the maxlength tests on the tested aspects

test('honors maxlength', () => {
const {element, getEventSnapshot} = setup('<input maxlength="2" />')
userEvent.type(element as Element, '123')

// NOTE: no input event when typing "3"
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: input[value="12"]
input[value=""] - pointerover
input[value=""] - pointerenter
input[value=""] - mouseover: Left (0)
input[value=""] - mouseenter: Left (0)
input[value=""] - pointermove
input[value=""] - mousemove: Left (0)
input[value=""] - pointerdown
input[value=""] - mousedown: Left (0)
input[value=""] - focus
input[value=""] - focusin
input[value=""] - pointerup
input[value=""] - mouseup: Left (0)
input[value=""] - click: Left (0)
input[value=""] - keydown: 1 (49)
input[value=""] - keypress: 1 (49)
input[value="1"] - input
"{CURSOR}" -> "1{CURSOR}"
input[value="1"] - keyup: 1 (49)
input[value="1"] - keydown: 2 (50)
input[value="1"] - keypress: 2 (50)
input[value="12"] - input
"1{CURSOR}" -> "12{CURSOR}"
input[value="12"] - keyup: 2 (50)
input[value="12"] - keydown: 3 (51)
input[value="12"] - keypress: 3 (51)
input[value="12"] - keyup: 3 (51)
`)
})

test('honors maxlength="" as if there was no maxlength', () => {
const {element, getEventSnapshot} = setup('<input maxlength="" />')
userEvent.type(element as Element, '123')

expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: input[value="123"]
input[value=""] - pointerover
input[value=""] - pointerenter
input[value=""] - mouseover: Left (0)
input[value=""] - mouseenter: Left (0)
input[value=""] - pointermove
input[value=""] - mousemove: Left (0)
input[value=""] - pointerdown
input[value=""] - mousedown: Left (0)
input[value=""] - focus
input[value=""] - focusin
input[value=""] - pointerup
input[value=""] - mouseup: Left (0)
input[value=""] - click: Left (0)
input[value=""] - keydown: 1 (49)
input[value=""] - keypress: 1 (49)
input[value="1"] - input
"{CURSOR}" -> "1{CURSOR}"
input[value="1"] - keyup: 1 (49)
input[value="1"] - keydown: 2 (50)
input[value="1"] - keypress: 2 (50)
input[value="12"] - input
"1{CURSOR}" -> "12{CURSOR}"
input[value="12"] - keyup: 2 (50)
input[value="12"] - keydown: 3 (51)
input[value="12"] - keypress: 3 (51)
input[value="123"] - input
"12{CURSOR}" -> "123{CURSOR}"
input[value="123"] - keyup: 3 (51)
`)
})

test('honors maxlength with existing text', () => {
const {element, getEventSnapshot} = setup(
'<input value="12" maxlength="2" />',
)
userEvent.type(element as Element, '3')

// NOTE: no input event when typing "3"
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: input[value="12"]
input[value="12"] - pointerover
input[value="12"] - pointerenter
input[value="12"] - mouseover: Left (0)
input[value="12"] - mouseenter: Left (0)
input[value="12"] - pointermove
input[value="12"] - mousemove: Left (0)
input[value="12"] - pointerdown
input[value="12"] - mousedown: Left (0)
input[value="12"] - focus
input[value="12"] - focusin
input[value="12"] - pointerup
input[value="12"] - mouseup: Left (0)
input[value="12"] - click: Left (0)
input[value="12"] - select
input[value="12"] - keydown: 3 (51)
input[value="12"] - keypress: 3 (51)
input[value="12"] - keyup: 3 (51)
`)
})

test('honors maxlength on textarea', () => {
const {element, getEventSnapshot} = setup(
'<textarea maxlength="2">12</textarea>',
)

userEvent.type(element as Element, '3')

expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: textarea[value="12"]
textarea[value="12"] - pointerover
textarea[value="12"] - pointerenter
textarea[value="12"] - mouseover: Left (0)
textarea[value="12"] - mouseenter: Left (0)
textarea[value="12"] - pointermove
textarea[value="12"] - mousemove: Left (0)
textarea[value="12"] - pointerdown
textarea[value="12"] - mousedown: Left (0)
textarea[value="12"] - focus
textarea[value="12"] - focusin
textarea[value="12"] - pointerup
textarea[value="12"] - mouseup: Left (0)
textarea[value="12"] - click: Left (0)
textarea[value="12"] - select
textarea[value="12"] - keydown: 3 (51)
textarea[value="12"] - keypress: 3 (51)
textarea[value="12"] - keyup: 3 (51)
`)
})

// https://github.com/testing-library/user-event/issues/418
test('ignores maxlength on input[type=number]', () => {
const {element} = setup(`<input maxlength="2" type="number" value="12" />`)

userEvent.type(element as Element, '3')

expect(element).toHaveValue(123)
})
20 changes: 15 additions & 5 deletions src/utils/edit/calculateNewValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ export function calculateNewValue(
} {
const {selectionStart, selectionEnd} = selectionRange

// can't use .maxLength property because of a jsdom bug:
// https://github.com/jsdom/jsdom/issues/2927
const maxLength = Number(element.getAttribute('maxlength') ?? -1)

let newValue: string, newSelectionStart: number

if (selectionStart === null) {
Expand Down Expand Up @@ -72,7 +68,11 @@ export function calculateNewValue(
}
}

if (!supportsMaxLength(element) || maxLength < 0) {
// can't use .maxLength property because of a jsdom bug:
// https://github.com/jsdom/jsdom/issues/2927
const maxLength = getSanitizedMaxLength(element)

if (maxLength === undefined) {
return {
newValue,
newSelectionStart,
Expand All @@ -86,6 +86,16 @@ export function calculateNewValue(
}
}

function getSanitizedMaxLength(element: Element) {
if (!supportsMaxLength(element)) {
return undefined
}

const attr = element.getAttribute('maxlength') ?? ''

return /^\d+$/.test(attr) && Number(attr) >= 0 ? Number(attr) : undefined
}

function supportsMaxLength(element: Element) {
if (element.tagName === 'TEXTAREA') return true

Expand Down

0 comments on commit 2180430

Please sign in to comment.