Skip to content

Commit 80a512d

Browse files
committed
feat(cdk/testing): support modifiers for clicking on a TestElement
Adds the ability to provide modifier keys when clicking on a TestElement
1 parent 0cd3671 commit 80a512d

File tree

9 files changed

+93
-32
lines changed

9 files changed

+93
-32
lines changed

src/cdk/testing/protractor/protractor-element.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ export class ProtractorElement implements TestElement {
8181
return this.element.clear();
8282
}
8383

84-
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
85-
await this._dispatchClickEventSequence(args);
84+
async click(...args: [ModifierKeys?] | ['center', ModifierKeys?] |
85+
[number, number, ModifierKeys?]): Promise<void> {
86+
await this._dispatchClickEventSequence(args, Button.LEFT);
8687
}
8788

8889
async rightClick(...args: [] | ['center'] | [number, number]): Promise<void> {
@@ -202,17 +203,33 @@ export class ProtractorElement implements TestElement {
202203

203204
/** Dispatches all the events that are part of a click event sequence. */
204205
private async _dispatchClickEventSequence(
205-
args: [] | ['center'] | [number, number],
206-
button?: string) {
206+
args: [ModifierKeys?] | ['center', ModifierKeys?] |
207+
[number, number, ModifierKeys?],
208+
button: string) {
209+
let modifiers: ModifierKeys = {};
210+
if (args.length && typeof args[args.length - 1] === 'object') {
211+
modifiers = args.pop() as ModifierKeys;
212+
}
213+
const modifierKeys = toProtractorModifierKeys(modifiers);
214+
207215
// Omitting the offset argument to mouseMove results in clicking the center.
208-
// This is the default behavior we want, so we use an empty array of offsetArgs if no args are
209-
// passed to this method.
210-
const offsetArgs = args.length === 2 ? [{x: args[0], y: args[1]}] : [];
211-
212-
await browser.actions()
213-
.mouseMove(await this.element.getWebElement(), ...offsetArgs)
214-
.click(button)
215-
.perform();
216+
// This is the default behavior we want, so we use an empty array of offsetArgs if
217+
// no args remain after popping the modifiers from the args passed to this function.
218+
const offsetArgs = (args.length === 2 ?
219+
[{x: args[0], y: args[1]}] : []) as [{x: number, y: number}];
220+
221+
let actions = browser.actions()
222+
.mouseMove(await this.element.getWebElement(), ...offsetArgs);
223+
224+
for (const modifierKey of modifierKeys) {
225+
actions = actions.keyDown(modifierKey);
226+
}
227+
actions = actions.click(button);
228+
for (const modifierKey of modifierKeys) {
229+
actions = actions.keyUp(modifierKey);
230+
}
231+
232+
await actions.perform();
216233
}
217234
}
218235

src/cdk/testing/test-element.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,17 @@ export interface TestElement {
7676
* the element is clicked at a specific location, consider using `click('center')` or
7777
* `click(x, y)` instead.
7878
*/
79-
click(): Promise<void>;
79+
click(modifiers?: ModifierKeys): Promise<void>;
8080

8181
/** Click the element at the element's center. */
82-
click(location: 'center'): Promise<void>;
82+
click(location: 'center', modifiers?: ModifierKeys, ): Promise<void>;
8383

8484
/**
8585
* Click the element at the specified coordinates relative to the top-left of the element.
8686
* @param relativeX Coordinate within the element, along the X-axis at which to click.
8787
* @param relativeY Coordinate within the element, along the Y-axis at which to click.
8888
*/
89-
click(relativeX: number, relativeY: number): Promise<void>;
89+
click(relativeX: number, relativeY: number, modifiers?: ModifierKeys, ): Promise<void>;
9090

9191
/**
9292
* Right clicks on the element at the specified coordinates relative to the top-left of it.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export function dispatchKeyboardEvent(node: Node, type: string, keyCode?: number
4747
* Shorthand to dispatch a mouse event on the specified coordinates.
4848
* @docs-private
4949
*/
50-
export function dispatchMouseEvent(
51-
node: Node, type: string, clientX = 0, clientY = 0, button?: number): MouseEvent {
52-
return dispatchEvent(node, createMouseEvent(type, clientX, clientY, button));
50+
export function dispatchMouseEvent( node: Node, type: string, clientX = 0, clientY = 0,
51+
button?: number, modifiers?: ModifierKeys): MouseEvent {
52+
return dispatchEvent(node, createMouseEvent(type, clientX, clientY, button, modifiers));
5353
}
5454

5555
/**

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {ModifierKeys} from '@angular/cdk/testing';
1212
* Creates a browser MouseEvent with the specified options.
1313
* @docs-private
1414
*/
15-
export function createMouseEvent(type: string, clientX = 0, clientY = 0, button = 0) {
15+
export function createMouseEvent(
16+
type: string, clientX = 0, clientY = 0, button = 0, modifiers: ModifierKeys = {}) {
1617
const event = document.createEvent('MouseEvent');
1718
const originalPreventDefault = event.preventDefault.bind(event);
1819

@@ -32,10 +33,10 @@ export function createMouseEvent(type: string, clientX = 0, clientY = 0, button
3233
/* screenY */ screenY,
3334
/* clientX */ clientX,
3435
/* clientY */ clientY,
35-
/* ctrlKey */ false,
36-
/* altKey */ false,
37-
/* shiftKey */ false,
38-
/* metaKey */ false,
36+
/* ctrlKey */ !!modifiers.control,
37+
/* altKey */ !!modifiers.alt,
38+
/* shiftKey */ !!modifiers.shift,
39+
/* metaKey */ !!modifiers.meta,
3940
/* button */ button,
4041
/* relatedTarget */ null);
4142

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ export class UnitTestElement implements TestElement {
8080
await this._stabilize();
8181
}
8282

83-
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
84-
await this._dispatchMouseEventSequence('click', args);
83+
async click(...args: [ModifierKeys?] | ['center', ModifierKeys?] |
84+
[number, number, ModifierKeys?]): Promise<void> {
85+
await this._dispatchMouseEventSequence('click', args, 0);
8586
await this._stabilize();
8687
}
8788

@@ -224,15 +225,20 @@ export class UnitTestElement implements TestElement {
224225
/** Dispatches all the events that are part of a mouse event sequence. */
225226
private async _dispatchMouseEventSequence(
226227
name: string,
227-
args: [] | ['center'] | [number, number],
228+
args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?],
228229
button?: number) {
229230
let clientX: number | undefined = undefined;
230231
let clientY: number | undefined = undefined;
232+
let modifiers: ModifierKeys = {};
233+
234+
if (args.length && typeof args[args.length - 1] === 'object') {
235+
modifiers = args.pop() as ModifierKeys;
236+
}
231237

232238
if (args.length) {
233239
const {left, top, width, height} = await this.getDimensions();
234-
const relativeX = args[0] === 'center' ? width / 2 : args[0];
235-
const relativeY = args[0] === 'center' ? height / 2 : args[1];
240+
const relativeX = args[0] === 'center' ? width / 2 : args[0] as number;
241+
const relativeY = args[0] === 'center' ? height / 2 : args[1] as number;
236242

237243
// Round the computed click position as decimal pixels are not
238244
// supported by mouse events and could lead to unexpected results.
@@ -241,10 +247,9 @@ export class UnitTestElement implements TestElement {
241247
}
242248

243249
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY, button);
244-
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button);
250+
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button, modifiers);
245251
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, button);
246-
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button);
247-
dispatchMouseEvent(this.element, name, clientX, clientY, button);
248-
await this._stabilize();
252+
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button, modifiers);
253+
dispatchMouseEvent(this.element, name, clientX, clientY, button, modifiers);
249254
}
250255
}

