@@ -26,32 +26,59 @@ const getActiveElement = document => {
26
26
}
27
27
}
28
28
29
- async function typeImpl ( element , text , { allAtOnce = false , delay} = { } ) {
29
+ // eslint-disable-next-line complexity
30
+ async function typeImpl (
31
+ element ,
32
+ text ,
33
+ { allAtOnce = false , delay, initialSelectionStart, initialSelectionEnd} = { } ,
34
+ ) {
30
35
if ( element . disabled ) return
31
36
32
37
element . focus ( )
33
38
34
39
// The focused element could change between each event, so get the currently active element each time
35
40
const currentElement = ( ) => getActiveElement ( element . ownerDocument )
36
41
const currentValue = ( ) => currentElement ( ) . value
37
- const setSelectionRange = newSelectionStart => {
38
- // if the actual selection start is different from the one we expected
39
- // then we set it to the end of the input
40
- if ( currentElement ( ) . selectionStart !== newSelectionStart ) {
41
- currentElement ( ) . setSelectionRange ?. (
42
- currentValue ( ) . length ,
43
- currentValue ( ) . length ,
44
- )
42
+ const setSelectionRange = ( { newValue, newSelectionStart} ) => {
43
+ // if we *can* change the selection start, then we will if the new value
44
+ // is the same as the current value (so it wasn't programatically changed
45
+ // when the fireEvent.input was triggered).
46
+ // The reason we have to do this at all is because it actually *is*
47
+ // programmatically changed by fireEvent.input, so we have to simulate the
48
+ // browser's default behavior
49
+ if (
50
+ currentElement ( ) . selectionStart !== null &&
51
+ currentValue ( ) === newValue
52
+ ) {
53
+ currentElement ( ) . setSelectionRange ?. ( newSelectionStart , newSelectionStart )
45
54
}
46
55
}
47
56
57
+ // by default, a new element has it's selection start and end at 0
58
+ // but most of the time when people call "type", they expect it to type
59
+ // at the end of the current input value. So, if the selection start
60
+ // and end are both the default of 0, then we'll go ahead and change
61
+ // them to the length of the current value.
62
+ // the only time it would make sense to pass the initialSelectionStart or
63
+ // initialSelectionEnd is if you have an input with a value and want to
64
+ // explicitely start typing with the cursor at 0. Not super common.
65
+ if (
66
+ currentElement ( ) . selectionStart === 0 &&
67
+ currentElement ( ) . selectionEnd === 0
68
+ ) {
69
+ currentElement ( ) . setSelectionRange (
70
+ initialSelectionStart ?? currentValue ( ) ?. length ?? 0 ,
71
+ initialSelectionEnd ?? currentValue ( ) ?. length ?? 0 ,
72
+ )
73
+ }
74
+
48
75
if ( allAtOnce ) {
49
76
if ( ! element . readOnly ) {
50
77
const { newValue, newSelectionStart} = calculateNewValue ( text )
51
78
fireEvent . input ( element , {
52
79
target : { value : newValue } ,
53
80
} )
54
- setSelectionRange ( newSelectionStart )
81
+ setSelectionRange ( { newValue , newSelectionStart} )
55
82
}
56
83
} else {
57
84
const eventCallbackMap = {
@@ -116,7 +143,7 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
116
143
inputType : 'insertLineBreak' ,
117
144
...eventOverrides ,
118
145
} )
119
- setSelectionRange ( newSelectionStart )
146
+ setSelectionRange ( { newValue , newSelectionStart} )
120
147
}
121
148
122
149
await tick ( )
@@ -222,7 +249,7 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
222
249
...eventOverrides ,
223
250
} )
224
251
225
- setSelectionRange ( newSelectionStart )
252
+ setSelectionRange ( { newValue , newSelectionStart} )
226
253
}
227
254
}
228
255
@@ -235,7 +262,12 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
235
262
const value = currentValue ( )
236
263
let newValue , newSelectionStart
237
264
238
- if ( selectionStart === selectionEnd ) {
265
+ if ( selectionStart === null ) {
266
+ // at the end of an input type that does not support selection ranges
267
+ // https://github.com/testing-library/user-event/issues/316#issuecomment-639744793
268
+ newValue = value . slice ( 0 , value . length - 1 )
269
+ newSelectionStart = selectionStart - 1
270
+ } else if ( selectionStart === selectionEnd ) {
239
271
if ( selectionStart === 0 ) {
240
272
// at the beginning of the input
241
273
newValue = value
@@ -267,7 +299,11 @@ async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
267
299
const value = currentValue ( )
268
300
let newValue , newSelectionStart
269
301
270
- if ( selectionStart === selectionEnd ) {
302
+ if ( selectionStart === null ) {
303
+ // at the end of an input type that does not support selection ranges
304
+ // https://github.com/testing-library/user-event/issues/316#issuecomment-639744793
305
+ newValue = value + newEntry
306
+ } else if ( selectionStart === selectionEnd ) {
271
307
if ( selectionStart === 0 ) {
272
308
// at the beginning of the input
273
309
newValue = newEntry + value
0 commit comments