Skip to content

Commit 2180430

Browse files
fix(keyboard): handle invalid or empty maxlength (#595)
`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 <[email protected]>
1 parent 0ffb5f3 commit 2180430

File tree

3 files changed

+163
-111
lines changed

3 files changed

+163
-111
lines changed

src/__tests__/type.js

Lines changed: 0 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -255,112 +255,6 @@ test('should delay the typing when opts.delay is not 0', async () => {
255255
}
256256
})
257257

258-
test('honors maxlength', () => {
259-
const {element, getEventSnapshot} = setup('<input maxlength="2" />')
260-
userEvent.type(element, '123')
261-
262-
// NOTE: no input event when typing "3"
263-
expect(getEventSnapshot()).toMatchInlineSnapshot(`
264-
Events fired on: input[value="12"]
265-
266-
input[value=""] - pointerover
267-
input[value=""] - pointerenter
268-
input[value=""] - mouseover: Left (0)
269-
input[value=""] - mouseenter: Left (0)
270-
input[value=""] - pointermove
271-
input[value=""] - mousemove: Left (0)
272-
input[value=""] - pointerdown
273-
input[value=""] - mousedown: Left (0)
274-
input[value=""] - focus
275-
input[value=""] - focusin
276-
input[value=""] - pointerup
277-
input[value=""] - mouseup: Left (0)
278-
input[value=""] - click: Left (0)
279-
input[value=""] - keydown: 1 (49)
280-
input[value=""] - keypress: 1 (49)
281-
input[value="1"] - input
282-
"{CURSOR}" -> "1{CURSOR}"
283-
input[value="1"] - keyup: 1 (49)
284-
input[value="1"] - keydown: 2 (50)
285-
input[value="1"] - keypress: 2 (50)
286-
input[value="12"] - input
287-
"1{CURSOR}" -> "12{CURSOR}"
288-
input[value="12"] - keyup: 2 (50)
289-
input[value="12"] - keydown: 3 (51)
290-
input[value="12"] - keypress: 3 (51)
291-
input[value="12"] - keyup: 3 (51)
292-
`)
293-
})
294-
295-
test('honors maxlength with existing text', () => {
296-
const {element, getEventSnapshot} = setup(
297-
'<input value="12" maxlength="2" />',
298-
)
299-
userEvent.type(element, '3')
300-
301-
// NOTE: no input event when typing "3"
302-
expect(getEventSnapshot()).toMatchInlineSnapshot(`
303-
Events fired on: input[value="12"]
304-
305-
input[value="12"] - pointerover
306-
input[value="12"] - pointerenter
307-
input[value="12"] - mouseover: Left (0)
308-
input[value="12"] - mouseenter: Left (0)
309-
input[value="12"] - pointermove
310-
input[value="12"] - mousemove: Left (0)
311-
input[value="12"] - pointerdown
312-
input[value="12"] - mousedown: Left (0)
313-
input[value="12"] - focus
314-
input[value="12"] - focusin
315-
input[value="12"] - pointerup
316-
input[value="12"] - mouseup: Left (0)
317-
input[value="12"] - click: Left (0)
318-
input[value="12"] - select
319-
input[value="12"] - keydown: 3 (51)
320-
input[value="12"] - keypress: 3 (51)
321-
input[value="12"] - keyup: 3 (51)
322-
`)
323-
})
324-
325-
test('honors maxlength on textarea', () => {
326-
const {element, getEventSnapshot} = setup(
327-
'<textarea maxlength="2">12</textarea>',
328-
)
329-
330-
userEvent.type(element, '3')
331-
332-
expect(getEventSnapshot()).toMatchInlineSnapshot(`
333-
Events fired on: textarea[value="12"]
334-
335-
textarea[value="12"] - pointerover
336-
textarea[value="12"] - pointerenter
337-
textarea[value="12"] - mouseover: Left (0)
338-
textarea[value="12"] - mouseenter: Left (0)
339-
textarea[value="12"] - pointermove
340-
textarea[value="12"] - mousemove: Left (0)
341-
textarea[value="12"] - pointerdown
342-
textarea[value="12"] - mousedown: Left (0)
343-
textarea[value="12"] - focus
344-
textarea[value="12"] - focusin
345-
textarea[value="12"] - pointerup
346-
textarea[value="12"] - mouseup: Left (0)
347-
textarea[value="12"] - click: Left (0)
348-
textarea[value="12"] - select
349-
textarea[value="12"] - keydown: 3 (51)
350-
textarea[value="12"] - keypress: 3 (51)
351-
textarea[value="12"] - keyup: 3 (51)
352-
`)
353-
})
354-
355-
// https://github.com/testing-library/user-event/issues/418
356-
test('ignores maxlength on input[type=number]', () => {
357-
const {element} = setup(`<input maxlength="2" type="number" value="12" />`)
358-
359-
userEvent.type(element, '3')
360-
361-
expect(element).toHaveValue(123)
362-
})
363-
364258
test('should fire events on the currently focused element', () => {
365259
const {element} = setup(`<div><input /><input /></div>`, {
366260
eventHandlers: {keyDown: handleKeyDown},
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import userEvent from 'index'
2+
import {setup} from '__tests__/helpers/utils'
3+
4+
// TODO: focus the maxlength tests on the tested aspects
5+
6+
test('honors maxlength', () => {
7+
const {element, getEventSnapshot} = setup('<input maxlength="2" />')
8+
userEvent.type(element as Element, '123')
9+
10+
// NOTE: no input event when typing "3"
11+
expect(getEventSnapshot()).toMatchInlineSnapshot(`
12+
Events fired on: input[value="12"]
13+
14+
input[value=""] - pointerover
15+
input[value=""] - pointerenter
16+
input[value=""] - mouseover: Left (0)
17+
input[value=""] - mouseenter: Left (0)
18+
input[value=""] - pointermove
19+
input[value=""] - mousemove: Left (0)
20+
input[value=""] - pointerdown
21+
input[value=""] - mousedown: Left (0)
22+
input[value=""] - focus
23+
input[value=""] - focusin
24+
input[value=""] - pointerup
25+
input[value=""] - mouseup: Left (0)
26+
input[value=""] - click: Left (0)
27+
input[value=""] - keydown: 1 (49)
28+
input[value=""] - keypress: 1 (49)
29+
input[value="1"] - input
30+
"{CURSOR}" -> "1{CURSOR}"
31+
input[value="1"] - keyup: 1 (49)
32+
input[value="1"] - keydown: 2 (50)
33+
input[value="1"] - keypress: 2 (50)
34+
input[value="12"] - input
35+
"1{CURSOR}" -> "12{CURSOR}"
36+
input[value="12"] - keyup: 2 (50)
37+
input[value="12"] - keydown: 3 (51)
38+
input[value="12"] - keypress: 3 (51)
39+
input[value="12"] - keyup: 3 (51)
40+
`)
41+
})
42+
43+
test('honors maxlength="" as if there was no maxlength', () => {
44+
const {element, getEventSnapshot} = setup('<input maxlength="" />')
45+
userEvent.type(element as Element, '123')
46+
47+
expect(getEventSnapshot()).toMatchInlineSnapshot(`
48+
Events fired on: input[value="123"]
49+
50+
input[value=""] - pointerover
51+
input[value=""] - pointerenter
52+
input[value=""] - mouseover: Left (0)
53+
input[value=""] - mouseenter: Left (0)
54+
input[value=""] - pointermove
55+
input[value=""] - mousemove: Left (0)
56+
input[value=""] - pointerdown
57+
input[value=""] - mousedown: Left (0)
58+
input[value=""] - focus
59+
input[value=""] - focusin
60+
input[value=""] - pointerup
61+
input[value=""] - mouseup: Left (0)
62+
input[value=""] - click: Left (0)
63+
input[value=""] - keydown: 1 (49)
64+
input[value=""] - keypress: 1 (49)
65+
input[value="1"] - input
66+
"{CURSOR}" -> "1{CURSOR}"
67+
input[value="1"] - keyup: 1 (49)
68+
input[value="1"] - keydown: 2 (50)
69+
input[value="1"] - keypress: 2 (50)
70+
input[value="12"] - input
71+
"1{CURSOR}" -> "12{CURSOR}"
72+
input[value="12"] - keyup: 2 (50)
73+
input[value="12"] - keydown: 3 (51)
74+
input[value="12"] - keypress: 3 (51)
75+
input[value="123"] - input
76+
"12{CURSOR}" -> "123{CURSOR}"
77+
input[value="123"] - keyup: 3 (51)
78+
`)
79+
})
80+
81+
test('honors maxlength with existing text', () => {
82+
const {element, getEventSnapshot} = setup(
83+
'<input value="12" maxlength="2" />',
84+
)
85+
userEvent.type(element as Element, '3')
86+
87+
// NOTE: no input event when typing "3"
88+
expect(getEventSnapshot()).toMatchInlineSnapshot(`
89+
Events fired on: input[value="12"]
90+
91+
input[value="12"] - pointerover
92+
input[value="12"] - pointerenter
93+
input[value="12"] - mouseover: Left (0)
94+
input[value="12"] - mouseenter: Left (0)
95+
input[value="12"] - pointermove
96+
input[value="12"] - mousemove: Left (0)
97+
input[value="12"] - pointerdown
98+
input[value="12"] - mousedown: Left (0)
99+
input[value="12"] - focus
100+
input[value="12"] - focusin
101+
input[value="12"] - pointerup
102+
input[value="12"] - mouseup: Left (0)
103+
input[value="12"] - click: Left (0)
104+
input[value="12"] - select
105+
input[value="12"] - keydown: 3 (51)
106+
input[value="12"] - keypress: 3 (51)
107+
input[value="12"] - keyup: 3 (51)
108+
`)
109+
})
110+
111+
test('honors maxlength on textarea', () => {
112+
const {element, getEventSnapshot} = setup(
113+
'<textarea maxlength="2">12</textarea>',
114+
)
115+
116+
userEvent.type(element as Element, '3')
117+
118+
expect(getEventSnapshot()).toMatchInlineSnapshot(`
119+
Events fired on: textarea[value="12"]
120+
121+
textarea[value="12"] - pointerover
122+
textarea[value="12"] - pointerenter
123+
textarea[value="12"] - mouseover: Left (0)
124+
textarea[value="12"] - mouseenter: Left (0)
125+
textarea[value="12"] - pointermove
126+
textarea[value="12"] - mousemove: Left (0)
127+
textarea[value="12"] - pointerdown
128+
textarea[value="12"] - mousedown: Left (0)
129+
textarea[value="12"] - focus
130+
textarea[value="12"] - focusin
131+
textarea[value="12"] - pointerup
132+
textarea[value="12"] - mouseup: Left (0)
133+
textarea[value="12"] - click: Left (0)
134+
textarea[value="12"] - select
135+
textarea[value="12"] - keydown: 3 (51)
136+
textarea[value="12"] - keypress: 3 (51)
137+
textarea[value="12"] - keyup: 3 (51)
138+
`)
139+
})
140+
141+
// https://github.com/testing-library/user-event/issues/418
142+
test('ignores maxlength on input[type=number]', () => {
143+
const {element} = setup(`<input maxlength="2" type="number" value="12" />`)
144+
145+
userEvent.type(element as Element, '3')
146+
147+
expect(element).toHaveValue(123)
148+
})

src/utils/edit/calculateNewValue.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ export function calculateNewValue(
1414
} {
1515
const {selectionStart, selectionEnd} = selectionRange
1616

17-
// can't use .maxLength property because of a jsdom bug:
18-
// https://github.com/jsdom/jsdom/issues/2927
19-
const maxLength = Number(element.getAttribute('maxlength') ?? -1)
20-
2117
let newValue: string, newSelectionStart: number
2218

2319
if (selectionStart === null) {
@@ -72,7 +68,11 @@ export function calculateNewValue(
7268
}
7369
}
7470

75-
if (!supportsMaxLength(element) || maxLength < 0) {
71+
// can't use .maxLength property because of a jsdom bug:
72+
// https://github.com/jsdom/jsdom/issues/2927
73+
const maxLength = getSanitizedMaxLength(element)
74+
75+
if (maxLength === undefined) {
7676
return {
7777
newValue,
7878
newSelectionStart,
@@ -86,6 +86,16 @@ export function calculateNewValue(
8686
}
8787
}
8888

89+
function getSanitizedMaxLength(element: Element) {
90+
if (!supportsMaxLength(element)) {
91+
return undefined
92+
}
93+
94+
const attr = element.getAttribute('maxlength') ?? ''
95+
96+
return /^\d+$/.test(attr) && Number(attr) >= 0 ? Number(attr) : undefined
97+
}
98+
8999
function supportsMaxLength(element: Element) {
90100
if (element.tagName === 'TEXTAREA') return true
91101

0 commit comments

Comments
 (0)