src/cdk/testing/tests/cross-environment.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,20 +319,52 @@ export function crossEnvironmentSpecs(
319319
expect(await counter.text()).toBe('3');
320320
});
321321

322+
it('should be able to click with no modifiers', async () => {
323+
const clickTest = await harness.clickTest();
324+
const modifiersResult = await harness.clickModifiersResult();
325+
326+
await clickTest.click();
327+
expect(await modifiersResult.text()).toBe('---');
328+
});
329+
330+
it('should be able to click with shift and meta modifiers', async () => {
331+
const clickTest = await harness.clickTest();
332+
const modifiersResult = await harness.clickModifiersResult();
333+
334+
await clickTest.click({shift: true, meta: true});
335+
expect(await modifiersResult.text()).toBe('shift---meta');
336+
});
337+
322338
it('should be able to click at a specific position within an element', async () => {
323339
const clickTest = await harness.clickTest();
324340
const clickTestResult = await harness.clickTestResult();
325341
await clickTest.click(10, 10);
326342
expect(await clickTestResult.text()).toBe('10-10');
327343
});
328344

345+
it('should be able to click at a specific position with shift and meta modifiers', async () => {
346+
const clickTest = await harness.clickTest();
347+
const modifiersResult = await harness.clickModifiersResult();
348+
349+
await clickTest.click(10, 10, {shift: true, meta: true});
350+
expect(await modifiersResult.text()).toBe('shift---meta');
351+
});
352+
329353
it('should be able to click the center of an element', async () => {
330354
const clickTest = await harness.clickTest();
331355
const clickTestResult = await harness.clickTestResult();
332356
await clickTest.click('center');
333357
expect(await clickTestResult.text()).toBe('50-50');
334358
});
335359

