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

feat: add flag to skip pointer-events check #731

Merged
merged 4 commits into from
Oct 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/__tests__/click.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,9 @@ test('throws when clicking element with pointer-events set to none', () => {
const {element} = setup(`<div style="pointer-events: none"></div>`)
expect(() => userEvent.click(element)).toThrowError(/unable to click/i)
})

test('does not throws when clicking element with pointer-events set to none and skipPointerEventsCheck is set', () => {
const {element, getEvents} = setup(`<div style="pointer-events: none"></div>`)
userEvent.click(element, undefined, {skipPointerEventsCheck: true})
expect(getEvents('click')).toHaveLength(1)
})
6 changes: 6 additions & 0 deletions src/__tests__/dblclick.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,9 @@ test('throws an error when dblClick element with pointer-events set to none', ()
/unable to double-click/i,
)
})

test('does not throws when clicking element with pointer-events set to none and skipPointerEventsCheck is set', () => {
const {element, getEvents} = setup(`<div style="pointer-events: none"></div>`)
userEvent.dblClick(element, undefined, {skipPointerEventsCheck: true})
expect(getEvents('click')).toHaveLength(2)
})
17 changes: 17 additions & 0 deletions src/__tests__/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,20 @@ test('throws when hovering element with pointer-events set to none', () => {
const {element} = setup(`<div style="pointer-events: none"></div>`)
expect(() => userEvent.hover(element)).toThrowError(/unable to hover/i)
})

test('does not throws when hover element with pointer-events set to none and skipPointerEventsCheck is set', () => {
const {element, getEventSnapshot} = setup(
`<div style="pointer-events: none"></div>`,
)
userEvent.hover(element, undefined, {skipPointerEventsCheck: true})
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: div

div - pointerover
div - pointerenter
div - mouseover: Left (0)
div - mouseenter: Left (0)
div - pointermove
div - mousemove: Left (0)
`)
})
85 changes: 85 additions & 0 deletions src/__tests__/select-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,88 @@ test('fire no pointer events when multiple select has disabled pointer events',
expect(o2.selected).toBe(true)
expect(o3.selected).toBe(true)
})

test('fires correct events when pointer events set to none but skipPointerEvents is set', () => {
const {select, options, getEventSnapshot} = setupSelect({
pointerEvents: 'none',
})
userEvent.selectOptions(select, '2', undefined, {
skipPointerEventsCheck: true,
})
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: select[name="select"][value="2"]

select[name="select"][value="1"] - pointerover
select[name="select"][value="1"] - pointerenter
select[name="select"][value="1"] - mouseover: Left (0)
select[name="select"][value="1"] - mouseenter: Left (0)
select[name="select"][value="1"] - pointermove
select[name="select"][value="1"] - mousemove: Left (0)
select[name="select"][value="1"] - pointerdown
select[name="select"][value="1"] - mousedown: Left (0)
select[name="select"][value="1"] - focus
select[name="select"][value="1"] - focusin
select[name="select"][value="1"] - pointerup
select[name="select"][value="1"] - mouseup: Left (0)
select[name="select"][value="1"] - click: Left (0)
select[name="select"][value="2"] - input
select[name="select"][value="2"] - change
select[name="select"][value="2"] - pointerover
select[name="select"][value="2"] - pointerenter
select[name="select"][value="2"] - mouseover: Left (0)
select[name="select"][value="2"] - mouseenter: Left (0)
select[name="select"][value="2"] - pointerup
select[name="select"][value="2"] - mouseup: Left (0)
select[name="select"][value="2"] - click: Left (0)
`)
const [o1, o2, o3] = options
expect(o1.selected).toBe(false)
expect(o2.selected).toBe(true)
expect(o3.selected).toBe(false)
})

