Skip to content

Commit 92d95fe

Browse files
committed
feat(cdk/testing): allow for data to be attached to custm event
Allows for data to be attached to an event that is triggered through `TestElement.dispatchEvent`. This is required in order to simulate some events like `animationend`. It is limited to primitive data types, because the data has to be JSON-serializeable for Protractor.
1 parent 7d99c35 commit 92d95fe

File tree

10 files changed

+51
-11
lines changed

10 files changed

+51
-11
lines changed

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
ModifierKeys,
1313
TestElement,
1414
TestKey,
15-
TextOptions
15+
TextOptions,
16+
EventData,
1617
} from '@angular/cdk/testing';
1718
import {browser, by, ElementFinder, Key} from 'protractor';
1819

@@ -199,8 +200,8 @@ export class ProtractorElement implements TestElement {
199200
return this.element.equals(browser.driver.switchTo().activeElement());
200201
}
201202

202-
async dispatchEvent(name: string): Promise<void> {
203-
return browser.executeScript(_dispatchEvent, name, this.element);
203+
async dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void> {
204+
return browser.executeScript(_dispatchEvent, name, this.element, data);
204205
}
205206
}
206207

@@ -209,9 +210,15 @@ export class ProtractorElement implements TestElement {
209210
* Note that this needs to be a pure function, because it gets stringified by
210211
* Protractor and is executed inside the browser.
211212
*/
212-
function _dispatchEvent(name: string, element: ElementFinder) {
213+
function _dispatchEvent(name: string, element: ElementFinder, data?: Record<string, EventData>) {
213214
const event = document.createEvent('Event');
214215
event.initEvent(name);
216+
217+
if (data) {
218+
// tslint:disable-next-line:ban Have to use `Object.assign` to preserve the original object.
219+
Object.assign(event, data);
220+
}
221+
215222
// This type has a string index signature, so we cannot access it using a dotted property access.
216223
element['dispatchEvent'](event);
217224
}

src/cdk/testing/test-element.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export interface ModifierKeys {
1616
meta?: boolean;
1717
}
1818

19+
/** Data that can be attached to a custom event dispatched from a `TestElement`. */
20+
export type EventData =
21+
string | number | boolean | undefined | null | EventData[] | {[key: string]: EventData};
22+
1923
/** An enum of non-text keys that can be used with the `sendKeys` method. */
2024
// NOTE: This is a separate enum from `@angular/cdk/keycodes` because we don't necessarily want to
2125
// support every possible keyCode. We also can't rely on Protractor's `Key` because we don't want a
@@ -153,7 +157,7 @@ export interface TestElement {
153157
* @param name Name of the event to be dispatched.
154158
* @breaking-change 12.0.0 To be a required method.
155159
*/
156-
dispatchEvent?(name: string): Promise<void>;
160+
dispatchEvent?(name: string, data?: Record<string, EventData>): Promise<void>;
157161
}
158162

159163
export interface TextOptions {

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ import {
1313
ModifierKeys,
1414
TestElement,
1515
TestKey,
16-
TextOptions
16+
TextOptions,
17+
EventData,
1718
} from '@angular/cdk/testing';
1819
import {
1920
clearElement,
21+
createFakeEvent,
2022
dispatchFakeEvent,
2123
dispatchMouseEvent,
2224
dispatchPointerEvent,
2325
isTextInput,
2426
triggerBlur,
2527
triggerFocus,
2628
typeInElement,
29+
dispatchEvent,
2730
} from './fake-events';
2831

2932
/** Maps `TestKey` constants to the `keyCode` and `key` values used by native browser events. */
@@ -200,8 +203,15 @@ export class UnitTestElement implements TestElement {
200203
return document.activeElement === this.element;
201204
}
202205

203-
async dispatchEvent(name: string): Promise<void> {
204-
dispatchFakeEvent(this.element, name);
206+
async dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void> {
207+
const event = createFakeEvent(name);
208+
209+
if (data) {
210+
// tslint:disable-next-line:ban Have to use `Object.assign` to preserve the original object.
211+
Object.assign(event, data);
212+
}
213+
214+
dispatchEvent(this.element, event);
205215
await this._stabilize();
206216
}
207217

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,14 @@ export function crossEnvironmentSpecs(
461461
expect(await target.text()).toBe('Basic event: 1');
462462
});
463463

464+
it('should dispatch a custom event with attached data', async () => {
465+
const target = await harness.customEventObject();
466+
467+
// @breaking-change 12.0.0 Remove non-null assertion once `dispatchEvent` is required.
468+
await target.dispatchEvent!('myCustomEvent', {message: 'Hello', value: 1337});
469+
expect(await target.text()).toBe('Event with object: {"message":"Hello","value":1337}');
470+
});
471+
464472
it('should get TestElements and ComponentHarnesses', async () => {
465473
const results = await harness.subcomponentHarnessesAndElements();
466474
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
@@ -92,6 +92,7 @@ export class MainComponentHarness extends ComponentHarness {
9292
'test-shadow-boundary test-sub-shadow-boundary > .in-the-shadows');
9393
readonly hoverTest = this.locatorFor('#hover-box');
9494
readonly customEventBasic = this.locatorFor('#custom-event-basic');
95+
readonly customEventObject = this.locatorFor('#custom-event-object');
9596

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ <h1 style="height: 100px; width: 200px;">Main Component</h1>
4242
<div id="multi-select-value">Multi-select: {{multiSelect}}</div>
4343
<div id="multi-select-change-counter">Change events: {{multiSelectChangeEventCount}}</div>
4444
<div (myCustomEvent)="basicEvent = basicEvent + 1" id="custom-event-basic">Basic event: {{basicEvent}}</div>
45+
<div (myCustomEvent)="onCustomEvent($event)" id="custom-event-object">Event with object: {{customEventData}}</div>
4546
</div>
4647
<div class="subcomponents">
4748
<test-sub class="test-special" title="test tools" [items]="testTools"></test-sub>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class TestMainComponent implements OnDestroy {
4242
multiSelect: string[] = [];
4343
multiSelectChangeEventCount = 0;
4444
basicEvent = 0;
45+
customEventData: string | null = null;
4546
_shadowDomSupported = _supportsShadowDom();
4647

4748
@ViewChild('clickTestElement') clickTestElement: ElementRef<HTMLElement>;
@@ -94,6 +95,10 @@ export class TestMainComponent implements OnDestroy {
9495
this.relativeY = Math.round(event.clientY - top);
9596
}
9697

98+
onCustomEvent(event: any) {
99+
this.customEventData = JSON.stringify({message: event.message, value: event.value});
100+
}
101+
97102
runTaskOutsideZone() {
98103
this._zone.runOutsideAngular(() => setTimeout(() => {
99104
this.taskStateResultElement.nativeElement.textContent = 'result';

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export interface ElementDimensions {
4848
width: number;
4949
}
5050

51+
export declare type EventData = string | number | boolean | undefined | null | EventData[] | {
52+
[key: string]: EventData;
53+
};
54+
5155
export declare function handleAutoChangeDetectionStatus(handler: (status: AutoChangeDetectionStatus) => void): void;
5256

5357
export declare abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFactory {
@@ -135,7 +139,7 @@ export interface TestElement {
135139
click(): Promise<void>;
136140
click(location: 'center'): Promise<void>;
137141
click(relativeX: number, relativeY: number): Promise<void>;
138-
dispatchEvent?(name: string): Promise<void>;
142+
dispatchEvent?(name: string, data?: Record<string, EventData>): Promise<void>;
139143
focus(): Promise<void>;
140144
getAttribute(name: string): Promise<string | null>;
141145
getCssValue(property: string): Promise<string>;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +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>;
7+
dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>;
88
focus(): Promise<void>;
99
getAttribute(name: string): Promise<string | null>;
1010
getCssValue(property: string): Promise<string>;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +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>;
25+
dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>;
2626
focus(): Promise<void>;
2727
getAttribute(name: string): Promise<string | null>;
2828
getCssValue(property: string): Promise<string>;

0 commit comments

Comments
 (0)