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