From 5ea48f553f5863ea13851141875123ce302a2c99 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Mon, 18 Jul 2022 08:51:25 +0000 Subject: [PATCH] fix(pointer): do not throw for `pointer-events: none` on previous target --- src/convenience/hover.ts | 7 +++- src/pointer/pointerMove.ts | 13 +++---- src/utility/selectOptions.ts | 6 ++-- src/utils/pointer/cssPointerEvents.ts | 18 ++++++---- tests/pointer/index.ts | 48 ++++++++++++++++++++----- tests/utils/pointer/cssPointerEvents.ts | 8 ++--- 6 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/convenience/hover.ts b/src/convenience/hover.ts index a92635fa..df647b2a 100644 --- a/src/convenience/hover.ts +++ b/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}) } diff --git a/src/pointer/pointerMove.ts b/src/pointer/pointerMove.ts index a60a91d2..492d48b6 100644 --- a/src/pointer/pointerMove.ts +++ b/src/pointer/pointerMove.ts @@ -7,6 +7,7 @@ import { assertPointerEvents, setLevelRef, ApiLevel, + hasPointerEvents, } from '../utils' import {firePointerEvent} from './firePointerEvents' import {resolveSelectionTarget} from './resolveSelectionTarget' @@ -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) + } } } diff --git a/src/utility/selectOptions.ts b/src/utility/selectOptions.ts index 520c62d2..1a16951e 100644 --- a/src/utility/selectOptions.ts +++ b/src/utility/selectOptions.ts @@ -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) { @@ -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) diff --git a/src/utils/pointer/cssPointerEvents.ts b/src/utils/pointer/cssPointerEvents.ts index 88a31c80..3ef25258 100644 --- a/src/utils/pointer/cssPointerEvents.ts +++ b/src/utils/pointer/cssPointerEvents.ts @@ -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): @@ -37,12 +37,12 @@ declare global { [PointerEventsCheck]?: { [k in ApiLevel]?: object } & { - result: boolean + result: ReturnType } } } -export function assertPointerEvents(config: Config, element: Element) { +function checkPointerEvents(config: Config, element: Element) { const lastCheck = element[PointerEventsCheck] const needsCheck = @@ -60,7 +60,7 @@ export function assertPointerEvents(config: Config, element: Element) { lastCheck[ApiLevel.Trigger] !== getLevelRef(config, ApiLevel.Trigger))) if (!needsCheck) { - return + return lastCheck?.result } const declaration = closestPointerEventsDeclaration(element) @@ -68,9 +68,15 @@ export function assertPointerEvents(config: Config, element: 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( [ diff --git a/tests/pointer/index.ts b/tests/pointer/index.ts index 670ef111..096472b1 100644 --- a/tests/pointer/index.ts +++ b/tests/pointer/index.ts @@ -122,14 +122,20 @@ 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, + Parameters + > + beforeAll(() => { + getComputedStyle = jest + .spyOn(window, 'getComputedStyle') + .mockImplementation( + () => + ({ + pointerEvents: 'foo', + } as CSSStyleDeclaration), + ) + }) beforeEach(() => { getComputedStyle.mockClear() document.body.parentElement?.replaceChild( @@ -137,6 +143,9 @@ describe('check for pointer-events', () => { document.body, ) }) + afterAll(() => { + jest.restoreAllMocks() + }) test('skip check', async () => { const {element, user} = setup(``, { @@ -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(``) + + 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(``) + 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() +}) diff --git a/tests/utils/pointer/cssPointerEvents.ts b/tests/utils/pointer/cssPointerEvents.ts index 98e202f8..5c49b5db 100644 --- a/tests/utils/pointer/cssPointerEvents.ts +++ b/tests/utils/pointer/cssPointerEvents.ts @@ -11,10 +11,10 @@ test('get pointer-events from element or ancestor', async () => { `) - 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 () => {