Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(pointer): do not throw for pointer-events: none on previous target #991

Merged
merged 2 commits into from Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/convenience/hover.ts
@@ -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
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
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
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
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
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