Skip to content

Commit 1d15ea7

Browse files
committed
fix(cdk/testing): simulate offsetX and offsetY in mouse events
We already supported dispatching mouse events at specific element coordinates, but we were using them only to set `clientX` and `clientY`. These changes also set `offsetX` and `offsetY` since they're the native representation of element-relative coordinates.
1 parent aadcb96 commit 1d15ea7

File tree

8 files changed

+96
-20
lines changed

8 files changed

+96
-20
lines changed

src/cdk/testing/testbed/fake-events/dispatch-events.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,15 @@ export function dispatchMouseEvent(
5656
type: string,
5757
clientX = 0,
5858
clientY = 0,
59+
offsetX?: number,
60+
offsetY?: number,
5961
button?: number,
6062
modifiers?: ModifierKeys,
6163
): MouseEvent {
62-
return dispatchEvent(node, createMouseEvent(type, clientX, clientY, button, modifiers));
64+
return dispatchEvent(
65+
node,
66+
createMouseEvent(type, clientX, clientY, offsetX, offsetY, button, modifiers),
67+
);
6368
}
6469

6570
/**
@@ -71,9 +76,14 @@ export function dispatchPointerEvent(
7176
type: string,
7277
clientX = 0,
7378
clientY = 0,
79+
offsetX?: number,
80+
offsetY?: number,
7481
options?: PointerEventInit,
7582
): PointerEvent {
76-
return dispatchEvent(node, createPointerEvent(type, clientX, clientY, options)) as PointerEvent;
83+
return dispatchEvent(
84+
node,
85+
createPointerEvent(type, clientX, clientY, offsetX, offsetY, options),
86+
) as PointerEvent;
7787
}
7888

7989
/**

src/cdk/testing/testbed/fake-events/event-objects.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export function createMouseEvent(
1919
type: string,
2020
clientX = 0,
2121
clientY = 0,
22+
offsetX = 1,
23+
offsetY = 1,
2224
button = 0,
2325
modifiers: ModifierKeys = {},
2426
) {
@@ -50,8 +52,13 @@ export function createMouseEvent(
5052

5153
// The `MouseEvent` constructor doesn't allow us to pass these properties into the constructor.
5254
// Override them to `1`, because they're used for fake screen reader event detection.
53-
defineReadonlyEventProperty(event, 'offsetX', 1);
54-
defineReadonlyEventProperty(event, 'offsetY', 1);
55+
if (offsetX != null) {
56+
defineReadonlyEventProperty(event, 'offsetX', offsetX);
57+
}
58+
59+
if (offsetY != null) {
60+
defineReadonlyEventProperty(event, 'offsetY', offsetY);
61+
}
5562

5663
return event;
5764
}
@@ -70,9 +77,11 @@ export function createPointerEvent(
7077
type: string,
7178
clientX = 0,
7279
clientY = 0,
80+
offsetX?: number,
81+
offsetY?: number,
7382
options: PointerEventInit = {isPrimary: true},
7483
) {
75-
return new PointerEvent(type, {
84+
const event = new PointerEvent(type, {
7685
bubbles: true,
7786
cancelable: true,
7887
composed: true, // Required for shadow DOM events.
@@ -81,6 +90,16 @@ export function createPointerEvent(
8190
clientY,
8291
...options,
8392
});
93+
94+
if (offsetX != null) {
95+
defineReadonlyEventProperty(event, 'offsetX', offsetX);
96+
}
97+
98+
if (offsetY != null) {
99+
defineReadonlyEventProperty(event, 'offsetY', offsetY);
100+
}
101+
102+
return event;
84103
}
85104

86105
/**

src/cdk/testing/testbed/unit-test-element.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,19 @@ export class UnitTestElement implements TestElement {
283283
name: string,
284284
clientX?: number,
285285
clientY?: number,
286+
offsetX?: number,
287+
offsetY?: number,
286288
button?: number,
287289
) {
288290
// The latest versions of all browsers we support have the new `PointerEvent` API.
289291
// Though since we capture the two most recent versions of these browsers, we also
290292
// need to support Safari 12 at time of writing. Safari 12 does not have support for this,
291293
// so we need to conditionally create and dispatch these events based on feature detection.
292294
if (typeof PointerEvent !== 'undefined' && PointerEvent) {
293-
dispatchPointerEvent(this.element, name, clientX, clientY, {isPrimary: true, button});
295+
dispatchPointerEvent(this.element, name, clientX, clientY, offsetX, offsetY, {
296+
isPrimary: true,
297+
button,
298+
});
294299
}
295300
}
296301

@@ -305,6 +310,8 @@ export class UnitTestElement implements TestElement {
305310
) {
306311
let clientX: number | undefined = undefined;
307312
let clientY: number | undefined = undefined;
313+
let offsetX: number | undefined = undefined;
314+
let offsetY: number | undefined = undefined;
308315
let modifiers: ModifierKeys = {};
309316

310317
if (args.length && typeof args[args.length - 1] === 'object') {
@@ -313,23 +320,57 @@ export class UnitTestElement implements TestElement {
313320

314321
if (args.length) {
315322
const {left, top, width, height} = await this.getDimensions();
316-
const relativeX = args[0] === 'center' ? width / 2 : (args[0] as number);
317-
const relativeY = args[0] === 'center' ? height / 2 : (args[1] as number);
323+
offsetX = args[0] === 'center' ? width / 2 : (args[0] as number);
324+
offsetY = args[0] === 'center' ? height / 2 : (args[1] as number);
318325

319326
// Round the computed click position as decimal pixels are not
320327
// supported by mouse events and could lead to unexpected results.
321-
clientX = Math.round(left + relativeX);
322-
clientY = Math.round(top + relativeY);
328+
clientX = Math.round(left + offsetX);
329+
clientY = Math.round(top + offsetY);
323330
}
324331

325-
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY, button);
326-
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button, modifiers);
327-
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, button);
328-
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button, modifiers);
332+
this._dispatchPointerEventIfSupported(
333+
'pointerdown',
334+
clientX,
335+
clientY,
336+
offsetX,
337+
offsetY,
338+
button,
339+
);
340+
dispatchMouseEvent(
341+
this.element,
342+
'mousedown',
343+
clientX,
344+
clientY,
345+
offsetX,
346+
offsetY,
347+
button,
348+
modifiers,
349+
);
350+
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, offsetX, offsetY, button);
351+
dispatchMouseEvent(
352+
this.element,
353+
'mouseup',
354+
clientX,
355+
clientY,
356+
offsetX,
357+
offsetY,
358+
button,
359+
modifiers,
360+
);
329361

330362
// If a primary event name is specified, emit it after the mouse event sequence.
331363
if (primaryEventName !== null) {
332-
dispatchMouseEvent(this.element, primaryEventName, clientX, clientY, button, modifiers);
364+
dispatchMouseEvent(
365+
this.element,
366+
primaryEventName,
367+
clientX,
368+
clientY,
369+
offsetX,
370+
offsetY,
371+
button,
372+
modifiers,
373+
);
333374
}
334375

335376
// This call to _stabilize should not be needed since the callers will already do that them-

src/material-experimental/mdc-menu/menu.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ describe('MDC-based MatMenu', () => {
431431
focusMonitor.monitor(triggerEl, false);
432432

433433
// Trigger a fake right click.
434-
dispatchEvent(triggerEl, createMouseEvent('mousedown', 50, 100, 2));
434+
dispatchEvent(triggerEl, createMouseEvent('mousedown', 50, 100, undefined, undefined, 2));
435435

436436
// A click without a left button mousedown before it is considered a keyboard open.
437437
triggerEl.click();

src/material-experimental/mdc-tabs/tab-header.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,10 @@ describe('MDC-based MatTabHeader', () => {
493493
it('should not scroll when pressing the right mouse button', fakeAsync(() => {
494494
expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0);
495495

496-
dispatchEvent(nextButton, createMouseEvent('mousedown', undefined, undefined, 2));
496+
dispatchEvent(
497+
nextButton,
498+
createMouseEvent('mousedown', undefined, undefined, undefined, undefined, 2),
499+
);
497500
fixture.detectChanges();
498501
tick(3000);
499502

src/material/menu/menu.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ describe('MatMenu', () => {
432432
focusMonitor.monitor(triggerEl, false);
433433

434434
// Trigger a fake right click.
435-
dispatchEvent(triggerEl, createMouseEvent('mousedown', 50, 100, 2));
435+
dispatchEvent(triggerEl, createMouseEvent('mousedown', 50, 100, undefined, undefined, 2));
436436

437437
// A click without a left button mousedown before it is considered a keyboard open.
438438
triggerEl.click();

src/material/slider/slider.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1778,7 +1778,7 @@ function dispatchMousedownEventSequence(
17781778
const y = dimensions.top + dimensions.height * percentage;
17791779

17801780
dispatchMouseenterEvent(sliderElement);
1781-
dispatchEvent(sliderElement, createMouseEvent('mousedown', x, y, button));
1781+
dispatchEvent(sliderElement, createMouseEvent('mousedown', x, y, undefined, undefined, button));
17821782
}
17831783

17841784
/**

src/material/tabs/tab-header.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,10 @@ describe('MatTabHeader', () => {
490490
it('should not scroll when pressing the right mouse button', fakeAsync(() => {
491491
expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0);
492492

493-
dispatchEvent(nextButton, createMouseEvent('mousedown', undefined, undefined, 2));
493+
dispatchEvent(
494+
nextButton,
495+
createMouseEvent('mousedown', undefined, undefined, undefined, undefined, 2),
496+
);
494497
fixture.detectChanges();
495498
tick(3000);
496499

0 commit comments

Comments
 (0)