Skip to content

Commit e70e5dd

Browse files
authored
feat(cdk/testing): support modifiers for clicking on a TestElement (#20758)
* feat(cdk/testing): support modifiers for clicking on a TestElement Adds the ability to provide modifier keys when clicking on a TestElement * feat(cdk/testing): update cdk/testing public API Update API golden files for cdk/testing. * feat(cdk/testing): update cdk/testing public API Update API golden files for ProtractorElement and UnitTestElement. * feat(cdk/testing): support modifiers for right clicking on a TestElement Adds the ability to provide modifier keys when right clicking on a TestElement * feat(cdk/testing): update cdk/testing public API Update API golden files * feat(cdk/testing): chore: fix spacing Indentation and space changes based on review. * feat(cdk/testing): re-add stabilize call The call to _stabilize should not be needed since the callers will already do that themselves. Nevertheless it breaks some tests in g3 without it. It needs to be investigated why removing breaks those tests.
1 parent 0a458ce commit e70e5dd

File tree

12 files changed

+138
-42
lines changed

12 files changed

+138
-42
lines changed

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

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,13 @@ 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

88-
async rightClick(...args: [] | ['center'] | [number, number]): Promise<void> {
89+
async rightClick(...args: [ModifierKeys?] | ['center', ModifierKeys?] |
90+
[number, number, ModifierKeys?]): Promise<void> {
8991
await this._dispatchClickEventSequence(args, Button.RIGHT);
9092
}
9193

@@ -202,17 +204,33 @@ export class ProtractorElement implements TestElement {
202204

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

src/cdk/testing/test-element.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,25 +76,27 @@ 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.
88+
* @param modifiers Modifier keys held while clicking
8889
*/
89-
click(relativeX: number, relativeY: number): Promise<void>;
90+
click(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
9091

9192
/**
9293
* Right clicks on the element at the specified coordinates relative to the top-left of it.
9394
* @param relativeX Coordinate within the element, along the X-axis at which to click.
9495
* @param relativeY Coordinate within the element, along the Y-axis at which to click.
96+
* @param modifiers Modifier keys held while clicking
9597
* @breaking-change 11.0.0 To become a required method.
9698
*/
97-
rightClick?(relativeX: number, relativeY: number): Promise<void>;
99+
rightClick?(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
98100

99101
/** Focus the element. */
100102
focus(): Promise<void>;

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: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ 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

88-
async rightClick(...args: [] | ['center'] | [number, number]): Promise<void> {
89+
async rightClick(...args: [ModifierKeys?] | ['center', ModifierKeys?] |
90+
[number, number, ModifierKeys?]): Promise<void> {
8991
await this._dispatchMouseEventSequence('contextmenu', args, 2);
9092
await this._stabilize();
9193
}
@@ -224,15 +226,20 @@ export class UnitTestElement implements TestElement {
224226
/** Dispatches all the events that are part of a mouse event sequence. */
225227
private async _dispatchMouseEventSequence(
226228
name: string,
227-
args: [] | ['center'] | [number, number],
229+
args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?],
228230
button?: number) {
229231
let clientX: number | undefined = undefined;
230232
let clientY: number | undefined = undefined;
233+
let modifiers: ModifierKeys = {};
234+
235+
if (args.length && typeof args[args.length - 1] === 'object') {
236+
modifiers = args.pop() as ModifierKeys;
237+
}
231238

232239
if (args.length) {
233240
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];
241+
const relativeX = args[0] === 'center' ? width / 2 : args[0] as number;
242+
const relativeY = args[0] === 'center' ? height / 2 : args[1] as number;
236243

237244
// Round the computed click position as decimal pixels are not
238245
// supported by mouse events and could lead to unexpected results.
@@ -241,10 +248,14 @@ export class UnitTestElement implements TestElement {
241248
}
242249

243250
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY, button);
244-
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button);
251+
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button, modifiers);
245252
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, button);
246-
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button);
247-
dispatchMouseEvent(this.element, name, clientX, clientY, button);
253+
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button, modifiers);
254+
dispatchMouseEvent(this.element, name, clientX, clientY, button, modifiers);
255+
256+
// This call to _stabilize should not be needed since the callers will already do that them-
257+
// selves. Nevertheless it breaks some tests in g3 without it. It needs to be investigated
258+
// why removing breaks those tests.
248259
await this._stabilize();
249260
}
250261
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,27 +320,66 @@ export function crossEnvironmentSpecs(
320320
expect(await counter.text()).toBe('3');
321321
});
322322

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

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

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

