Skip to content

Commit 200a434

Browse files
authored
feat(browser): add tripleClick to interactive api (#5987)
1 parent 152891b commit 200a434

File tree

6 files changed

+124
-1
lines changed

6 files changed

+124
-1
lines changed

docs/guide/browser.md

+25
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,31 @@ References:
551551
- [WebdriverIO `element.doubleClick` API](https://webdriver.io/docs/api/element/doubleClick/)
552552
- [testing-library `dblClick` API](https://testing-library.com/docs/user-event/convenience/#dblClick)
553553

554+
### userEvent.tripleClick
555+
556+
- **Type:** `(element: Element, options?: UserEventTripleClickOptions) => Promise<void>`
557+
558+
Triggers a triple click event on an element
559+
560+
Please refer to your provider's documentation for detailed explanation about how this method works.
561+
562+
```ts
563+
import { userEvent } from '@vitest/browser/context'
564+
import { screen } from '@testing-library/dom'
565+
566+
test('triggers a triple click on an element', async () => {
567+
const logo = screen.getByRole('img', { name: /logo/ })
568+
569+
await userEvent.tripleClick(logo)
570+
})
571+
```
572+
573+
References:
574+
575+
- [Playwright `locator.click` API](https://playwright.dev/docs/api/class-locator#locator-click)
576+
- [WebdriverIO `browser.action` API](https://webdriver.io/docs/api/browser/action/)
577+
- [testing-library `tripleClick` API](https://testing-library.com/docs/user-event/convenience/#tripleClick)
578+
554579
### userEvent.fill
555580

556581
- **Type:** `(element: Element, text: string) => Promise<void>`

packages/browser/context.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ export interface UserEvent {
6060
* @see {@link https://testing-library.com/docs/user-event/convenience/#dblClick} testing-library API
6161
*/
6262
dblClick: (element: Element, options?: UserEventDoubleClickOptions) => Promise<void>
63+
/**
64+
* Triggers a triple click event on an element. Uses provider's API under the hood.
65+
* @see {@link https://playwright.dev/docs/api/class-locator#locator-click} Playwright API: using `click` with `clickCount: 3`
66+
* @see {@link https://webdriver.io/docs/api/browser/actions/} WebdriverIO API: using actions with `move` and 3 `down + up + down` events in a row
67+
* @see {@link https://testing-library.com/docs/user-event/convenience/#tripleclick} testing-library API
68+
*/
69+
tripleClick: (element: Element, options?: UserEventTripleClickOptions) => Promise<void>
6370
/**
6471
* Choose one or more values from a select element. Uses provider's API under the hood.
6572
* If select doesn't have `multiple` attribute, only the first value will be selected.
@@ -165,6 +172,7 @@ export interface UserEventHoverOptions {}
165172
export interface UserEventSelectOptions {}
166173
export interface UserEventClickOptions {}
167174
export interface UserEventDoubleClickOptions {}
175+
export interface UserEventTripleClickOptions {}
168176
export interface UserEventDragAndDropOptions {}
169177

170178
export interface UserEventTabOptions {

packages/browser/src/client/tester/context.ts

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export const userEvent: UserEvent = {
7373
const xpath = convertElementToXPath(element)
7474
return triggerCommand('__vitest_dblClick', xpath, options)
7575
},
76+
tripleClick(element: Element, options: UserEventClickOptions = {}) {
77+
const xpath = convertElementToXPath(element)
78+
return triggerCommand('__vitest_tripleClick', xpath, options)
79+
},
7680
selectOptions(element, value) {
7781
const values = provider === 'webdriverio'
7882
? getWebdriverioSelectOptions(element, value)

packages/browser/src/node/commands/click.ts

+39
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,42 @@ export const dblClick: UserEventCommand<UserEvent['dblClick']> = async (
4545
throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`)
4646
}
4747
}
48+
49+
export const tripleClick: UserEventCommand<UserEvent['tripleClick']> = async (
50+
context,
51+
xpath,
52+
options = {},
53+
) => {
54+
const provider = context.provider
55+
if (provider instanceof PlaywrightBrowserProvider) {
56+
const tester = context.iframe
57+
await tester.locator(`xpath=${xpath}`).click({
58+
timeout: 1000,
59+
...options,
60+
clickCount: 3,
61+
})
62+
}
63+
else if (provider instanceof WebdriverBrowserProvider) {
64+
const browser = context.browser
65+
const markedXpath = `//${xpath}`
66+
await browser
67+
.action('pointer', { parameters: { pointerType: 'mouse' } })
68+
// move the pointer over the button
69+
.move({ origin: await browser.$(markedXpath) })
70+
// simulate 3 clicks
71+
.down()
72+
.up()
73+
.pause(50)
74+
.down()
75+
.up()
76+
.pause(50)
77+
.down()
78+
.up()
79+
.pause(50)
80+
// run the sequence
81+
.perform()
82+
}
83+
else {
84+
throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`)
85+
}
86+
}

packages/browser/src/node/commands/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { click, dblClick } from './click'
1+
import { click, dblClick, tripleClick } from './click'
22
import { type } from './type'
33
import { clear } from './clear'
44
import { fill } from './fill'
@@ -20,6 +20,7 @@ export default {
2020
writeFile,
2121
__vitest_click: click,
2222
__vitest_dblClick: dblClick,
23+
__vitest_tripleClick: tripleClick,
2324
__vitest_screenshot: screenshot,
2425
__vitest_type: type,
2526
__vitest_clear: clear,

test/browser/test/userEvent.test.ts

+46
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,52 @@ describe('userEvent.dblClick', () => {
7575
})
7676
})
7777

78+
describe('userEvent.tripleClick', () => {
79+
test('correctly clicks a button', async () => {
80+
const button = document.createElement('button')
81+
button.textContent = 'Click me'
82+
document.body.appendChild(button)
83+
const onClick = vi.fn()
84+
const dblClick = vi.fn()
85+
const tripleClick = vi.fn()
86+
button.addEventListener('click', onClick)
87+
button.addEventListener('dblclick', dblClick)
88+
button.addEventListener('click', tripleClick)
89+
90+
await userEvent.tripleClick(button)
91+
92+
expect(onClick).toHaveBeenCalledTimes(3)
93+
expect(dblClick).toHaveBeenCalledTimes(1)
94+
expect(tripleClick).toHaveBeenCalledTimes(3)
95+
expect(tripleClick.mock.calls.length).toBe(3)
96+
expect(tripleClick.mock.calls
97+
.map(c => c[0] as MouseEvent)
98+
.filter(c => c.detail === 3)).toHaveLength(1)
99+
})
100+
101+
test('correctly doesn\'t click on a disabled button', async () => {
102+
const button = document.createElement('button')
103+
button.textContent = 'Click me'
104+
button.disabled = true
105+
document.body.appendChild(button)
106+
const onClick = vi.fn()
107+
const dblClick = vi.fn()
108+
const tripleClick = vi.fn()
109+
button.addEventListener('click', onClick)
110+
button.addEventListener('dblclick', dblClick)
111+
button.addEventListener('click', tripleClick)
112+
113+
await userEvent.tripleClick(button, {
114+
// playwright requires force: true to click on a disabled button
115+
force: true,
116+
})
117+
118+
expect(onClick).not.toHaveBeenCalled()
119+
expect(dblClick).not.toHaveBeenCalled()
120+
expect(tripleClick).not.toHaveBeenCalled()
121+
})
122+
})
123+
78124
describe('userEvent.hover, userEvent.unhover', () => {
79125
test('hover works correctly', async () => {
80126
const target = document.createElement('div')

0 commit comments

Comments
 (0)