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) },