376+
it('should be able to right click with modifiers', async () => {
377+
const clickTest = await harness.clickTest();
378+
const modifiersResult = await harness.clickModifiersResult();
379+
await clickTest.rightClick!(50, 50, {alt: true, control: true});
380+
expect(await modifiersResult.text()).toBe('-alt-control-');
381+
});
382+
344383
it('should be able to send key', async () => {
345384
const input = await harness.input();
346385
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
@@ -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: 7 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,11 +92,17 @@ 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) {
97101
this.rightClickResult.button = event.button;
98102
this._assignRelativeCoordinates(event, this.rightClickResult);
103+
104+
this.modifiers = ['Shift', 'Alt', 'Control', 'Meta']
105+
.map(key => event.getModifierState(key) ? key.toLowerCase() : '').join('-');
99106
}
100107

101108
onCustomEvent(event: any) {

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ export declare function stopHandlingAutoChangeDetectionStatus(): void;
151151
export interface TestElement {
152152
blur(): Promise<void>;
153153
clear(): Promise<void>;
154-
click(): Promise<void>;
155-
click(location: 'center'): Promise<void>;
156-
click(relativeX: number, relativeY: number): Promise<void>;
154+
click(modifiers?: ModifierKeys): Promise<void>;
155+
click(location: 'center', modifiers?: ModifierKeys): Promise<void>;
156+
click(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
157157
dispatchEvent?(name: string, data?: Record<string, EventData>): Promise<void>;
158158
focus(): Promise<void>;
159159
getAttribute(name: string): Promise<string | null>;
@@ -165,7 +165,7 @@ export interface TestElement {
165165
isFocused(): Promise<boolean>;
166166
matchesSelector(selector: string): Promise<boolean>;
167167
mouseAway(): Promise<void>;
168-
rightClick?(relativeX: number, relativeY: number): Promise<void>;
168+
rightClick?(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
169169
selectOptions?(...optionIndexes: number[]): Promise<void>;
170170
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
171171
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;

tools/public_api_guard/cdk/testing/protractor.d.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ export declare class ProtractorElement implements TestElement {
33
constructor(element: ElementFinder);
44
blur(): Promise<void>;
55
clear(): Promise<void>;
6-
click(...args: [] | ['center'] | [number, number]): Promise<void>;
6+
click(...args: [ModifierKeys?] | ['center', ModifierKeys?] | [
7+
number,
8+
number,
9+
ModifierKeys?
10+
]): Promise<void>;
711
dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>;
812
focus(): Promise<void>;
913
getAttribute(name: string): Promise<string | null>;
@@ -15,7 +19,11 @@ export declare class ProtractorElement implements TestElement {
1519
isFocused(): Promise<boolean>;
1620
matchesSelector(selector: string): Promise<boolean>;
1721
mouseAway(): Promise<void>;
18-
rightClick(...args: [] | ['center'] | [number, number]): Promise<void>;
22+
rightClick(...args: [ModifierKeys?] | ['center', ModifierKeys?] | [
23+
number,
24+
number,
25+
ModifierKeys?
26+
]): Promise<void>;
1927
selectOptions(...optionIndexes: number[]): Promise<void>;
2028
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
2129
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;

tools/public_api_guard/cdk/testing/testbed.d.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ export declare class UnitTestElement implements TestElement {
2121
constructor(element: Element, _stabilize: () => Promise<void>);
2222
blur(): Promise<void>;
2323
clear(): Promise<void>;
24-
click(...args: [] | ['center'] | [number, number]): Promise<void>;
24+
click(...args: [ModifierKeys?] | ['center', ModifierKeys?] | [
25+
number,
26+
number,
27+
ModifierKeys?
28+
]): Promise<void>;
2529
dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>;
2630
focus(): Promise<void>;
2731
getAttribute(name: string): Promise<string | null>;
@@ -33,7 +37,11 @@ export declare class UnitTestElement implements TestElement {
3337
isFocused(): Promise<boolean>;
3438
matchesSelector(selector: string): Promise<boolean>;
3539
mouseAway(): Promise<void>;
36-
rightClick(...args: [] | ['center'] | [number, number]): Promise<void>;
40+
rightClick(...args: [ModifierKeys?] | ['center', ModifierKeys?] | [
41+
number,
42+
number,
43+
ModifierKeys?
44+
]): Promise<void>;
3745
selectOptions(...optionIndexes: number[]): Promise<void>;
3846
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
3947
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;

0 commit comments

Comments
 (0)