Skip to content

Commit b7cfe67

Browse files
authored
feat(cdk/testing): add the ability to dispatch arbitrary events (#20714)
Allows for arbitrary events to be dispatched through a `TestElement`. This covers the following use cases: 1. Some events can't be simulated easily without a user interaction (e.g. `change` event on an `input`). 2. Allows for custom DOM event handlers to be triggered.
1 parent d49a774 commit b7cfe67

File tree

10 files changed

+41
-3
lines changed

10 files changed

+41
-3
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,19 @@ export class ProtractorElement implements TestElement {
198198
async isFocused(): Promise<boolean> {
199199
return this.element.equals(browser.driver.switchTo().activeElement());
200200
}
201+
202+
async dispatchEvent(name: string): Promise<void> {
203+
return browser.executeScript(_dispatchEvent, name, this.element);
204+
}
205+
}
206+
207+
/**
208+
* Dispatches an event with a particular name and data to an element.
209+
* Note that this needs to be a pure function, because it gets stringified by
210+
* Protractor and is executed inside the browser.
211+
*/
212+
function _dispatchEvent(name: string, element: ElementFinder) {
213+
const event = document.createEvent('Event');
214+
event.initEvent(name);
215+
element.dispatchEvent(event);
201216
}

src/cdk/testing/test-element.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ export interface TestElement {
147147
* @breaking-change 12.0.0 To become a required method.
148148
*/
149149
selectOptions?(...optionIndexes: number[]): Promise<void>;
150+
151+
/**
152+
* Dispatches an event with a particular name.
153+
* @param name Name of the event to be dispatched.
154+
* @breaking-change 12.0.0 To be a required method.
155+
*/
156+
dispatchEvent?(name: string): Promise<void>;
150157
}
151158

152159
export interface TextOptions {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ export class UnitTestElement implements TestElement {
200200
return document.activeElement === this.element;
201201
}
202202

203+
async dispatchEvent(name: string): Promise<void> {
204+
dispatchFakeEvent(this.element, name);
205+
await this._stabilize();
206+
}
207+
203208
/**
204209
* Dispatches a pointer event on the current element if the browser supports it.
205210
* @param name Name of the pointer event to be dispatched.

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,6 @@ export function crossEnvironmentSpecs(
445445
expect(await button.matchesSelector('button:disabled')).toBe(false);
446446
});
447447

448-
449448
it('should get correct text excluding certain selectors', async () => {
450449
const results = await harness.subcomponentAndSpecialHarnesses();
451450
const subHarnessHost = await results[0].host();
@@ -454,6 +453,14 @@ export function crossEnvironmentSpecs(
454453
expect(await subHarnessHost.text({exclude: 'li'})).toBe('List of test tools');
455454
});
456455

456+
it('should dispatch a basic custom event', async () => {
457+
const target = await harness.customEventBasic();
458+
459+
// @breaking-change 12.0.0 Remove non-null assertion once `dispatchEvent` is required.
460+
await target.dispatchEvent!('myCustomEvent');
461+
expect(await target.text()).toBe('Basic event: 1');
462+
});
463+
457464
it('should get TestElements and ComponentHarnesses', async () => {
458465
const results = await harness.subcomponentHarnessesAndElements();
459466
expect(results.length).toBe(5);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export class MainComponentHarness extends ComponentHarness {
9191
readonly deepShadow = this.locatorFor(
9292
'test-shadow-boundary test-sub-shadow-boundary > .in-the-shadows');
9393
readonly hoverTest = this.locatorFor('#hover-box');
94+
readonly customEventBasic = this.locatorFor('#custom-event-basic');
9495

9596
private _testTools = this.locatorFor(SubComponentHarness);
9697

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ <h1 style="height: 100px; width: 200px;">Main Component</h1>
4141
</select>
4242
<div id="multi-select-value">Multi-select: {{multiSelect}}</div>
4343
<div id="multi-select-change-counter">Change events: {{multiSelectChangeEventCount}}</div>
44-
44+
<div (myCustomEvent)="basicEvent = basicEvent + 1" id="custom-event-basic">Basic event: {{basicEvent}}</div>
4545
</div>
4646
<div class="subcomponents">
4747
<test-sub class="test-special" title="test tools" [items]="testTools"></test-sub>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
encapsulation: ViewEncapsulation.None,
2626
changeDetection: ChangeDetectionStrategy.OnPush,
2727
})
28-
2928
export class TestMainComponent implements OnDestroy {
3029
username: string;
3130
counter: number;
@@ -42,6 +41,7 @@ export class TestMainComponent implements OnDestroy {
4241
singleSelectChangeEventCount = 0;
4342
multiSelect: string[] = [];
4443
multiSelectChangeEventCount = 0;
44+
basicEvent = 0;
4545
_shadowDomSupported = _supportsShadowDom();
4646

4747
@ViewChild('clickTestElement') clickTestElement: ElementRef<HTMLElement>;

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export interface TestElement {
135135
click(): Promise<void>;
136136
click(location: 'center'): Promise<void>;
137137
click(relativeX: number, relativeY: number): Promise<void>;
138+
dispatchEvent?(name: string): Promise<void>;
138139
focus(): Promise<void>;
139140
getAttribute(name: string): Promise<string | null>;
140141
getCssValue(property: string): Promise<string>;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export declare class ProtractorElement implements TestElement {
44
blur(): Promise<void>;
55
clear(): Promise<void>;
66
click(...args: [] | ['center'] | [number, number]): Promise<void>;
7+
dispatchEvent(name: string): Promise<void>;
78
focus(): Promise<void>;
89
getAttribute(name: string): Promise<string | null>;
910
getCssValue(property: string): Promise<string>;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export declare class UnitTestElement implements TestElement {
2222
blur(): Promise<void>;
2323
clear(): Promise<void>;
2424
click(...args: [] | ['center'] | [number, number]): Promise<void>;
25+
dispatchEvent(name: string): Promise<void>;
2526
focus(): Promise<void>;
2627
getAttribute(name: string): Promise<string | null>;
2728
getCssValue(property: string): Promise<string>;

0 commit comments

Comments
 (0)