Skip to content

Commit 522382d

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 7d99c35 commit 522382d

File tree

9 files changed

+88
-25
lines changed

9 files changed

+88
-25
lines changed

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

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,32 @@ export class ProtractorElement implements TestElement {
8080
return this.element.clear();
8181
}
8282

83-
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
83+
async click(...args: [ModifierKeys?] | ['center', ModifierKeys?] |
84+
[number, number, ModifierKeys?]): Promise<void> {
85+
let modifiers: ModifierKeys = {};
86+
if (args.length && typeof args[args.length - 1] === 'object') {
87+
modifiers = args.pop() as ModifierKeys;
88+
}
89+
const modifierKeys = toProtractorModifierKeys(modifiers);
90+
8491
// Omitting the offset argument to mouseMove results in clicking the center.
85-
// This is the default behavior we want, so we use an empty array of offsetArgs if no args are
86-
// passed to this method.
87-
const offsetArgs = args.length === 2 ? [{x: args[0], y: args[1]}] : [];
88-
89-
await browser.actions()
90-
.mouseMove(await this.element.getWebElement(), ...offsetArgs)
91-
.click()
92-
.perform();
92+
// This is the default behavior we want, so we use an empty array of offsetArgs if
93+
// no args remain after popping the modifiers from the args passed to this function.
94+
const offsetArgs = (args.length === 2 ?
95+
[{x: args[0], y: args[1]}] : []) as [{x: number, y: number}];
96+
97+
let actions = browser.actions()
98+
.mouseMove(await this.element.getWebElement(), ...offsetArgs);
99+
100+
for (const modifierKey of modifierKeys) {
101+
actions = actions.keyDown(modifierKey);
102+
}
103+
actions = actions.click();
104+
for (const modifierKey of modifierKeys) {
105+
actions = actions.keyUp(modifierKey);
106+
}
107+
108+
await actions.perform();
93109
}
94110

95111
async focus(): Promise<void> {

src/cdk/testing/test-element.ts

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

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

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

8787
/** Focus the element. */
8888
focus(): Promise<void>;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +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(node: Node, type: string, clientX = 0, clientY = 0): MouseEvent {
51-
return dispatchEvent(node, createMouseEvent(type, clientX, clientY));
50+
export function dispatchMouseEvent(
51+
node: Node, type: string, clientX = 0, clientY = 0, modifiers?: ModifierKeys): MouseEvent {
52+
return dispatchEvent(node, createMouseEvent(type, clientX, clientY, 0, modifiers));
5253
}
5354

5455
/**

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: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,20 @@ export class UnitTestElement implements TestElement {
7777
await this._stabilize();
7878
}
7979

80-
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
80+
async click(...args: [ModifierKeys?] | ['center', ModifierKeys?] |
81+
[number, number, ModifierKeys?]): Promise<void> {
8182
let clientX: number | undefined = undefined;
8283
let clientY: number | undefined = undefined;
84+
let modifiers: ModifierKeys = {};
85+
86+
if (args.length && typeof args[args.length - 1] === 'object') {
87+
modifiers = args.pop() as ModifierKeys;
88+
}
89+
8390
if (args.length) {
8491
const {left, top, width, height} = await this.getDimensions();
85-
const relativeX = args[0] === 'center' ? width / 2 : args[0];
86-
const relativeY = args[0] === 'center' ? height / 2 : args[1];
92+
const relativeX = args[0] === 'center' ? width / 2 : args[0] as number;
93+
const relativeY = args[0] === 'center' ? height / 2 : args[1] as number;
8794

8895
// Round the computed click position as decimal pixels are not
8996
// supported by mouse events and could lead to unexpected results.
@@ -92,10 +99,10 @@ export class UnitTestElement implements TestElement {
9299
}
93100

94101
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY);
95-
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY);
102+
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, modifiers);
96103
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY);
97-
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY);
98-
dispatchMouseEvent(this.element, 'click', clientX, clientY);
104+
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, modifiers);
105+
dispatchMouseEvent(this.element, 'click', clientX, clientY, modifiers);
99106
await this._stabilize();
100107
}
101108

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 send key', async () => {
337369
const input = await harness.input();
338370
const value = await harness.value();

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
@@ -3,6 +3,7 @@
33
#clickTestElement>
44
</div>
55
<div class="click-test-result">{{relativeX}}-{{relativeY}}</div>
6+
<div class="click-modifiers-test-result">{{modifiers}}</div>
67
<h1 style="height: 100px; width: 200px;">Main Component</h1>
78
<div id="username">Hello {{username}} from Angular 2!</div>
89
<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
@@ -37,6 +37,7 @@ export class TestMainComponent implements OnDestroy {
3737
specialKey = '';
3838
relativeX = 0;
3939
relativeY = 0;
40+
modifiers: string;
4041
singleSelect: string;
4142
singleSelectChangeEventCount = 0;
4243
multiSelect: string[] = [];
@@ -92,6 +93,9 @@ export class TestMainComponent implements OnDestroy {
9293
const {top, left} = this.clickTestElement.nativeElement.getBoundingClientRect();
9394
this.relativeX = Math.round(event.clientX - left);
9495
this.relativeY = Math.round(event.clientY - top);
96+
97+
this.modifiers = ['Shift', 'Alt', 'Control', 'Meta']
98+
.map(key => event.getModifierState(key) ? key.toLowerCase() : '').join('-');
9599
}
96100

97101
runTaskOutsideZone() {

0 commit comments

Comments
 (0)