360+
it('should be able to click the center of an element with shift meta modifiers', async () => {
361+
const clickTest = await harness.clickTest();
362+
const modifiersResult = await harness.clickModifiersResult();
363+
364+
await clickTest.click('center', {shift: true, meta: true});
365+
expect(await modifiersResult.text()).toBe('shift---meta');
366+
});
367+
336368
it('should be able to right click at a specific position within an element', async () => {
337369
const clickTest = await harness.clickTest();
338370
const contextmenuTestResult = await harness.contextmenuTestResult();

src/cdk/testing/tests/harnesses/main-component-harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class MainComponentHarness extends ComponentHarness {
2828
readonly memo = this.locatorFor('textarea');
2929
readonly clickTest = this.locatorFor('.click-test');
3030
readonly clickTestResult = this.locatorFor('.click-test-result');
31+
readonly clickModifiersResult = this.locatorFor('.click-modifiers-test-result');
3132
readonly singleSelect = this.locatorFor('#single-select');
3233
readonly singleSelectValue = this.locatorFor('#single-select-value');
3334
readonly singleSelectChangeEventCounter = this.locatorFor('#single-select-change-counter');

src/cdk/testing/tests/test-main-component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
</div>
55
<div class="click-test-result">{{clickResult.x}}-{{clickResult.y}}</div>
66
<div class="contextmenu-test-result">{{rightClickResult.x}}-{{rightClickResult.y}}-{{rightClickResult.button}}</div>
7+
<div class="click-modifiers-test-result">{{modifiers}}</div>
78
<h1 style="height: 100px; width: 200px;">Main Component</h1>
89
<div id="username">Hello {{username}} from Angular 2!</div>
910
<div class="counters">

src/cdk/testing/tests/test-main-component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export class TestMainComponent implements OnDestroy {
3535
testMethods: string[];
3636
isHovering = false;
3737
specialKey = '';
38+
modifiers: string;
3839
singleSelect: string;
3940
singleSelectChangeEventCount = 0;
4041
multiSelect: string[] = [];
@@ -91,6 +92,9 @@ export class TestMainComponent implements OnDestroy {
9192

9293
onClick(event: MouseEvent) {
9394
this._assignRelativeCoordinates(event, this.clickResult);
95+
96+
this.modifiers = ['Shift', 'Alt', 'Control', 'Meta']
97+
.map(key => event.getModifierState(key) ? key.toLowerCase() : '').join('-');
9498
}
9599

96100
onRightClick(event: MouseEvent) {

0 commit comments

Comments
 (0)