From 200a4349a2f85686bc7005dce686d9d1b48b84d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Fri, 28 Jun 2024 10:38:07 +0200 Subject: [PATCH] feat(browser): add `tripleClick` to interactive api (#5987) --- docs/guide/browser.md | 25 ++++++++++ packages/browser/context.d.ts | 8 ++++ packages/browser/src/client/tester/context.ts | 4 ++ packages/browser/src/node/commands/click.ts | 39 ++++++++++++++++ packages/browser/src/node/commands/index.ts | 3 +- test/browser/test/userEvent.test.ts | 46 +++++++++++++++++++ 6 files changed, 124 insertions(+), 1 deletion(-) diff --git a/docs/guide/browser.md b/docs/guide/browser.md index da74fddec2c9..7cf1838181c9 100644 --- a/docs/guide/browser.md +++ b/docs/guide/browser.md @@ -551,6 +551,31 @@ References: - [WebdriverIO `element.doubleClick` API](https://webdriver.io/docs/api/element/doubleClick/) - [testing-library `dblClick` API](https://testing-library.com/docs/user-event/convenience/#dblClick) +### userEvent.tripleClick + +- **Type:** `(element: Element, options?: UserEventTripleClickOptions) => Promise` + +Triggers a triple click event on an element + +Please refer to your provider's documentation for detailed explanation about how this method works. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('triggers a triple click on an element', async () => { + const logo = screen.getByRole('img', { name: /logo/ }) + + await userEvent.tripleClick(logo) +}) +``` + +References: + +- [Playwright `locator.click` API](https://playwright.dev/docs/api/class-locator#locator-click) +- [WebdriverIO `browser.action` API](https://webdriver.io/docs/api/browser/action/) +- [testing-library `tripleClick` API](https://testing-library.com/docs/user-event/convenience/#tripleClick) + ### userEvent.fill - **Type:** `(element: Element, text: string) => Promise` diff --git a/packages/browser/context.d.ts b/packages/browser/context.d.ts index 2b9bd673a2f4..66b6d0cfdcec 100644 --- a/packages/browser/context.d.ts +++ b/packages/browser/context.d.ts @@ -60,6 +60,13 @@ export interface UserEvent { * @see {@link https://testing-library.com/docs/user-event/convenience/#dblClick} testing-library API */ dblClick: (element: Element, options?: UserEventDoubleClickOptions) => Promise + /** + * Triggers a triple click event on an element. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-click} Playwright API: using `click` with `clickCount: 3` + * @see {@link https://webdriver.io/docs/api/browser/actions/} WebdriverIO API: using actions with `move` and 3 `down + up + down` events in a row + * @see {@link https://testing-library.com/docs/user-event/convenience/#tripleclick} testing-library API + */ + tripleClick: (element: Element, options?: UserEventTripleClickOptions) => Promise /** * Choose one or more values from a select element. Uses provider's API under the hood. * If select doesn't have `multiple` attribute, only the first value will be selected. @@ -165,6 +172,7 @@ export interface UserEventHoverOptions {} export interface UserEventSelectOptions {} export interface UserEventClickOptions {} export interface UserEventDoubleClickOptions {} +export interface UserEventTripleClickOptions {} export interface UserEventDragAndDropOptions {} export interface UserEventTabOptions { diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts index c7d1675f43be..2a8880d63972 100644 --- a/packages/browser/src/client/tester/context.ts +++ b/packages/browser/src/client/tester/context.ts @@ -73,6 +73,10 @@ export const userEvent: UserEvent = { const xpath = convertElementToXPath(element) return triggerCommand('__vitest_dblClick', xpath, options) }, + tripleClick(element: Element, options: UserEventClickOptions = {}) { + const xpath = convertElementToXPath(element) + return triggerCommand('__vitest_tripleClick', xpath, options) + }, selectOptions(element, value) { const values = provider === 'webdriverio' ? getWebdriverioSelectOptions(element, value) diff --git a/packages/browser/src/node/commands/click.ts b/packages/browser/src/node/commands/click.ts index 7a9b7ce39625..d01c8f2768bc 100644 --- a/packages/browser/src/node/commands/click.ts +++ b/packages/browser/src/node/commands/click.ts @@ -45,3 +45,42 @@ export const dblClick: UserEventCommand = async ( throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`) } } + +export const tripleClick: UserEventCommand = async ( + context, + xpath, + options = {}, +) => { + const provider = context.provider + if (provider instanceof PlaywrightBrowserProvider) { + const tester = context.iframe + await tester.locator(`xpath=${xpath}`).click({ + timeout: 1000, + ...options, + clickCount: 3, + }) + } + else if (provider instanceof WebdriverBrowserProvider) { + const browser = context.browser + const markedXpath = `//${xpath}` + await browser + .action('pointer', { parameters: { pointerType: 'mouse' } }) + // move the pointer over the button + .move({ origin: await browser.$(markedXpath) }) + // simulate 3 clicks + .down() + .up() + .pause(50) + .down() + .up() + .pause(50) + .down() + .up() + .pause(50) + // run the sequence + .perform() + } + else { + throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`) + } +} diff --git a/packages/browser/src/node/commands/index.ts b/packages/browser/src/node/commands/index.ts index ced9b4af34c2..429882d5d08c 100644 --- a/packages/browser/src/node/commands/index.ts +++ b/packages/browser/src/node/commands/index.ts @@ -1,4 +1,4 @@ -import { click, dblClick } from './click' +import { click, dblClick, tripleClick } from './click' import { type } from './type' import { clear } from './clear' import { fill } from './fill' @@ -20,6 +20,7 @@ export default { writeFile, __vitest_click: click, __vitest_dblClick: dblClick, + __vitest_tripleClick: tripleClick, __vitest_screenshot: screenshot, __vitest_type: type, __vitest_clear: clear, diff --git a/test/browser/test/userEvent.test.ts b/test/browser/test/userEvent.test.ts index 81b18735d2da..b02b64ebf07b 100644 --- a/test/browser/test/userEvent.test.ts +++ b/test/browser/test/userEvent.test.ts @@ -75,6 +75,52 @@ describe('userEvent.dblClick', () => { }) }) +describe('userEvent.tripleClick', () => { + test('correctly clicks a button', async () => { + const button = document.createElement('button') + button.textContent = 'Click me' + document.body.appendChild(button) + const onClick = vi.fn() + const dblClick = vi.fn() + const tripleClick = vi.fn() + button.addEventListener('click', onClick) + button.addEventListener('dblclick', dblClick) + button.addEventListener('click', tripleClick) + + await userEvent.tripleClick(button) + + expect(onClick).toHaveBeenCalledTimes(3) + expect(dblClick).toHaveBeenCalledTimes(1) + expect(tripleClick).toHaveBeenCalledTimes(3) + expect(tripleClick.mock.calls.length).toBe(3) + expect(tripleClick.mock.calls + .map(c => c[0] as MouseEvent) + .filter(c => c.detail === 3)).toHaveLength(1) + }) + + test('correctly doesn\'t click on a disabled button', async () => { + const button = document.createElement('button') + button.textContent = 'Click me' + button.disabled = true + document.body.appendChild(button) + const onClick = vi.fn() + const dblClick = vi.fn() + const tripleClick = vi.fn() + button.addEventListener('click', onClick) + button.addEventListener('dblclick', dblClick) + button.addEventListener('click', tripleClick) + + await userEvent.tripleClick(button, { + // playwright requires force: true to click on a disabled button + force: true, + }) + + expect(onClick).not.toHaveBeenCalled() + expect(dblClick).not.toHaveBeenCalled() + expect(tripleClick).not.toHaveBeenCalled() + }) +}) + describe('userEvent.hover, userEvent.unhover', () => { test('hover works correctly', async () => { const target = document.createElement('div')