Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit 1c87bd6

Browse files
authored
docs(testing): explain DebugElement.triggerEventHandler (#2438)
1 parent 8870f30 commit 1c87bd6

File tree

7 files changed

+104
-37
lines changed

7 files changed

+104
-37
lines changed

public/docs/_examples/testing/ts/app/app.component.router.spec.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick,
77
import { RouterTestingModule } from '@angular/router/testing';
88
import { SpyLocation } from '@angular/common/testing';
99

10-
// tslint:disable:no-unused-variable
11-
import { newEvent } from '../testing';
12-
// tslint:enable:no-unused-variable
10+
import { click } from '../testing';
1311

1412
// r - for relatively obscure router symbols
1513
import * as r from '@angular/router';
@@ -48,9 +46,8 @@ describe('AppComponent & RouterTestingModule', () => {
4846

4947
it('should navigate to "About" on click', fakeAsync(() => {
5048
createComponent();
51-
// page.aboutLinkDe.triggerEventHandler('click', null); // fails
52-
// page.aboutLinkDe.nativeElement.dispatchEvent(newEvent('click')); // fails
53-
page.aboutLinkDe.nativeElement.click(); // fails in phantom
49+
click(page.aboutLinkDe);
50+
// page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom
5451

5552
advance();
5653
expectPathToBe('/about');

public/docs/_examples/testing/ts/app/bag/bag.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { NgModel, NgControl } from '@angular/forms';
2626
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick
2727
} from '@angular/core/testing';
2828

29-
import { addMatchers, newEvent } from '../../testing';
29+
import { addMatchers, newEvent, click } from '../../testing';
3030

3131
beforeEach( addMatchers );
3232

@@ -180,7 +180,7 @@ describe('TestBed Component Tests', () => {
180180
const comp = fixture.componentInstance;
181181
const hero = comp.heroes[0];
182182

183-
heroes[0].triggerEventHandler('click', null);
183+
click(heroes[0]);
184184
fixture.detectChanges();
185185

186186
const selected = fixture.debugElement.query(By.css('p'));
@@ -213,7 +213,7 @@ describe('TestBed Component Tests', () => {
213213
fixture.detectChanges();
214214
expect(span.textContent).toMatch(/is off/i, 'before click');
215215

216-
btn.triggerEventHandler('click', null);
216+
click(btn);
217217
fixture.detectChanges();
218218
expect(span.textContent).toMatch(/is on/i, 'after click');
219219
});
@@ -610,7 +610,7 @@ describe('Lifecycle hooks w/ MyIfParentComp', () => {
610610
getChild();
611611

612612
const btn = fixture.debugElement.query(By.css('button'));
613-
btn.triggerEventHandler('click', null);
613+
click(btn);
614614

615615
fixture.detectChanges();
616616
expect(child.ngOnDestroyCalled).toBe(true);

public/docs/_examples/testing/ts/app/dashboard/dashboard-hero.component.spec.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { async, ComponentFixture, TestBed
44
import { By } from '@angular/platform-browser';
55
import { DebugElement } from '@angular/core';
66

7-
import { addMatchers } from '../../testing';
7+
import { addMatchers, click } from '../../testing';
88

99
import { Hero } from '../model/hero';
1010
import { DashboardHeroComponent } from './dashboard-hero.component';
@@ -53,10 +53,22 @@ describe('DashboardHeroComponent when tested directly', () => {
5353
let selectedHero: Hero;
5454
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
5555

56+
// #docregion trigger-event-handler
5657
heroEl.triggerEventHandler('click', null);
58+
// #enddocregion trigger-event-handler
5759
expect(selectedHero).toBe(expectedHero);
5860
});
5961
// #enddocregion click-test
62+
63+
// #docregion click-test-2
64+
it('should raise selected event when clicked', () => {
65+
let selectedHero: Hero;
66+
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
67+
68+
click(heroEl); // triggerEventHandler helper
69+
expect(selectedHero).toBe(expectedHero);
70+
});
71+
// #enddocregion click-test-2
6072
});
6173

6274
//////////////////
@@ -89,7 +101,7 @@ describe('DashboardHeroComponent when inside a test host', () => {
89101
});
90102

91103
it('should raise selected event when clicked', () => {
92-
heroEl.triggerEventHandler('click', null);
104+
click(heroEl);
93105
// selected hero should be the same data bound hero
94106
expect(testHost.selectedHero).toBe(testHost.hero);
95107
});
@@ -102,8 +114,7 @@ import { Component } from '@angular/core';
102114
// #docregion test-host
103115
@Component({
104116
template: `
105-
<dashboard-hero [hero]="hero" (selected)="onSelected($event)">
106-
</dashboard-hero>`
117+
<dashboard-hero [hero]="hero" (selected)="onSelected($event)"></dashboard-hero>`
107118
})
108119
class TestHostComponent {
109120
hero = new Hero(42, 'Test Name');

public/docs/_examples/testing/ts/app/dashboard/dashboard.component.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import { async, inject, ComponentFixture, TestBed
33
} from '@angular/core/testing';
44

5-
import { addMatchers } from '../../testing';
6-
import { HeroService } from '../model';
7-
import { FakeHeroService } from '../model/testing';
5+
import { addMatchers, click } from '../../testing';
6+
import { HeroService } from '../model';
7+
import { FakeHeroService } from '../model/testing';
88

99
import { By } from '@angular/platform-browser';
1010
import { Router } from '@angular/router';
@@ -39,7 +39,7 @@ describe('DashboardComponent (deep)', () => {
3939
function clickForDeep() {
4040
// get first <div class="hero"> DebugElement
4141
const heroEl = fixture.debugElement.query(By.css('.hero'));
42-
heroEl.triggerEventHandler('click', null);
42+
click(heroEl);
4343
}
4444
});
4545

public/docs/_examples/testing/ts/app/hero/hero-detail.component.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { By } from '@angular/platform-browser';
77
import { DebugElement } from '@angular/core';
88

99
import {
10-
ActivatedRoute, ActivatedRouteStub, newEvent, Router, RouterStub
10+
ActivatedRoute, ActivatedRouteStub, click, newEvent, Router, RouterStub
1111
} from '../../testing';
1212

1313
import { Hero } from '../model';
@@ -103,7 +103,7 @@ function overrideSetup() {
103103
expect(comp.hero.name).toBe(newName, 'component hero has new name');
104104
expect(hds.testHero.name).toBe(origName, 'service hero unchanged before save');
105105

106-
page.saveBtn.triggerEventHandler('click', null);
106+
click(page.saveBtn);
107107
tick(); // wait for async save to complete
108108
expect(hds.testHero.name).toBe(newName, 'service hero has new name after save');
109109
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
@@ -159,18 +159,18 @@ function heroModuleSetup() {
159159
// #enddocregion route-good-id
160160

161161
it('should navigate when click cancel', () => {
162-
page.cancelBtn.triggerEventHandler('click', null);
162+
click(page.cancelBtn);
163163
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
164164
});
165165

166166
it('should save when click save but not navigate immediately', () => {
167-
page.saveBtn.triggerEventHandler('click', null);
167+
click(page.saveBtn);
168168
expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
169169
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
170170
});
171171

172172
it('should navigate when click save and save resolves', fakeAsync(() => {
173-
page.saveBtn.triggerEventHandler('click', null);
173+
click(page.saveBtn);
174174
tick(); // wait for async save to complete
175175
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
176176
}));
Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import { DebugElement } from '@angular/core';
12
import { tick, ComponentFixture } from '@angular/core/testing';
23

34
export * from './jasmine-matchers';
45
export * from './router-stubs';
56

6-
// Short utilities
7+
///// Short utilities /////
8+
9+
/** Wait a tick, then detect changes */
10+
export function advance(f: ComponentFixture<any>): void {
11+
tick();
12+
f.detectChanges();
13+
}
14+
715
/**
816
* Create custom DOM event the old fashioned way
917
*
@@ -16,8 +24,20 @@ export function newEvent(eventName: string, bubbles = false, cancelable = false)
1624
return evt;
1725
}
1826

19-
/** Wait a tick, then detect changes */
20-
export function advance(f: ComponentFixture<any>): void {
21-
tick();
22-
f.detectChanges();
27+
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
28+
// #docregion click-event
29+
/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
30+
export const ButtonClickEvents = {
31+
left: { button: 0 },
32+
right: { button: 2 }
33+
};
34+
35+
/** Simulate element click. Defaults to mouse left-button click event. */
36+
export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void {
37+
if (el instanceof HTMLElement) {
38+
el.click();
39+
} else {
40+
el.triggerEventHandler('click', eventObj);
41+
}
2342
}
43+
// #enddocregion click-event

public/docs/ts/latest/guide/testing.jade

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ block includes
4343
- [_async_](#async-in-before-each) in `beforeEach`
4444
- [_compileComponents_](#compile-components)
4545
1. [Test a component with inputs and outputs](#component-with-inputs-output)
46-
<br><br>
46+
- [_triggerEventHandler_](#trigger-event-handler)
4747
1. [Test a component inside a test host component](#component-inside-test-host)
4848
<br><br>
4949
1. [Test a routed component](#routed-component)
@@ -965,14 +965,51 @@ a(href="#top").to-top Back to top
965965
:marked
966966
The component exposes an `EventEmitter` property. The test subscribes to it just as the host component would do.
967967

968-
The Angular `DebugElement.triggerEventHandler` lets the test raise _any data-bound event_.
969-
In this example, the component's template binds to the hero `<div>`.
968+
The `heroEl` is a `DebugElement` that represents the hero `<div>`.
969+
The test calls `triggerEventHandler` with the "click" event name.
970+
The "click" event binding responds by calling `DashboardHeroComponent.click()`.
971+
972+
If the component behaves as expected, `click()` tells the component's `selected` property to emit the `hero` object,
973+
the test detects that value through its subscription to `selected`, and the test should pass.
974+
975+
#trigger-event-handler
976+
:marked
977+
### _triggerEventHandler_
978+
979+
The Angular `DebugElement.triggerEventHandler` can raise _any data-bound event_ by its _event name_.
980+
The second parameter is the event object passed to the handler.
981+
982+
In this example, the test triggers a "click" event with a null event object.
970983

971-
The test has a reference to that `<div>` in `heroEl` so triggering the `heroEl` click event should cause Angular
972-
to call `DashboardHeroComponent.click`.
984+
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'trigger-event-handler')(format='.')
985+
:marked
986+
The test assumes (correctly in this case) that the runtime event handler &mdash; the component's `click()` method &mdash;
987+
doesn't care about the event object.
973988

974-
If the component behaves as expected, its `selected` property should emit the `hero` object,
975-
the test detects that emission through its subscription, and the test will pass.
989+
Other handlers will be less forgiving.
990+
For example, the `RouterLink` directive expects an object with a `button` property indicating the mouse button that was pressed.
991+
The directive throws an error if the event object doesn't do this correctly.
992+
993+
#click-helper
994+
:marked
995+
Clicking a button, an anchor, or an arbitrary HTML element is a common test task.
996+
Make that easy by encapsulating the _click-triggering_ process in a helper such as the `click` function below:
997+
+makeExample('testing/ts/testing/index.ts', 'click-event', 'testing/index.ts (click helper)')(format='.')
998+
:marked
999+
The first parameter is the _element-to-click_. You can pass a custom event object as the second parameter if you wish. The default is a (partial)
1000+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button" target="_blank">left-button mouse event object</a>
1001+
accepted by many handlers including the `RouterLink` directive.
1002+
1003+
.callout.is-critical
1004+
header click() is not an ATP function
1005+
:marked
1006+
The `click()` helper function is **not** part of the _Angular Testing Platform_.
1007+
It's a function defined in _this chapter's sample code_ and used by all of the sample tests.
1008+
If you like it, add it to your own collection of helpers.
1009+
:marked
1010+
Here's the previous test, rewritten using this click helper.
1011+
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'click-test-2', 'app/dashboard/dashboard-hero.component.spec.ts (click test revised)')(format='.')
1012+
9761013

9771014
.l-hr
9781015

@@ -2135,8 +2172,10 @@ table
21352172
:marked
21362173
Triggers the event by its name if there is a corresponding listener
21372174
in the element's `listeners` collection.
2138-
2139-
If the event lacks a listner or there's some other problem,
2175+
The second parameter is the _event object_ expected by the handler.
2176+
See [above](#trigger-event-handler).
2177+
2178+
If the event lacks a listener or there's some other problem,
21402179
consider calling `nativeElement.dispatchEvent(eventObject)`
21412180

21422181
tr

0 commit comments

Comments
 (0)