Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/browser/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ export async function dragDropElems(elem, toElem) {
await sendMouse({ type: 'up' });
}

export async function dragElemBy(elem, offsetX = 0, offsetY = 0) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name is a little odd. I'm trying to follow the pattern of the other helpers in this file, taking x and y offsets as parameters. They work a little differently though, and use "at" terminology, which doesn't make sense here.

But dragElemTo isn't correct, unless I take an x and y location rather than an offset. This is nicer for the helper, worse for the consumer - it's much easier if I can say "Drag it back 5 pixels" instead of "Drag it to spot x = 250".

const pixels = 10; // Mimic dragging by moving in 10px increments to the target position

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to simulate a user actually dragging something, which fires events every few pixels. Just moving from one place to another programmatically doesn't fire any intermediate values.

I'm firing every 10px, but this could also just break up the values passed into 5 steps of x pixels. That's likely better for large numbers, whereas this is better for small number (eg not bothering to divide a move of 2 pixels).

const numSteps = Math.ceil(Math.max(Math.abs(offsetX), Math.abs(offsetY)) / pixels);

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), pixels * i);
const dy = Math.sign(offsetY) * Math.min(Math.abs(offsetY), pixels * 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 });
Expand Down
2 changes: 1 addition & 1 deletion src/browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
69 changes: 68 additions & 1 deletion test/browser/commands.test.js
Original file line number Diff line number Diff line change
@@ -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`<button>text</button>`;
const dragTemplate = html`<div style="position: absolute; top: 95px; left: 95px; width: 10px; height: 10px;"></div>`;
const draggableTemplate = html`
<div>
<div id="dest" style="height: 100px; width: 100px;"></div>
Expand All @@ -19,6 +20,7 @@ describe('commands', () => {
let elem, focusSource, hovered, key, keys;
const clickPos = { x: 0, y: 0 };
const mousePos = { x: 0, y: 0 };
const pointerEvents = [];

function onClick(e) {
clickPos.x = e.clientX;
Expand All @@ -41,6 +43,14 @@ describe('commands', () => {
mousePos.y = e.clientY;
}

function onPointer(e) {
pointerEvents.push({
type: e.type,
x: e.clientX,
y: e.clientY
});
}

function onMouseOver() {
hovered = true;
}
Expand All @@ -53,12 +63,22 @@ describe('commands', () => {
window.addEventListener('click', onClick);
window.addEventListener('keydown', onKeyDown);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('pointerdown', onPointer);
window.addEventListener('pointermove', onPointer);
window.addEventListener('pointerup', onPointer);
});

beforeEach(() => {
pointerEvents.length = 0;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not sure I love this - was trying to follow the pattern of the file and define this stuff at the top level. But it might be better to just set it and remove it in the tests below, rather than having to clear this value every test, then clear it again after calling the fixture so I don't get any mouse moves as things reset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what that looks like instead: #1070

});

after(() => {
window.removeEventListener('click', onClick);
window.removeEventListener('keydown', onKeyDown);
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('pointerdown', onPointer);
window.removeEventListener('pointermove', onPointer);
window.removeEventListener('pointerup', onPointer);
});

describe('click/hover', () => {
Expand Down Expand Up @@ -194,6 +214,53 @@ describe('commands', () => {

});

describe('drag', () => {
const getDragMoves = (events) => events.slice(1).filter(e => e.type === 'pointermove').map(e => ({ x: e.x, y: e.y }));

beforeEach(async() => {
elem = await fixture(dragTemplate);
pointerEvents.length = 0;
});

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(pointerEvents).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(getDragMoves(pointerEvents)).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(getDragMoves(pointerEvents)).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(getDragMoves(pointerEvents)).to.deep.equal([
{ x: 90, y: 90 },
{ x: 80, y: 80 },
{ x: 75, y: 75 },
]);
});
});

describe('drag & drop', () => {

it('should drag & drop element', (done) => {
Expand Down