Skip to content

Commit

Permalink
fix(pointer): do not throw for pointer-events: none on previous target
Browse files Browse the repository at this point in the history
  • Loading branch information
ph-fritsche committed Jul 18, 2022
1 parent 77a7fa8 commit 5ea48f5
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 27 deletions.
7 changes: 6 additions & 1 deletion src/convenience/hover.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {Instance} from '../setup'
import {Config, Instance} from '../setup'
import {assertPointerEvents} from '../utils'

export async function hover(this: Instance, element: Element) {
return this.pointer({target: element})
}

export async function unhover(this: Instance, element: Element) {
assertPointerEvents(
this[Config],
this[Config].pointerState.position.mouse.target as Element,
)
return this.pointer({target: element.ownerDocument.body})
}
13 changes: 7 additions & 6 deletions src/pointer/pointerMove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
assertPointerEvents,
setLevelRef,
ApiLevel,
hasPointerEvents,
} from '../utils'
import {firePointerEvent} from './firePointerEvents'
import {resolveSelectionTarget} from './resolveSelectionTarget'
Expand Down Expand Up @@ -37,13 +38,13 @@ export async function pointerMove(

if (prevTarget && prevTarget !== target) {
setLevelRef(config, ApiLevel.Trigger)
assertPointerEvents(config, prevTarget)
if (hasPointerEvents(config, prevTarget)) {
// Here we could probably calculate a few coords to a fake boundary(?)
fireMove(prevTarget, prevCoords)

// Here we could probably calculate a few coords to a fake boundary(?)
fireMove(prevTarget, prevCoords)

if (!isDescendantOrSelf(target, prevTarget)) {
fireLeave(prevTarget, prevCoords)
if (!isDescendantOrSelf(target, prevTarget)) {
fireLeave(prevTarget, prevCoords)
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/utility/selectOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async function selectOptionsBase(
const withPointerEvents =
this[Config].pointerEventsCheck === 0
? true
: hasPointerEvents(option)
: hasPointerEvents(this[Config], option)

// events fired for multiple select are weird. Can't use hover...
if (withPointerEvents) {
Expand Down Expand Up @@ -111,7 +111,9 @@ async function selectOptionsBase(
}
} else if (selectedOptions.length === 1) {
const withPointerEvents =
this[Config].pointerEventsCheck === 0 ? true : hasPointerEvents(select)
this[Config].pointerEventsCheck === 0
? true
: hasPointerEvents(this[Config], select)
// the click to open the select options
if (withPointerEvents) {
await this.click(select)
Expand Down
18 changes: 12 additions & 6 deletions src/utils/pointer/cssPointerEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {ApiLevel, getLevelRef} from '..'
import {getWindow} from '../misc/getWindow'
import {isElementType} from '../misc/isElementType'

export function hasPointerEvents(element: Element): boolean {
return closestPointerEventsDeclaration(element)?.pointerEvents !== 'none'
export function hasPointerEvents(config: Config, element: Element): boolean {
return checkPointerEvents(config, element)?.pointerEvents !== 'none'
}

function closestPointerEventsDeclaration(element: Element):
Expand Down Expand Up @@ -37,12 +37,12 @@ declare global {
[PointerEventsCheck]?: {
[k in ApiLevel]?: object
} & {
result: boolean
result: ReturnType<typeof closestPointerEventsDeclaration>
}
}
}

export function assertPointerEvents(config: Config, element: Element) {
function checkPointerEvents(config: Config, element: Element) {
const lastCheck = element[PointerEventsCheck]

const needsCheck =
Expand All @@ -60,17 +60,23 @@ export function assertPointerEvents(config: Config, element: Element) {
lastCheck[ApiLevel.Trigger] !== getLevelRef(config, ApiLevel.Trigger)))

if (!needsCheck) {
return
return lastCheck?.result
}

const declaration = closestPointerEventsDeclaration(element)

element[PointerEventsCheck] = {
[ApiLevel.Call]: getLevelRef(config, ApiLevel.Call),
[ApiLevel.Trigger]: getLevelRef(config, ApiLevel.Trigger),
result: declaration?.pointerEvents !== 'none',
result: declaration,
}

return declaration
}

export function assertPointerEvents(config: Config, element: Element) {
const declaration = checkPointerEvents(config, element)

if (declaration?.pointerEvents === 'none') {
throw new Error(
[
Expand Down
48 changes: 40 additions & 8 deletions tests/pointer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,30 @@ test('only pointer events on disabled elements', async () => {
})

describe('check for pointer-events', () => {
const getComputedStyle = jest
.spyOn(window, 'getComputedStyle')
.mockImplementation(
() =>
({
pointerEvents: 'foo',
} as CSSStyleDeclaration),
)
let getComputedStyle: jest.SpyInstance<
ReturnType<Window['getComputedStyle']>,
Parameters<Window['getComputedStyle']>
>
beforeAll(() => {
getComputedStyle = jest
.spyOn(window, 'getComputedStyle')
.mockImplementation(
() =>
({
pointerEvents: 'foo',
} as CSSStyleDeclaration),
)
})
beforeEach(() => {
getComputedStyle.mockClear()
document.body.parentElement?.replaceChild(
document.createElement('body'),
document.body,
)
})
afterAll(() => {
jest.restoreAllMocks()
})

test('skip check', async () => {
const {element, user} = setup(`<input>`, {
Expand Down Expand Up @@ -216,3 +225,26 @@ describe('check for pointer-events', () => {
expect(getComputedStyle).toHaveBeenNthCalledWith(6, document.body) // enter
})
})

test('reject if target has `pointer-events: none`', async () => {
const {element, user} = setup(`<input style="pointer-events: none"/>`)

await expect(user.pointer({target: element})).rejects.toThrowError(
'pointer-events',
)
await expect(
user.pointer({target: element, keys: '[MouseLeft]'}),
).rejects.toThrowError('pointer-events')
})

test('omit pointer events on previous target if it has `pointer-events: none`', async () => {
const {element, user} = setup(`<input/>`)
const onPointerLeave = jest.fn()
element.addEventListener('pointerleave', onPointerLeave)

await user.pointer({target: element})
element.style.pointerEvents = 'none'
await user.pointer({target: document.body})

expect(onPointerLeave).not.toBeCalled()
})
8 changes: 4 additions & 4 deletions tests/utils/pointer/cssPointerEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ test('get pointer-events from element or ancestor', async () => {
</div>
`)

expect(hasPointerEvents(element)).toBe(false)
expect(hasPointerEvents(element.children[0])).toBe(true)
expect(hasPointerEvents(element.children[1])).toBe(false)
expect(hasPointerEvents(element.children[2])).toBe(false)
expect(hasPointerEvents(createConfig(), element)).toBe(false)
expect(hasPointerEvents(createConfig(), element.children[0])).toBe(true)
expect(hasPointerEvents(createConfig(), element.children[1])).toBe(false)
expect(hasPointerEvents(createConfig(), element.children[2])).toBe(false)
})

test('report element that declared pointer-events', async () => {
Expand Down

0 comments on commit 5ea48f5

Please sign in to comment.