diff --git a/src/__tests__/click.js b/src/__tests__/click.js index cf6fce1c..21cd2b2d 100644 --- a/src/__tests__/click.js +++ b/src/__tests__/click.js @@ -478,3 +478,9 @@ test('throws when clicking element with pointer-events set to none', () => { const {element} = setup(`
`) 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(`
`) + userEvent.click(element, undefined, {skipPointerEventsCheck: true}) + expect(getEvents('click')).toHaveLength(1) +}) diff --git a/src/__tests__/dblclick.js b/src/__tests__/dblclick.js index 02986157..c234fffc 100644 --- a/src/__tests__/dblclick.js +++ b/src/__tests__/dblclick.js @@ -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(`
`) + userEvent.dblClick(element, undefined, {skipPointerEventsCheck: true}) + expect(getEvents('click')).toHaveLength(2) +}) diff --git a/src/__tests__/hover.js b/src/__tests__/hover.js index 710803e0..54cf0f12 100644 --- a/src/__tests__/hover.js +++ b/src/__tests__/hover.js @@ -127,3 +127,20 @@ test('throws when hovering element with pointer-events set to none', () => { const {element} = setup(`
`) 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( + `
`, + ) + 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) + `) +}) diff --git a/src/__tests__/select-options.js b/src/__tests__/select-options.js index 26ee00d3..68ed21ca 100644 --- a/src/__tests__/select-options.js +++ b/src/__tests__/select-options.js @@ -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) +}) diff --git a/src/__tests__/unhover.js b/src/__tests__/unhover.js index b3858c8b..de761717 100644 --- a/src/__tests__/unhover.js +++ b/src/__tests__/unhover.js @@ -43,3 +43,20 @@ test('throws when unhover element with pointer-events set to none', () => { const {element} = setup(`
`) 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( + `
`, + ) + 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) + `) +}) diff --git a/src/click.ts b/src/click.ts index af9c488b..69fde860 100644 --- a/src/click.ts +++ b/src/click.ts @@ -6,6 +6,7 @@ import { isDisabled, isElementType, hasPointerEvents, + PointerOptions, } from './utils' import {hover} from './hover' import {blur} from './blur' @@ -28,7 +29,7 @@ export declare interface clickOptions { function clickLabel( label: HTMLLabelElement, init: MouseEventInit | undefined, - {clickCount}: clickOptions, + {clickCount}: clickOptions & PointerOptions, ) { if (isLabelWithInternallyDisabledControl(label)) return @@ -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) { @@ -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) @@ -116,14 +117,19 @@ 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) + // We just checked for `pointerEvents`. We can always skip this one in `hover`. + if (!skipHover) hover(element, init, {skipPointerEventsCheck: true}) if (isElementType(element, 'label')) { clickLabel(element, init, {clickCount}) @@ -146,15 +152,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)) } diff --git a/src/hover.ts b/src/hover.ts index 04d8406c..0a45bf08 100644 --- a/src/hover.ts +++ b/src/hover.ts @@ -4,6 +4,7 @@ import { getMouseEventOptions, isDisabled, hasPointerEvents, + PointerOptions, } from './utils' // includes `element` @@ -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".', ) @@ -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".', ) diff --git a/src/select-options.ts b/src/select-options.ts index 47e4d76b..2781e21b 100644 --- a/src/select-options.ts +++ b/src/select-options.ts @@ -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' @@ -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( @@ -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) { @@ -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) } @@ -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( diff --git a/src/utils/misc/hasPointerEvents.ts b/src/utils/misc/hasPointerEvents.ts index 32c8f83a..3403ac37 100644 --- a/src/utils/misc/hasPointerEvents.ts +++ b/src/utils/misc/hasPointerEvents.ts @@ -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)