diff --git a/src/__tests__/type.js b/src/__tests__/type.js index ab5b1b7b..bc8e0191 100644 --- a/src/__tests__/type.js +++ b/src/__tests__/type.js @@ -1022,3 +1022,47 @@ test('should not type inside a contenteditable=false div', () => { div - click: Left (0) `) }) + +test('navigation key: {arrowleft} and {arrowright} moves the cursor', () => { + const {element, getEventSnapshot} = setup('') + userEvent.type(element, 'b{arrowleft}a{arrowright}c') + expect(getEventSnapshot()).toMatchInlineSnapshot(` + Events fired on: input[value="abc"] + + 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: b (98) + input[value=""] - keypress: b (98) + input[value="b"] - input + "{CURSOR}" -> "b{CURSOR}" + input[value="b"] - keyup: b (98) + input[value="b"] - keydown: ArrowLeft (37) + input[value="b"] - select + input[value="b"] - keyup: ArrowLeft (37) + input[value="b"] - keydown: a (97) + input[value="b"] - keypress: a (97) + input[value="ab"] - input + "{CURSOR}b" -> "ab{CURSOR}" + input[value="ab"] - select + input[value="ab"] - keyup: a (97) + input[value="ab"] - keydown: ArrowRight (39) + input[value="ab"] - select + input[value="ab"] - keyup: ArrowRight (39) + input[value="ab"] - keydown: c (99) + input[value="ab"] - keypress: c (99) + input[value="abc"] - input + "ab{CURSOR}" -> "abc{CURSOR}" + input[value="abc"] - keyup: c (99) + `) +}) diff --git a/src/keys/navigation-key.js b/src/keys/navigation-key.js new file mode 100644 index 00000000..d232f90b --- /dev/null +++ b/src/keys/navigation-key.js @@ -0,0 +1,50 @@ +import {fireEvent} from '@testing-library/dom' + +import {setSelectionRangeIfNecessary} from '../utils' + +const keys = { + ArrowLeft: { + keyCode: 37, + }, + ArrowRight: { + keyCode: 39, + }, +} + +function getSelectionRange(currentElement, key) { + const {selectionStart, selectionEnd} = currentElement() + const cursorChange = Number(key in keys) * (key === 'ArrowLeft' ? -1 : 1) + return { + selectionStart: selectionStart + cursorChange, + selectionEnd: selectionEnd + cursorChange, + } +} + +function navigationKey(key) { + const event = { + key, + keyCode: keys[key].keyCode, + which: keys[key].keyCode, + } + + return ({currentElement, eventOverrides}) => { + fireEvent.keyDown(currentElement(), { + ...event, + ...eventOverrides, + }) + + const range = getSelectionRange(currentElement, key) + setSelectionRangeIfNecessary( + currentElement(), + range.selectionStart, + range.selectionEnd, + ) + + fireEvent.keyUp(currentElement(), { + ...event, + ...eventOverrides, + }) + } +} + +export {navigationKey} diff --git a/src/type.js b/src/type.js index 66cd8ae7..39c2c159 100644 --- a/src/type.js +++ b/src/type.js @@ -15,6 +15,7 @@ import { isContentEditable, } from './utils' import {click} from './click' +import {navigationKey} from './keys/navigation-key' const modifierCallbackMap = { ...createModifierCallbackEntries({ @@ -44,6 +45,8 @@ const modifierCallbackMap = { } const specialCharCallbackMap = { + '{arrowleft}': navigationKey('ArrowLeft'), + '{arrowright}': navigationKey('ArrowRight'), '{enter}': handleEnter, '{esc}': handleEsc, '{del}': handleDel,