test('fires correct events on multi-selects when pointer events is set and skipPointerEventsCheck is set', () => {
const {select, options, getEventSnapshot} = setupSelect({
multiple: true,
pointerEvents: 'none',
})
userEvent.selectOptions(select, ['1', '3'], undefined, {
skipPointerEventsCheck: true,
})
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: select[name="select"][value=["1","3"]]

option[value="1"][selected=false] - pointerover
select[name="select"][value=[]] - pointerenter
option[value="1"][selected=false] - mouseover: Left (0)
select[name="select"][value=[]] - mouseenter: Left (0)
option[value="1"][selected=false] - pointermove
option[value="1"][selected=false] - mousemove: Left (0)
option[value="1"][selected=false] - pointerdown
option[value="1"][selected=false] - mousedown: Left (0)
select[name="select"][value=[]] - focus
select[name="select"][value=[]] - focusin
option[value="1"][selected=false] - pointerup
option[value="1"][selected=false] - mouseup: Left (0)
select[name="select"][value=["1"]] - input
select[name="select"][value=["1"]] - change
option[value="1"][selected=true] - click: Left (0)
option[value="3"][selected=false] - pointerover
select[name="select"][value=["1"]] - pointerenter
option[value="3"][selected=false] - mouseover: Left (0)
select[name="select"][value=["1"]] - mouseenter: Left (0)
option[value="3"][selected=false] - pointermove
option[value="3"][selected=false] - mousemove: Left (0)
option[value="3"][selected=false] - pointerdown
option[value="3"][selected=false] - mousedown: Left (0)
option[value="3"][selected=false] - pointerup
option[value="3"][selected=false] - mouseup: Left (0)
select[name="select"][value=["1","3"]] - input
select[name="select"][value=["1","3"]] - change
option[value="3"][selected=true] - click: Left (0)
`)
const [o1, o2, o3] = options
expect(o1.selected).toBe(true)
expect(o2.selected).toBe(false)
expect(o3.selected).toBe(true)
})
17 changes: 17 additions & 0 deletions src/__tests__/unhover.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,20 @@ test('throws when unhover element with pointer-events set to none', () => {
const {element} = setup(`<div style="pointer-events: none"></div>`)
expect(() => userEvent.unhover(element)).toThrowError(/unable to unhover/i)
})

test('does not throws when hover element with pointer-events set to none and skipPointerEventsCheck is set', () => {
const {element, getEventSnapshot} = setup(
`<div style="pointer-events: none"></div>`,
)
userEvent.unhover(element, undefined, {skipPointerEventsCheck: true})
expect(getEventSnapshot()).toMatchInlineSnapshot(`
Events fired on: div

div - pointermove
div - mousemove: Left (0)
div - pointerout
div - pointerleave
div - mouseout: Left (0)
div - mouseleave: Left (0)
`)
})
31 changes: 20 additions & 11 deletions src/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
isDisabled,
isElementType,
hasPointerEvents,
PointerOptions,
} from './utils'
import {hover} from './hover'
import {blur} from './blur'
Expand All @@ -28,7 +29,7 @@ export declare interface clickOptions {
function clickLabel(
label: HTMLLabelElement,
init: MouseEventInit | undefined,
{clickCount}: clickOptions,
{clickCount}: clickOptions & PointerOptions,
) {
if (isLabelWithInternallyDisabledControl(label)) return

Expand All @@ -49,7 +50,7 @@ function clickLabel(
function clickBooleanElement(
element: HTMLInputElement,
init: MouseEventInit | undefined,
{clickCount}: clickOptions,
{clickCount}: clickOptions & PointerOptions,
) {
fireEvent.pointerDown(element, init)
if (!element.disabled) {
Expand All @@ -72,7 +73,7 @@ function clickBooleanElement(
function clickElement(
element: Element,
init: MouseEventInit | undefined,
{clickCount}: clickOptions,
{clickCount}: clickOptions & PointerOptions,
) {
const previousElement = getPreviouslyFocusedElement(element)
fireEvent.pointerDown(element, init)
Expand Down Expand Up @@ -116,14 +117,18 @@ function findClosest(element: Element, callback: (e: Element) => boolean) {
function click(
element: Element,
init?: MouseEventInit,
{skipHover = false, clickCount = 0}: clickOptions = {},
{
skipHover = false,
clickCount = 0,
skipPointerEventsCheck = false,
}: clickOptions & PointerOptions = {},
) {
if (!hasPointerEvents(element)) {
if (!skipPointerEventsCheck && !hasPointerEvents(element)) {
throw new Error(
'unable to click element as it has or inherits pointer-events set to "none".',
)
}
if (!skipHover) hover(element, init)
if (!skipHover) hover(element, init, {skipPointerEventsCheck})

if (isElementType(element, 'label')) {
clickLabel(element, init, {clickCount})
Expand All @@ -146,15 +151,19 @@ function fireClick(element: Element, mouseEventOptions: MouseEventInit) {
}
}

function dblClick(element: Element, init?: MouseEventInit) {
if (!hasPointerEvents(element)) {
function dblClick(
element: Element,
init?: MouseEventInit,
{skipPointerEventsCheck = false}: clickOptions & PointerOptions = {},
) {
if (!skipPointerEventsCheck && !hasPointerEvents(element)) {
throw new Error(
'unable to double-click element as it has or inherits pointer-events set to "none".',
)
}
hover(element, init)
click(element, init, {skipHover: true, clickCount: 0})
click(element, init, {skipHover: true, clickCount: 1})
hover(element, init, {skipPointerEventsCheck})
click(element, init, {skipHover: true, clickCount: 0, skipPointerEventsCheck})
click(element, init, {skipHover: true, clickCount: 1, skipPointerEventsCheck})
fireEvent.dblClick(element, getMouseEventOptions('dblclick', init, 2))
}

Expand Down
17 changes: 13 additions & 4 deletions src/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getMouseEventOptions,
isDisabled,
hasPointerEvents,
PointerOptions,
} from './utils'

// includes `element`
Expand All @@ -16,8 +17,12 @@ function getParentElements(element: Element) {
return parentElements
}

function hover(element: Element, init?: MouseEventInit) {
if (!hasPointerEvents(element)) {
function hover(
element: Element,
init?: MouseEventInit,
{skipPointerEventsCheck = false}: PointerOptions = {},
) {
if (!skipPointerEventsCheck && !hasPointerEvents(element)) {
throw new Error(
'unable to hover element as it has or inherits pointer-events set to "none".',
)
Expand All @@ -42,8 +47,12 @@ function hover(element: Element, init?: MouseEventInit) {
}
}

function unhover(element: Element, init?: MouseEventInit) {
if (!hasPointerEvents(element)) {
function unhover(
element: Element,
init?: MouseEventInit,
{skipPointerEventsCheck = false}: PointerOptions = {},
) {
if (!skipPointerEventsCheck && !hasPointerEvents(element)) {
throw new Error(
'unable to unhover element as it has or inherits pointer-events set to "none".',
)
Expand Down
24 changes: 17 additions & 7 deletions src/select-options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {createEvent, getConfig, fireEvent} from '@testing-library/dom'
import {hasPointerEvents, isDisabled, isElementType} from './utils'
import {
hasPointerEvents,
isDisabled,
isElementType,
PointerOptions,
} from './utils'
import {click} from './click'
import {focus} from './focus'
import {hover, unhover} from './hover'
Expand All @@ -9,6 +14,7 @@ function selectOptionsBase(
select: Element,
values: HTMLElement | HTMLElement[] | string[] | string,
init?: MouseEventInit,
{skipPointerEventsCheck = false}: PointerOptions = {},
) {
if (!newValue && !(select as HTMLSelectElement).multiple) {
throw getConfig().getElementError(
Expand Down Expand Up @@ -47,7 +53,9 @@ function selectOptionsBase(
if (isElementType(select, 'select')) {
if (select.multiple) {
for (const option of selectedOptions) {
const withPointerEvents = hasPointerEvents(option)
const withPointerEvents = skipPointerEventsCheck
? true
: hasPointerEvents(option)

// events fired for multiple select are weird. Can't use hover...
if (withPointerEvents) {
Expand Down Expand Up @@ -75,10 +83,12 @@ function selectOptionsBase(
}
}
} else if (selectedOptions.length === 1) {
const withPointerEvents = hasPointerEvents(select)
const withPointerEvents = skipPointerEventsCheck
? true
: hasPointerEvents(select)
// the click to open the select options
if (withPointerEvents) {
click(select, init)
click(select, init, {skipPointerEventsCheck})
} else {
focus(select)
}
Expand All @@ -104,9 +114,9 @@ function selectOptionsBase(
}
} else if (select.getAttribute('role') === 'listbox') {
selectedOptions.forEach(option => {
hover(option, init)
click(option, init)
unhover(option, init)
hover(option, init, {skipPointerEventsCheck})
click(option, init, {skipPointerEventsCheck})
unhover(option, init, {skipPointerEventsCheck})
})
} else {
throw getConfig().getElementError(
Expand Down
15 changes: 15 additions & 0 deletions src/utils/misc/hasPointerEvents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import {getWindowFromNode} from '@testing-library/dom/dist/helpers'

/**
* Options that can be passed to any event that relies
* on pointer-events property
*/
export declare interface PointerOptions {
/**
* When set to `true` the event skips checking if any element
* in the DOM-tree has `'pointer-events: none'` set. This check is
* costly in general and very costly when rendering large DOM-trees.
* Can be used to speed up tests.
* Default: `false`
* */
skipPointerEventsCheck?: boolean
}

export function hasPointerEvents(element: Element): boolean {
const window = getWindowFromNode(element)

Expand Down