diff --git a/src/__tests__/pointer/index.ts b/src/__tests__/pointer/index.ts
index c9a0bcdb..c8e73394 100644
--- a/src/__tests__/pointer/index.ts
+++ b/src/__tests__/pointer/index.ts
@@ -387,3 +387,28 @@ test('asynchronous pointer', async () => {
mousemove - button=0; buttons=0; detail=0
`)
})
+
+test('apply modifiers from keyboardstate', async () => {
+ const {element, getEvents} = setup(``)
+
+ element.focus()
+ let keyboardState = userEvent.keyboard('[ShiftLeft>]')
+ userEvent.pointer({keys: '[MouseLeft]', target: element}, {keyboardState})
+ keyboardState = userEvent.keyboard('[/ShiftLeft][ControlRight>]', {
+ keyboardState,
+ })
+ userEvent.pointer({keys: '[MouseLeft]', target: element}, {keyboardState})
+ keyboardState = userEvent.keyboard('[/ControlRight][AltLeft>]', {
+ keyboardState,
+ })
+ userEvent.pointer({keys: '[MouseLeft]', target: element}, {keyboardState})
+ keyboardState = userEvent.keyboard('[/AltLeft][MetaLeft>]', {keyboardState})
+ userEvent.pointer({keys: '[MouseLeft]', target: element}, {keyboardState})
+
+ expect(getEvents('click')).toEqual([
+ expect.objectContaining({shiftKey: true}),
+ expect.objectContaining({ctrlKey: true}),
+ expect.objectContaining({altKey: true}),
+ expect.objectContaining({metaKey: true}),
+ ])
+})
diff --git a/src/pointer/index.ts b/src/pointer/index.ts
index ee5cd753..75da2eeb 100644
--- a/src/pointer/index.ts
+++ b/src/pointer/index.ts
@@ -1,4 +1,5 @@
import {getConfig as getDOMTestingLibraryConfig} from '@testing-library/dom'
+import {createKeyboardState} from '../keyboard'
import {parseKeyDef} from './parseKeyDef'
import {defaultKeyMap} from './keyMap'
import {
@@ -6,33 +7,31 @@ import {
PointerAction,
PointerActionTarget,
} from './pointerAction'
-import {pointerOptions, pointerState} from './types'
+import type {inputDeviceState, pointerOptions, pointerState} from './types'
export function pointer(
input: PointerInput,
- options?: Partial,
+ options?: Partial,
): pointerState
export function pointer(
input: PointerInput,
- options: Partial<
- pointerOptions & {pointerState: pointerState; delay: number}
- >,
+ options: Partial,
): Promise
export function pointer(
input: PointerInput,
- options: Partial = {},
+ options: Partial = {},
) {
- const {promise, state} = pointerImplementationWrapper(input, options)
+ const {promise, pointerState} = pointerImplementationWrapper(input, options)
if ((options.delay ?? 0) > 0) {
return getDOMTestingLibraryConfig().asyncWrapper(() =>
- promise.then(() => state),
+ promise.then(() => pointerState),
)
} else {
// prevent users from dealing with UnhandledPromiseRejectionWarning in sync call
promise.catch(console.error)
- return state
+ return pointerState
}
}
@@ -44,10 +43,11 @@ type PointerInput = PointerActionInput | Array
export function pointerImplementationWrapper(
input: PointerInput,
- config: Partial,
+ config: Partial,
) {
const {
- pointerState: state = createPointerState(),
+ pointerState = createPointerState(),
+ keyboardState = createKeyboardState(),
delay = 0,
pointerMap = defaultKeyMap,
} = config
@@ -73,8 +73,8 @@ export function pointerImplementationWrapper(
})
return {
- promise: pointerAction(actions, options, state),
- state,
+ promise: pointerAction(actions, options, {pointerState, keyboardState}),
+ pointerState,
}
}
diff --git a/src/pointer/pointerAction.ts b/src/pointer/pointerAction.ts
index 9af8dc5a..f4df5e4d 100644
--- a/src/pointer/pointerAction.ts
+++ b/src/pointer/pointerAction.ts
@@ -1,7 +1,7 @@
import {Coords, wait} from '../utils'
import {pointerMove, PointerMoveAction} from './pointerMove'
import {pointerPress, PointerPressAction} from './pointerPress'
-import {pointerOptions, pointerState} from './types'
+import {inputDeviceState, pointerOptions, pointerState} from './types'
export type PointerActionTarget = {
target?: Element
@@ -17,7 +17,7 @@ export type PointerAction = PointerActionTarget &
export async function pointerAction(
actions: PointerAction[],
options: pointerOptions,
- state: pointerState,
+ state: inputDeviceState,
): Promise {
const ret: Array> = []
@@ -32,10 +32,11 @@ export async function pointerAction(
: action.keyDef.pointerType
: 'mouse'
- const target = action.target ?? getPrevTarget(pointerName, state)
+ const target =
+ action.target ?? getPrevTarget(pointerName, state.pointerState)
const coords = completeCoords({
- ...(pointerName in state.position
- ? state.position[pointerName].coords
+ ...(pointerName in state.pointerState.position
+ ? state.pointerState.position[pointerName].coords
: undefined),
...action.coords,
})
@@ -55,7 +56,7 @@ export async function pointerAction(
}
}
- delete state.activeClickCount
+ delete state.pointerState.activeClickCount
return Promise.all(ret)
}
diff --git a/src/pointer/pointerMove.ts b/src/pointer/pointerMove.ts
index 3ecd32f7..8a27539e 100644
--- a/src/pointer/pointerMove.ts
+++ b/src/pointer/pointerMove.ts
@@ -1,5 +1,5 @@
import {Coords, firePointerEvent, isDescendantOrSelf} from '../utils'
-import {pointerState, PointerTarget} from './types'
+import {inputDeviceState, PointerTarget} from './types'
export interface PointerMoveAction extends PointerTarget {
pointerName?: string
@@ -7,9 +7,9 @@ export interface PointerMoveAction extends PointerTarget {
export async function pointerMove(
{pointerName = 'mouse', target, coords}: PointerMoveAction,
- state: pointerState,
+ {pointerState, keyboardState}: inputDeviceState,
): Promise {
- if (!(pointerName in state.position)) {
+ if (!(pointerName in pointerState.position)) {
throw new Error(
`Trying to move pointer "${pointerName}" which does not exist.`,
)
@@ -20,7 +20,7 @@ export async function pointerMove(
pointerType,
target: prevTarget,
coords: prevCoords,
- } = state.position[pointerName]
+ } = pointerState.position[pointerName]
if (prevTarget && prevTarget !== target) {
// Here we could probably calculate a few coords to a fake boundary(?)
@@ -42,7 +42,7 @@ export async function pointerMove(
// Here we could probably calculate a few coords leading up to the final position
fireMove(target, coords)
- state.position[pointerName] = {pointerId, pointerType, target, coords}
+ pointerState.position[pointerName] = {pointerId, pointerType, target, coords}
function fireMove(eventTarget: Element, eventCoords: Coords) {
fire(eventTarget, 'pointermove', eventCoords)
@@ -71,9 +71,8 @@ export async function pointerMove(
function fire(eventTarget: Element, type: string, eventCoords: Coords) {
return firePointerEvent(eventTarget, type, {
- buttons: state.pressed
- .filter(p => p.keyDef.pointerType === pointerType)
- .map(p => p.keyDef.button ?? 0),
+ pointerState,
+ keyboardState,
coords: eventCoords,
pointerId,
pointerType,
diff --git a/src/pointer/pointerPress.ts b/src/pointer/pointerPress.ts
index d3421bec..5237522a 100644
--- a/src/pointer/pointerPress.ts
+++ b/src/pointer/pointerPress.ts
@@ -1,5 +1,10 @@
import {Coords, firePointerEvent} from '../utils'
-import type {pointerKey, pointerState, PointerTarget} from './types'
+import type {
+ inputDeviceState,
+ pointerKey,
+ pointerState,
+ PointerTarget,
+} from './types'
export interface PointerPressAction extends PointerTarget {
keyDef: pointerKey
@@ -9,9 +14,9 @@ export interface PointerPressAction extends PointerTarget {
export async function pointerPress(
{keyDef, releasePrevious, releaseSelf, target, coords}: PointerPressAction,
- state: pointerState,
+ state: inputDeviceState,
): Promise {
- const previous = state.pressed.find(p => p.keyDef === keyDef)
+ const previous = state.pointerState.pressed.find(p => p.keyDef === keyDef)
const pointerName =
keyDef.pointerType === 'touch' ? keyDef.name : keyDef.pointerType
@@ -39,12 +44,12 @@ function down(
keyDef: pointerKey,
target: Element,
coords: Coords,
- state: pointerState,
+ {pointerState, keyboardState}: inputDeviceState,
) {
const {name, pointerType, button} = keyDef
- const pointerId = pointerType === 'mouse' ? 1 : getNextPointerId(state)
+ const pointerId = pointerType === 'mouse' ? 1 : getNextPointerId(pointerState)
- state.position[pointerName] = {
+ pointerState.position[pointerName] = {
pointerId,
pointerType,
target,
@@ -54,7 +59,7 @@ function down(
let isMultiTouch = false
let isPrimary = true
if (pointerType !== 'mouse') {
- for (const obj of state.pressed) {
+ for (const obj of pointerState.pressed) {
// TODO: test multi device input across browsers
// istanbul ignore else
if (obj.keyDef.pointerType === pointerType) {
@@ -65,11 +70,11 @@ function down(
}
}
- if (state.activeClickCount?.[0] !== name) {
- delete state.activeClickCount
+ if (pointerState.activeClickCount?.[0] !== name) {
+ delete pointerState.activeClickCount
}
- const clickCount = Number(state.activeClickCount?.[1] ?? 0) + 1
- state.activeClickCount = [name, clickCount]
+ const clickCount = Number(pointerState.activeClickCount?.[1] ?? 0) + 1
+ pointerState.activeClickCount = [name, clickCount]
const pressObj = {
keyDef,
@@ -80,7 +85,7 @@ function down(
isPrimary,
clickCount,
}
- state.pressed.push(pressObj)
+ pointerState.pressed.push(pressObj)
if (pointerType !== 'mouse') {
fire('pointerover')
@@ -88,7 +93,7 @@ function down(
}
if (
pointerType !== 'mouse' ||
- !state.pressed.some(
+ !pointerState.pressed.some(
p => p.keyDef !== keyDef && p.keyDef.pointerType === pointerType,
)
) {
@@ -104,10 +109,9 @@ function down(
function fire(type: string) {
return firePointerEvent(target, type, {
+ pointerState,
+ keyboardState,
button,
- buttons: state.pressed
- .filter(p => p.keyDef.pointerType === pointerType)
- .map(p => p.keyDef.button ?? 0),
clickCount,
coords,
isPrimary,
@@ -122,15 +126,15 @@ function up(
{pointerType, button}: pointerKey,
target: Element,
coords: Coords,
- state: pointerState,
+ {pointerState, keyboardState}: inputDeviceState,
pressed: pointerState['pressed'][number],
) {
- state.pressed = state.pressed.filter(p => p !== pressed)
+ pointerState.pressed = pointerState.pressed.filter(p => p !== pressed)
const {isMultiTouch, isPrimary, pointerId, clickCount} = pressed
let {unpreventedDefault} = pressed
- state.position[pointerName] = {
+ pointerState.position[pointerName] = {
pointerId,
pointerType,
target,
@@ -141,7 +145,8 @@ function up(
if (
pointerType !== 'mouse' ||
- !state.pressed.filter(p => p.keyDef.pointerType === pointerType).length
+ !pointerState.pressed.filter(p => p.keyDef.pointerType === pointerType)
+ .length
) {
fire('pointerup')
}
@@ -169,10 +174,9 @@ function up(
function fire(type: string) {
return firePointerEvent(target, type, {
+ pointerState,
+ keyboardState,
button,
- buttons: state.pressed
- .filter(p => p.keyDef.pointerType === pointerType)
- .map(p => p.keyDef.button ?? 0),
clickCount,
coords,
isPrimary,
diff --git a/src/pointer/types.ts b/src/pointer/types.ts
index ddf52725..70e336b7 100644
--- a/src/pointer/types.ts
+++ b/src/pointer/types.ts
@@ -1,3 +1,4 @@
+import {keyboardState} from 'keyboard/types'
import {Coords, MouseButton} from '../utils'
/**
@@ -61,3 +62,8 @@ export interface PointerTarget {
target: Element
coords: Coords
}
+
+export interface inputDeviceState {
+ pointerState: pointerState
+ keyboardState: keyboardState
+}
diff --git a/src/setup.ts b/src/setup.ts
index 29d712b0..2d43c4ad 100644
--- a/src/setup.ts
+++ b/src/setup.ts
@@ -160,7 +160,7 @@ function _setup(
// pointer needs typecasting because of the overloading
pointer: ((...args: Parameters) => {
- args[1] = {...pointerApiDefaults, ...args[1], pointerState}
+ args[1] = {...pointerApiDefaults, ...args[1], pointerState, keyboardState}
const ret = pointer(...args) as pointerState | Promise
if (ret instanceof Promise) {
return ret.then(() => undefined)
diff --git a/src/utils/pointer/firePointerEvents.ts b/src/utils/pointer/firePointerEvents.ts
index 6c2f5c8a..69d367ba 100644
--- a/src/utils/pointer/firePointerEvents.ts
+++ b/src/utils/pointer/firePointerEvents.ts
@@ -1,4 +1,6 @@
import {fireEvent} from '@testing-library/dom'
+import type {pointerState} from 'pointer/types'
+import type {keyboardState} from 'keyboard/types'
import {FakeEventInit, FakeMouseEvent, FakePointerEvent} from './fakeEvent'
import {getMouseButton, getMouseButtons, MouseButton} from './mouseButtons'
@@ -17,17 +19,19 @@ export function firePointerEvent(
target: Element,
type: string,
{
+ pointerState,
+ keyboardState,
pointerType,
button,
- buttons,
coords,
pointerId,
isPrimary,
clickCount,
}: {
+ pointerState: pointerState
+ keyboardState: keyboardState
pointerType?: 'mouse' | 'pen' | 'touch'
button?: MouseButton
- buttons: MouseButton[]
coords: Coords
pointerId?: number
isPrimary?: boolean
@@ -41,6 +45,10 @@ export function firePointerEvent(
let init: FakeEventInit = {
...coords,
+ altKey: keyboardState.modifiers.alt,
+ ctrlKey: keyboardState.modifiers.ctrl,
+ metaKey: keyboardState.modifiers.meta,
+ shiftKey: keyboardState.modifiers.shift,
}
if (Event === FakePointerEvent) {
init = {...init, pointerId, pointerType}
@@ -52,7 +60,11 @@ export function firePointerEvent(
['mousedown', 'mouseup', 'pointerdown', 'pointerup', 'click'].includes(type)
) {
init.button = getMouseButton(button ?? 0)
- init.buttons = getMouseButtons(...buttons)
+ init.buttons = getMouseButtons(
+ ...pointerState.pressed
+ .filter(p => p.keyDef.pointerType === pointerType)
+ .map(p => p.keyDef.button ?? 0),
+ )
}
if (['mousedown', 'mouseup', 'click'].includes(type)) {
init.detail = clickCount