diff --git a/src/browser/commands.js b/src/browser/commands.js index 3d9883be..37bb3d89 100644 --- a/src/browser/commands.js +++ b/src/browser/commands.js @@ -40,6 +40,22 @@ export async function dragDropElems(elem, toElem) { await sendMouse({ type: 'up' }); } +export async function dragElemBy(elem, offsetX = 0, offsetY = 0) { + const dragIncrementSize = 10; // To mimic real user dragging + const numSteps = Math.ceil(Math.max(Math.abs(offsetX), Math.abs(offsetY)) / dragIncrementSize); + + const position = getElementPosition(elem); + await sendMouse({ type: 'move', position: [position.x, position.y] }); + + await sendMouse({ type: 'down' }); + for (let i = 1; i <= numSteps; i++) { + const dx = Math.sign(offsetX) * Math.min(Math.abs(offsetX), dragIncrementSize * i); + const dy = Math.sign(offsetY) * Math.min(Math.abs(offsetY), dragIncrementSize * i); + await sendMouse({ type: 'move', position: [position.x + dx, position.y + dy] }); + } + await sendMouse({ type: 'up' }); +} + export async function focusElem(elem) { await cmdSendKeys({ press: 'Shift' }); // Tab moves focus, Escape causes dismissible things to close elem.focus({ focusVisible: true }); diff --git a/src/browser/index.js b/src/browser/index.js index cc27b0ac..2a709401 100644 --- a/src/browser/index.js +++ b/src/browser/index.js @@ -2,6 +2,6 @@ import './vdiff.js'; import './axe.js'; export { assert, aTimeout, defineCE, expect, html, nextFrame, oneDefaultPreventedEvent, oneEvent, waitUntil } from '@open-wc/testing'; -export { clickAt, clickElem, clickElemAt, dragDropElems, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from './commands.js'; +export { clickAt, clickElem, clickElemAt, dragDropElems, dragElemBy, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from './commands.js'; export { fixture, waitForElem } from './fixture.js'; export { runConstructor } from './constructor.js'; diff --git a/test/browser/commands.test.js b/test/browser/commands.test.js index 7162ce3a..3cdbcd4f 100644 --- a/test/browser/commands.test.js +++ b/test/browser/commands.test.js @@ -1,9 +1,10 @@ -import { clickAt, clickElem, clickElemAt, dragDropElems, expect, fixture, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from '../../src/browser/index.js'; +import { clickAt, clickElem, clickElemAt, dragDropElems, dragElemBy, expect, fixture, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from '../../src/browser/index.js'; import { html } from 'lit'; import { spy } from 'sinon'; describe('commands', () => { const buttonTemplate = html``; + const dragTemplate = html`
`; const draggableTemplate = html`
@@ -194,6 +195,73 @@ describe('commands', () => { }); + describe('drag', () => { + const allPointerEvents = [], dragMoveEvents = []; + let dragStarted = false; + function onPointer(e) { + allPointerEvents.push({ type: e.type, x: e.clientX, y: e.clientY }); + if (e.type === 'pointerdown') dragStarted = true; + else if (e.type === 'pointerup') dragStarted = false; + else if (e.type === 'pointermove' && dragStarted) dragMoveEvents.push({ x: e.clientX, y: e.clientY }); + } + + beforeEach(async() => { + elem = await fixture(dragTemplate); + + window.addEventListener('pointerdown', onPointer); + window.addEventListener('pointermove', onPointer); + window.addEventListener('pointerup', onPointer); + }); + + afterEach(() => { + window.removeEventListener('pointerdown', onPointer); + window.removeEventListener('pointermove', onPointer); + window.removeEventListener('pointerup', onPointer); + + allPointerEvents.length = 0; + dragMoveEvents.length = 0; + dragStarted = false; + }); + + it('should start dragging from the center of element and fire pointer events throughout the full drag flow', async() => { + await dragElemBy(elem, 20, 20); + expect(allPointerEvents).to.deep.equal([ + { type: 'pointermove', x: 100, y: 100 }, // Move to element center + { type: 'pointerdown', x: 100, y: 100 }, // Start drag + { type: 'pointermove', x: 110, y: 110 }, // Drag 10px + { type: 'pointermove', x: 120, y: 120 }, // Drag 10px + { type: 'pointerup', x: 120, y: 120 }, // End drag + ]); + }); + + it('should move to the target offset in 10px increments to the max', async() => { + await dragElemBy(elem, 25, 0); + expect(dragMoveEvents).to.deep.equal([ + { x: 110, y: 100 }, + { x: 120, y: 100 }, + { x: 125, y: 100 }, + ]); + }); + + it('should clamp the shorter axis while the longer axis keeps stepping', async() => { + await dragElemBy(elem, 30, 15); + expect(dragMoveEvents).to.deep.equal([ + { x: 110, y: 110 }, + { x: 120, y: 115 }, + { x: 130, y: 115 }, + ]); + }); + + it('should drag in negative directions', async() => { + await dragElemBy(elem, -25, -25); + expect(dragMoveEvents).to.deep.equal([ + { x: 90, y: 90 }, + { x: 80, y: 80 }, + { x: 75, y: 75 }, + ]); + }); + }); + describe('drag & drop', () => { it('should drag & drop element', (done) => { @@ -226,6 +294,8 @@ describe('commands', () => { { command: 'clickElem', action: (elem) => clickElem(elem) }, { command: 'clickAt', action: () => clickAt(5, 10) }, { command: 'clickElemAt', action: (elem) => clickElemAt(elem, 10, 10) }, + { command: 'dragDropElems', action: (elem) => dragDropElems(elem, elem) }, + { command: 'dragElemBy', action: (elem) => dragElemBy(elem, 10, 10) }, { command: 'hoverElem', action: (elem) => hoverElem(elem) }, { command: 'hoverAt', action: () => hoverAt(5, 10) }, { command: 'hoverElemAt', action: (elem) => hoverElemAt(elem, 10, 10) },