diff --git a/public/docs/_examples/testing/ts/app/app.component.router.spec.ts b/public/docs/_examples/testing/ts/app/app.component.router.spec.ts
index 36e34a983e..db0effd427 100644
--- a/public/docs/_examples/testing/ts/app/app.component.router.spec.ts
+++ b/public/docs/_examples/testing/ts/app/app.component.router.spec.ts
@@ -7,9 +7,7 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick,
import { RouterTestingModule } from '@angular/router/testing';
import { SpyLocation } from '@angular/common/testing';
-// tslint:disable:no-unused-variable
-import { newEvent } from '../testing';
-// tslint:enable:no-unused-variable
+import { click } from '../testing';
// r - for relatively obscure router symbols
import * as r from '@angular/router';
@@ -48,9 +46,8 @@ describe('AppComponent & RouterTestingModule', () => {
it('should navigate to "About" on click', fakeAsync(() => {
createComponent();
- // page.aboutLinkDe.triggerEventHandler('click', null); // fails
- // page.aboutLinkDe.nativeElement.dispatchEvent(newEvent('click')); // fails
- page.aboutLinkDe.nativeElement.click(); // fails in phantom
+ click(page.aboutLinkDe);
+ // page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom
advance();
expectPathToBe('/about');
diff --git a/public/docs/_examples/testing/ts/app/bag/bag.spec.ts b/public/docs/_examples/testing/ts/app/bag/bag.spec.ts
index 1fede16bd7..d67bf66fd0 100644
--- a/public/docs/_examples/testing/ts/app/bag/bag.spec.ts
+++ b/public/docs/_examples/testing/ts/app/bag/bag.spec.ts
@@ -26,7 +26,7 @@ import { NgModel, NgControl } from '@angular/forms';
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick
} from '@angular/core/testing';
-import { addMatchers, newEvent } from '../../testing';
+import { addMatchers, newEvent, click } from '../../testing';
beforeEach( addMatchers );
@@ -180,7 +180,7 @@ describe('TestBed Component Tests', () => {
const comp = fixture.componentInstance;
const hero = comp.heroes[0];
- heroes[0].triggerEventHandler('click', null);
+ click(heroes[0]);
fixture.detectChanges();
const selected = fixture.debugElement.query(By.css('p'));
@@ -213,7 +213,7 @@ describe('TestBed Component Tests', () => {
fixture.detectChanges();
expect(span.textContent).toMatch(/is off/i, 'before click');
- btn.triggerEventHandler('click', null);
+ click(btn);
fixture.detectChanges();
expect(span.textContent).toMatch(/is on/i, 'after click');
});
@@ -610,7 +610,7 @@ describe('Lifecycle hooks w/ MyIfParentComp', () => {
getChild();
const btn = fixture.debugElement.query(By.css('button'));
- btn.triggerEventHandler('click', null);
+ click(btn);
fixture.detectChanges();
expect(child.ngOnDestroyCalled).toBe(true);
diff --git a/public/docs/_examples/testing/ts/app/dashboard/dashboard-hero.component.spec.ts b/public/docs/_examples/testing/ts/app/dashboard/dashboard-hero.component.spec.ts
index 86e0a88d2e..0ae563a831 100644
--- a/public/docs/_examples/testing/ts/app/dashboard/dashboard-hero.component.spec.ts
+++ b/public/docs/_examples/testing/ts/app/dashboard/dashboard-hero.component.spec.ts
@@ -4,7 +4,7 @@ import { async, ComponentFixture, TestBed
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
-import { addMatchers } from '../../testing';
+import { addMatchers, click } from '../../testing';
import { Hero } from '../model/hero';
import { DashboardHeroComponent } from './dashboard-hero.component';
@@ -53,10 +53,22 @@ describe('DashboardHeroComponent when tested directly', () => {
let selectedHero: Hero;
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
+ // #docregion trigger-event-handler
heroEl.triggerEventHandler('click', null);
+ // #enddocregion trigger-event-handler
expect(selectedHero).toBe(expectedHero);
});
// #enddocregion click-test
+
+ // #docregion click-test-2
+ it('should raise selected event when clicked', () => {
+ let selectedHero: Hero;
+ comp.selected.subscribe((hero: Hero) => selectedHero = hero);
+
+ click(heroEl); // triggerEventHandler helper
+ expect(selectedHero).toBe(expectedHero);
+ });
+ // #enddocregion click-test-2
});
//////////////////
@@ -89,7 +101,7 @@ describe('DashboardHeroComponent when inside a test host', () => {
});
it('should raise selected event when clicked', () => {
- heroEl.triggerEventHandler('click', null);
+ click(heroEl);
// selected hero should be the same data bound hero
expect(testHost.selectedHero).toBe(testHost.hero);
});
@@ -102,8 +114,7 @@ import { Component } from '@angular/core';
// #docregion test-host
@Component({
template: `
-
- `
+ `
})
class TestHostComponent {
hero = new Hero(42, 'Test Name');
diff --git a/public/docs/_examples/testing/ts/app/dashboard/dashboard.component.spec.ts b/public/docs/_examples/testing/ts/app/dashboard/dashboard.component.spec.ts
index f783899143..0b0f9e213a 100644
--- a/public/docs/_examples/testing/ts/app/dashboard/dashboard.component.spec.ts
+++ b/public/docs/_examples/testing/ts/app/dashboard/dashboard.component.spec.ts
@@ -2,9 +2,9 @@
import { async, inject, ComponentFixture, TestBed
} from '@angular/core/testing';
-import { addMatchers } from '../../testing';
-import { HeroService } from '../model';
-import { FakeHeroService } from '../model/testing';
+import { addMatchers, click } from '../../testing';
+import { HeroService } from '../model';
+import { FakeHeroService } from '../model/testing';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
@@ -39,7 +39,7 @@ describe('DashboardComponent (deep)', () => {
function clickForDeep() {
// get first
DebugElement
const heroEl = fixture.debugElement.query(By.css('.hero'));
- heroEl.triggerEventHandler('click', null);
+ click(heroEl);
}
});
diff --git a/public/docs/_examples/testing/ts/app/hero/hero-detail.component.spec.ts b/public/docs/_examples/testing/ts/app/hero/hero-detail.component.spec.ts
index 68f8d17582..03eb393c33 100644
--- a/public/docs/_examples/testing/ts/app/hero/hero-detail.component.spec.ts
+++ b/public/docs/_examples/testing/ts/app/hero/hero-detail.component.spec.ts
@@ -7,7 +7,7 @@ import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import {
- ActivatedRoute, ActivatedRouteStub, newEvent, Router, RouterStub
+ ActivatedRoute, ActivatedRouteStub, click, newEvent, Router, RouterStub
} from '../../testing';
import { Hero } from '../model';
@@ -103,7 +103,7 @@ function overrideSetup() {
expect(comp.hero.name).toBe(newName, 'component hero has new name');
expect(hds.testHero.name).toBe(origName, 'service hero unchanged before save');
- page.saveBtn.triggerEventHandler('click', null);
+ click(page.saveBtn);
tick(); // wait for async save to complete
expect(hds.testHero.name).toBe(newName, 'service hero has new name after save');
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
@@ -159,18 +159,18 @@ function heroModuleSetup() {
// #enddocregion route-good-id
it('should navigate when click cancel', () => {
- page.cancelBtn.triggerEventHandler('click', null);
+ click(page.cancelBtn);
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
});
it('should save when click save but not navigate immediately', () => {
- page.saveBtn.triggerEventHandler('click', null);
+ click(page.saveBtn);
expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
});
it('should navigate when click save and save resolves', fakeAsync(() => {
- page.saveBtn.triggerEventHandler('click', null);
+ click(page.saveBtn);
tick(); // wait for async save to complete
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
}));
diff --git a/public/docs/_examples/testing/ts/testing/index.ts b/public/docs/_examples/testing/ts/testing/index.ts
index 907b968c0c..e3de5164ca 100644
--- a/public/docs/_examples/testing/ts/testing/index.ts
+++ b/public/docs/_examples/testing/ts/testing/index.ts
@@ -1,9 +1,17 @@
+import { DebugElement } from '@angular/core';
import { tick, ComponentFixture } from '@angular/core/testing';
export * from './jasmine-matchers';
export * from './router-stubs';
-// Short utilities
+///// Short utilities /////
+
+/** Wait a tick, then detect changes */
+export function advance(f: ComponentFixture
): void {
+ tick();
+ f.detectChanges();
+}
+
/**
* Create custom DOM event the old fashioned way
*
@@ -16,8 +24,20 @@ export function newEvent(eventName: string, bubbles = false, cancelable = false)
return evt;
}
-/** Wait a tick, then detect changes */
-export function advance(f: ComponentFixture): void {
- tick();
- f.detectChanges();
+// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
+// #docregion click-event
+/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
+export const ButtonClickEvents = {
+ left: { button: 0 },
+ right: { button: 2 }
+};
+
+/** Simulate element click. Defaults to mouse left-button click event. */
+export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void {
+ if (el instanceof HTMLElement) {
+ el.click();
+ } else {
+ el.triggerEventHandler('click', eventObj);
+ }
}
+// #enddocregion click-event
diff --git a/public/docs/ts/latest/guide/testing.jade b/public/docs/ts/latest/guide/testing.jade
index ac38744aa6..54f61f369f 100644
--- a/public/docs/ts/latest/guide/testing.jade
+++ b/public/docs/ts/latest/guide/testing.jade
@@ -43,7 +43,7 @@ block includes
- [_async_](#async-in-before-each) in `beforeEach`
- [_compileComponents_](#compile-components)
1. [Test a component with inputs and outputs](#component-with-inputs-output)
-
+ - [_triggerEventHandler_](#trigger-event-handler)
1. [Test a component inside a test host component](#component-inside-test-host)
1. [Test a routed component](#routed-component)
@@ -965,14 +965,51 @@ a(href="#top").to-top Back to top
:marked
The component exposes an `EventEmitter` property. The test subscribes to it just as the host component would do.
- The Angular `DebugElement.triggerEventHandler` lets the test raise _any data-bound event_.
- In this example, the component's template binds to the hero ``.
-
- The test has a reference to that `
` in `heroEl` so triggering the `heroEl` click event should cause Angular
- to call `DashboardHeroComponent.click`.
+ The `heroEl` is a `DebugElement` that represents the hero `
`.
+ The test calls `triggerEventHandler` with the "click" event name.
+ The "click" event binding responds by calling `DashboardHeroComponent.click()`.
- If the component behaves as expected, its `selected` property should emit the `hero` object,
- the test detects that emission through its subscription, and the test will pass.
+ If the component behaves as expected, `click()` tells the component's `selected` property to emit the `hero` object,
+ the test detects that value through its subscription to `selected`, and the test should pass.
+
+#trigger-event-handler
+:marked
+ ### _triggerEventHandler_
+
+ The Angular `DebugElement.triggerEventHandler` can raise _any data-bound event_ by its _event name_.
+ The second parameter is the event object passed to the handler.
+
+ In this example, the test triggers a "click" event with a null event object.
+
++makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'trigger-event-handler')(format='.')
+:marked
+ The test assumes (correctly in this case) that the runtime event handler — the component's `click()` method —
+ doesn't care about the event object.
+
+ Other handlers will be less forgiving.
+ For example, the `RouterLink` directive expects an object with a `button` property indicating the mouse button that was pressed.
+ The directive throws an error if the event object doesn't do this correctly.
+
+#click-helper
+:marked
+ Clicking a button, an anchor, or an arbitrary HTML element is a common test task.
+ Make that easy by encapsulating the _click-triggering_ process in a helper such as the `click` function below:
++makeExample('testing/ts/testing/index.ts', 'click-event', 'testing/index.ts (click helper)')(format='.')
+:marked
+ 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)
+
left-button mouse event object
+ accepted by many handlers including the `RouterLink` directive.
+
+.callout.is-critical
+ header click() is not an ATP function
+ :marked
+ The `click()` helper function is **not** part of the _Angular Testing Platform_.
+ It's a function defined in _this chapter's sample code_ and used by all of the sample tests.
+ If you like it, add it to your own collection of helpers.
+:marked
+ Here's the previous test, rewritten using this click helper.
++makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'click-test-2', 'app/dashboard/dashboard-hero.component.spec.ts (click test revised)')(format='.')
+
.l-hr
@@ -2135,8 +2172,10 @@ table
:marked
Triggers the event by its name if there is a corresponding listener
in the element's `listeners` collection.
-
- If the event lacks a listner or there's some other problem,
+ The second parameter is the _event object_ expected by the handler.
+ See [above](#trigger-event-handler).
+
+ If the event lacks a listener or there's some other problem,
consider calling `nativeElement.dispatchEvent(eventObject)`
tr