Skip to content

Commit c0e89ea

Browse files
committed
feat(userEvent): add {del} and {selectall}
1 parent 03d08c8 commit c0e89ea

File tree

3 files changed

+175
-3
lines changed

3 files changed

+175
-3
lines changed

src/user-event/__tests__/type-modifiers.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,107 @@ test('{meta}{alt}{ctrl}a{/ctrl}{/alt}{/meta}', async () => {
342342
keyup: Meta (93)
343343
`)
344344
})
345+
346+
test('{selectall} selects all the text', async () => {
347+
const value = 'abcdefg'
348+
const {element, clearEventCalls, getEventCalls} = setup(
349+
`<input value="${value}" />`,
350+
)
351+
element.setSelectionRange(2, 6)
352+
353+
clearEventCalls()
354+
355+
await userEvent.type(element, '{selectall}')
356+
357+
expect(element.selectionStart).toBe(0)
358+
expect(element.selectionEnd).toBe(value.length)
359+
expect(getEventCalls()).toMatchInlineSnapshot(`
360+
Events fired on: input[value="abcdefg"]
361+
362+
focus
363+
select
364+
`)
365+
})
366+
367+
test('{del} at the start of the input', async () => {
368+
const {element, getEventCalls} = setup(`<input value="hello" />`)
369+
370+
await userEvent.type(element, '{del}', {
371+
initialSelectionStart: 0,
372+
initialSelectionEnd: 0,
373+
})
374+
375+
expect(element.selectionStart).toBe(0)
376+
expect(element.selectionEnd).toBe(0)
377+
expect(getEventCalls()).toMatchInlineSnapshot(`
378+
Events fired on: input[value="ello"]
379+
380+
focus
381+
select
382+
keydown: Delete (46)
383+
input: "{CURSOR}hello" -> "ello"
384+
select
385+
keyup: Delete (46)
386+
`)
387+
})
388+
389+
test('{del} at end of the input', async () => {
390+
const {element, getEventCalls} = setup(`<input value="hello" />`)
391+
392+
await userEvent.type(element, '{del}')
393+
394+
expect(element.selectionStart).toBe(element.value.length)
395+
expect(element.selectionEnd).toBe(element.value.length)
396+
expect(getEventCalls()).toMatchInlineSnapshot(`
397+
Events fired on: input[value="hello"]
398+
399+
focus
400+
select
401+
keydown: Delete (46)
402+
keyup: Delete (46)
403+
`)
404+
})
405+
406+
test('{del} in the middle of the input', async () => {
407+
const {element, getEventCalls} = setup(`<input value="hello" />`)
408+
409+
await userEvent.type(element, '{del}', {
410+
initialSelectionStart: 2,
411+
initialSelectionEnd: 2,
412+
})
413+
414+
expect(element.selectionStart).toBe(2)
415+
expect(element.selectionEnd).toBe(2)
416+
expect(getEventCalls()).toMatchInlineSnapshot(`
417+
Events fired on: input[value="helo"]
418+
419+
focus
420+
select
421+
keydown: Delete (46)
422+
input: "he{CURSOR}llo" -> "helo"
423+
select
424+
keyup: Delete (46)
425+
`)
426+
})
427+
428+
test('{del} with a selection range', async () => {
429+
const {element, getEventCalls} = setup(`<input value="hello" />`)
430+
431+
await userEvent.type(element, '{del}', {
432+
initialSelectionStart: 1,
433+
initialSelectionEnd: 3,
434+
})
435+
436+
expect(element.selectionStart).toBe(1)
437+
expect(element.selectionEnd).toBe(1)
438+
expect(getEventCalls()).toMatchInlineSnapshot(`
439+
Events fired on: input[value="hlo"]
440+
441+
focus
442+
select
443+
keydown: Delete (46)
444+
input: "h{SELECTION}el{/SELECTION}lo" -> "hlo"
445+
select
446+
keyup: Delete (46)
447+
`)
448+
})

src/user-event/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ async function upload(element, fileOrFiles, {clickInit, changeInit} = {}) {
403403
upload = wrapAsync(upload)
404404

405405
async function tab({shift = false, focusTrap = document} = {}) {
406+
// everything in user-event must be actually async, but since we're not
407+
// calling fireEvent in here, we'll add this tick here...
408+
await tick()
409+
406410
const focusableElements = focusTrap.querySelectorAll(
407411
'input, button, select, textarea, a[href], [tabindex]',
408412
)
@@ -464,9 +468,6 @@ async function tab({shift = false, focusTrap = document} = {}) {
464468
} else {
465469
next.focus()
466470
}
467-
// everything in user-event must be actually async, but since we're not
468-
// calling fireEvent in here, we'll add this tick here...
469-
await tick()
470471
}
471472
tab = wrapAsync(tab)
472473

src/user-event/type.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {wrapAsync} from '../wrap-async'
22
import {fireEvent} from './tick-fire-event'
3+
import {tick} from './tick'
34

45
function wait(time) {
56
return new Promise(resolve => setTimeout(() => resolve(), time))
@@ -160,6 +161,37 @@ async function type(
160161
...eventOverrides,
161162
})
162163
},
164+
'{del}': async ({eventOverrides}) => {
165+
const key = 'Delete'
166+
const keyCode = 46
167+
168+
const keyPressDefaultNotPrevented = await fireEvent.keyDown(
169+
currentElement(),
170+
{
171+
key,
172+
keyCode,
173+
which: keyCode,
174+
...eventOverrides,
175+
},
176+
)
177+
178+
if (keyPressDefaultNotPrevented) {
179+
await fireInputEventIfNeeded({
180+
...calculateNewDeleteValue(),
181+
eventOverrides: {
182+
inputType: 'deleteContentForward',
183+
...eventOverrides,
184+
},
185+
})
186+
}
187+
188+
await fireEvent.keyUp(currentElement(), {
189+
key,
190+
keyCode,
191+
which: keyCode,
192+
...eventOverrides,
193+
})
194+
},
163195
'{backspace}': async ({eventOverrides}) => {
164196
const key = 'Backspace'
165197
const keyCode = 8
@@ -191,6 +223,10 @@ async function type(
191223
...eventOverrides,
192224
})
193225
},
226+
'{selectall}': async () => {
227+
await tick()
228+
currentElement().setSelectionRange(0, currentValue().length)
229+
},
194230
}
195231
const eventCallbacks = []
196232
let remainingString = text
@@ -255,6 +291,7 @@ async function type(
255291
if (selectionStart === 0) {
256292
// at the beginning of the input
257293
newValue = value
294+
newSelectionStart = selectionStart
258295
} else if (selectionStart === value.length) {
259296
// at the end of the input
260297
newValue = value.slice(0, value.length - 1)
@@ -275,6 +312,36 @@ async function type(
275312
return {newValue, newSelectionStart}
276313
}
277314

315+
function calculateNewDeleteValue() {
316+
const {selectionStart, selectionEnd} = currentElement()
317+
const value = currentValue()
318+
let newValue
319+
320+
if (selectionStart === null) {
321+
// at the end of an input type that does not support selection ranges
322+
// https://github.com/testing-library/user-event/issues/316#issuecomment-639744793
323+
newValue = value
324+
} else if (selectionStart === selectionEnd) {
325+
if (selectionStart === 0) {
326+
// at the beginning of the input
327+
newValue = value.slice(1)
328+
} else if (selectionStart === value.length) {
329+
// at the end of the input
330+
newValue = value
331+
} else {
332+
// in the middle of the input
333+
newValue =
334+
value.slice(0, selectionStart) + value.slice(selectionEnd + 1)
335+
}
336+
} else {
337+
// we have something selected
338+
const firstPart = value.slice(0, selectionStart)
339+
newValue = firstPart + value.slice(selectionEnd)
340+
}
341+
342+
return {newValue, newSelectionStart: selectionStart}
343+
}
344+
278345
function calculateNewValue(newEntry) {
279346
const {selectionStart, selectionEnd} = currentElement()
280347
// can't use .maxLength property because of a jsdom bug:

0 commit comments

Comments
 (0)