[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

test: pointer API

Raul Macarie (Jul 21, 2026, 4:03 PM +0200) 4ac160ab ea74150e

+259 -2
+259 -2
test/browser/fixtures/user-event/pointer.test.ts
··· 1 1 import { test, vi } from 'vitest' 2 - import { userEvent, page } from 'vitest/browser' 2 + import { userEvent, page, server } from 'vitest/browser' 3 3 4 4 type PointerAction = (event: PointerEvent) => void 5 5 6 - test('hovers and clicks a button', async ({ expect }) => { 6 + test('click triggers hover events', async ({ expect }) => { 7 7 document.body.innerHTML = ` 8 8 <div style="padding: 1rem;"> 9 9 <button>Button</button> ··· 33 33 34 34 expect(enter).toHaveBeenCalledBefore(click) 35 35 expect(click).toHaveBeenCalledBefore(leave) 36 + }) 37 + 38 + test('moves between coordinates', async ({ expect }) => { 39 + document.body.innerHTML = ` 40 + <div id="a" style="position:absolute; top:0; left:0; width:100px; height:100px;"></div> 41 + <div id="b" style="position:absolute; top:200px; left:0; width:100px; height:100px;"></div> 42 + ` 43 + 44 + const enterA = vi.fn<PointerAction>() 45 + const leaveA = vi.fn<PointerAction>() 46 + const enterB = vi.fn<PointerAction>() 47 + 48 + const a = document.body.querySelector('#a') 49 + const b = document.body.querySelector('#b') 50 + 51 + a.addEventListener('mouseenter', enterA) 52 + a.addEventListener('mouseleave', leaveA) 53 + b.addEventListener('mouseenter', enterB) 54 + 55 + await userEvent.pointer([ 56 + { coordinates: { x: 50, y: 50 } }, 57 + { coordinates: { x: 50, y: 250 } }, 58 + ]) 59 + 60 + expect(enterA).toHaveBeenCalledOnce() 61 + expect(leaveA).toHaveBeenCalledOnce() 62 + expect(enterB).toHaveBeenCalledOnce() 63 + expect(enterA).toHaveBeenCalledBefore(leaveA) 64 + expect(leaveA).toHaveBeenCalledBefore(enterB) 65 + }) 66 + 67 + test('clicks at coordinates', async ({ expect }) => { 68 + document.body.innerHTML = ` 69 + <button style="position: absolute; top: 10px; left: 10px; width: 100px; height: 40px;">Button</button> 70 + ` 71 + 72 + const click = vi.fn<PointerAction>() 73 + 74 + const buttonElement = document.body.querySelector('button') 75 + 76 + buttonElement.addEventListener('click', click) 77 + 78 + await userEvent.pointer([ 79 + { coordinates: { x: 11, y: 11 }, action: 'click' }, 80 + ]) 81 + 82 + expect(click).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ 83 + clientX: expect.closeTo(11), 84 + clientY: expect.closeTo(11), 85 + })) 86 + }) 87 + 88 + test('down only fires mousedown', async ({ expect }) => { 89 + document.body.innerHTML = `<button>Button</button>` 90 + 91 + const down = vi.fn<PointerAction>() 92 + const up = vi.fn<PointerAction>() 93 + const click = vi.fn<PointerAction>() 94 + 95 + const buttonElement = document.body.querySelector('button') 96 + 97 + buttonElement.addEventListener('mousedown', down) 98 + buttonElement.addEventListener('mouseup', up) 99 + buttonElement.addEventListener('click', click) 100 + 101 + const target = page.getByRole('button') 102 + 103 + await userEvent.pointer([ 104 + { target, action: 'down' }, 105 + ]) 106 + 107 + expect(down).toHaveBeenCalledOnce() 108 + expect(up).not.toHaveBeenCalled() 109 + expect(click).not.toHaveBeenCalled() 110 + }) 111 + 112 + test('middle click', async ({ expect }) => { 113 + document.body.innerHTML = `<button>Button</button>` 114 + 115 + const down = vi.fn<PointerAction>() 116 + const up = vi.fn<PointerAction>() 117 + const click = vi.fn<PointerAction>() 118 + 119 + const buttonElement = document.body.querySelector('button') 120 + 121 + buttonElement.addEventListener('mousedown', down) 122 + buttonElement.addEventListener('mouseup', up) 123 + buttonElement.addEventListener('click', click) 124 + 125 + const target = page.getByRole('button') 126 + 127 + await userEvent.pointer([ 128 + { target, button: 'middle', action: 'down' }, 129 + ]) 130 + 131 + expect(down).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ 132 + button: 1, 133 + })) 134 + expect(up).not.toHaveBeenCalled() 135 + expect(click).not.toHaveBeenCalled() 136 + }) 137 + 138 + test('drags and drops', async ({ expect }) => { 139 + document.body.innerHTML = ` 140 + <div id="source" draggable="true" style="position: absolute; top: 0; left: 0; width: 50px; height: 50px;">Drag me</div> 141 + <div id="target" style="position: absolute; top: 0; left: 300px; width: 100px; height: 100px;">Drop here</div> 142 + ` 143 + 144 + const source = document.body.querySelector<HTMLElement>('#source') 145 + const dropTarget = document.body.querySelector<HTMLElement>('#target') 146 + 147 + type DragAction = (event: DragEvent) => void 148 + 149 + const dragStart = vi.fn<DragAction>() 150 + const dragEnter = vi.fn<DragAction>() 151 + const drop = vi.fn<DragAction>() 152 + const dragEnd = vi.fn<DragAction>() 153 + 154 + source.addEventListener('dragstart', dragStart) 155 + dropTarget.addEventListener('dragenter', dragEnter) 156 + dropTarget.addEventListener('dragover', (event) => event.preventDefault()) 157 + dropTarget.addEventListener('drop', drop) 158 + source.addEventListener('dragend', dragEnd) 159 + 160 + await userEvent.pointer([ 161 + { target: source, action: 'down' }, 162 + { target: dropTarget }, 163 + { target: dropTarget, action: 'up' }, 164 + ]) 165 + 166 + expect(dragStart).toHaveBeenCalledOnce() 167 + expect(dragEnter).toHaveBeenCalledOnce() 168 + expect(drop).toHaveBeenCalledOnce() 169 + expect(dragEnd).toHaveBeenCalledOnce() 170 + expect(dragStart).toHaveBeenCalledBefore(dragEnter) 171 + expect(dragEnter).toHaveBeenCalledBefore(drop) 172 + expect(drop).toHaveBeenCalledBefore(dragEnd) 173 + }) 174 + 175 + test('temporary modifiers apply to one action', async ({ expect }) => { 176 + document.body.innerHTML = `<button>Button</button>` 177 + 178 + const click = vi.fn<PointerAction>() 179 + 180 + const buttonElement = document.body.querySelector('button') 181 + 182 + buttonElement.addEventListener('click', click) 183 + 184 + const target = page.getByRole('button') 185 + 186 + await userEvent.pointer([ 187 + { target, action: 'click', keys: '{ShiftLeft}' }, 188 + { target, action: 'click' }, 189 + ]) 190 + 191 + expect(click).toHaveBeenCalledTimes(2) 192 + expect(click).toHaveBeenNthCalledWith(1, expect.objectContaining({ shiftKey: true })) 193 + expect(click).toHaveBeenNthCalledWith(2, expect.objectContaining({ shiftKey: false })) 194 + }) 195 + 196 + test('persistent modifiers survive multiple actions', async ({ expect }) => { 197 + document.body.innerHTML = ` 198 + <button id="a">A</button> 199 + <button id="b">B</button> 200 + <button id="c">C</button> 201 + <button id="d">D</button> 202 + ` 203 + 204 + const clickA = vi.fn<PointerAction>() 205 + const clickB = vi.fn<PointerAction>() 206 + const clickC = vi.fn<PointerAction>() 207 + const clickD = vi.fn<PointerAction>() 208 + 209 + const a = document.body.querySelector('#a') 210 + const b = document.body.querySelector('#b') 211 + const c = document.body.querySelector('#c') 212 + const d = document.body.querySelector('#d') 213 + 214 + a.addEventListener('click', clickA) 215 + b.addEventListener('click', clickB) 216 + c.addEventListener('click', clickC) 217 + d.addEventListener('click', clickD) 218 + 219 + await userEvent.pointer([ 220 + { target: a, action: 'click', keys: '{ShiftLeft>}{AltLeft>}' }, 221 + { target: b, action: 'click' }, 222 + { target: c, action: 'click', keys: '{/ShiftLeft}{/AltLeft}' }, 223 + { target: d, action: 'click' }, 224 + ]) 225 + 226 + expect(clickA).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ shiftKey: true, altKey: true })) 227 + expect(clickB).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ shiftKey: true, altKey: true })) 228 + expect(clickC).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ shiftKey: true, altKey: true })) 229 + expect(clickD).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ shiftKey: false, altKey: false })) 230 + }) 231 + 232 + test('modifiers work with coordinates', async ({ expect }) => { 233 + document.body.innerHTML = ` 234 + <button style="position: absolute; top: 10px; left: 10px; width: 100px; height: 40px;">Button</button> 235 + ` 236 + 237 + const click = vi.fn<PointerAction>() 238 + 239 + const buttonElement = document.body.querySelector('button') 240 + 241 + buttonElement.addEventListener('click', click) 242 + 243 + await userEvent.pointer([ 244 + { 245 + coordinates: { x: 11, y: 11 }, 246 + action: 'click', 247 + keys: '{AltLeft}', 248 + }, 249 + ]) 250 + 251 + expect(click).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ 252 + altKey: true, 253 + clientX: expect.closeTo(11), 254 + clientY: expect.closeTo(11), 255 + })) 256 + }) 257 + 258 + // on webkit, right click opens the context menu even in headless 259 + // keep as the last test to prevent failing tests 260 + test('right click', async ({ expect }) => { 261 + document.body.innerHTML = `<button>Button</button>` 262 + 263 + const down = vi.fn<PointerAction>() 264 + const up = vi.fn<PointerAction>() 265 + const click = vi.fn<PointerAction>() 266 + 267 + const buttonElement = document.body.querySelector('button') 268 + 269 + buttonElement.addEventListener('mousedown', down) 270 + buttonElement.addEventListener('mouseup', up) 271 + buttonElement.addEventListener('click', click) 272 + 273 + const target = page.getByRole('button') 274 + 275 + await userEvent.pointer([ 276 + { target, button: 'right', action: 'click' }, 277 + ]) 278 + 279 + expect(down).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ 280 + button: 2, 281 + })) 282 + 283 + // mouseup is not fired on webkit when right clicking 284 + if (server.config.browser.name === 'webkit') { 285 + expect(up).not.toHaveBeenCalled() 286 + } else { 287 + expect(up).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ 288 + button: 2, 289 + })) 290 + } 291 + 292 + expect(click).not.toHaveBeenCalled() 36 293 })