docs(testing): explain DebugElement.triggerEventHandler (#2438)
This commit is contained in:
parent
8870f30a3d
commit
1c87bd67d8
|
@ -7,9 +7,7 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick,
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { SpyLocation } from '@angular/common/testing';
|
import { SpyLocation } from '@angular/common/testing';
|
||||||
|
|
||||||
// tslint:disable:no-unused-variable
|
import { click } from '../testing';
|
||||||
import { newEvent } from '../testing';
|
|
||||||
// tslint:enable:no-unused-variable
|
|
||||||
|
|
||||||
// r - for relatively obscure router symbols
|
// r - for relatively obscure router symbols
|
||||||
import * as r from '@angular/router';
|
import * as r from '@angular/router';
|
||||||
|
@ -48,9 +46,8 @@ describe('AppComponent & RouterTestingModule', () => {
|
||||||
|
|
||||||
it('should navigate to "About" on click', fakeAsync(() => {
|
it('should navigate to "About" on click', fakeAsync(() => {
|
||||||
createComponent();
|
createComponent();
|
||||||
// page.aboutLinkDe.triggerEventHandler('click', null); // fails
|
click(page.aboutLinkDe);
|
||||||
// page.aboutLinkDe.nativeElement.dispatchEvent(newEvent('click')); // fails
|
// page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom
|
||||||
page.aboutLinkDe.nativeElement.click(); // fails in phantom
|
|
||||||
|
|
||||||
advance();
|
advance();
|
||||||
expectPathToBe('/about');
|
expectPathToBe('/about');
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { NgModel, NgControl } from '@angular/forms';
|
||||||
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick
|
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { addMatchers, newEvent } from '../../testing';
|
import { addMatchers, newEvent, click } from '../../testing';
|
||||||
|
|
||||||
beforeEach( addMatchers );
|
beforeEach( addMatchers );
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ describe('TestBed Component Tests', () => {
|
||||||
const comp = fixture.componentInstance;
|
const comp = fixture.componentInstance;
|
||||||
const hero = comp.heroes[0];
|
const hero = comp.heroes[0];
|
||||||
|
|
||||||
heroes[0].triggerEventHandler('click', null);
|
click(heroes[0]);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const selected = fixture.debugElement.query(By.css('p'));
|
const selected = fixture.debugElement.query(By.css('p'));
|
||||||
|
@ -213,7 +213,7 @@ describe('TestBed Component Tests', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(span.textContent).toMatch(/is off/i, 'before click');
|
expect(span.textContent).toMatch(/is off/i, 'before click');
|
||||||
|
|
||||||
btn.triggerEventHandler('click', null);
|
click(btn);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(span.textContent).toMatch(/is on/i, 'after click');
|
expect(span.textContent).toMatch(/is on/i, 'after click');
|
||||||
});
|
});
|
||||||
|
@ -610,7 +610,7 @@ describe('Lifecycle hooks w/ MyIfParentComp', () => {
|
||||||
getChild();
|
getChild();
|
||||||
|
|
||||||
const btn = fixture.debugElement.query(By.css('button'));
|
const btn = fixture.debugElement.query(By.css('button'));
|
||||||
btn.triggerEventHandler('click', null);
|
click(btn);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(child.ngOnDestroyCalled).toBe(true);
|
expect(child.ngOnDestroyCalled).toBe(true);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { async, ComponentFixture, TestBed
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
import { addMatchers } from '../../testing';
|
import { addMatchers, click } from '../../testing';
|
||||||
|
|
||||||
import { Hero } from '../model/hero';
|
import { Hero } from '../model/hero';
|
||||||
import { DashboardHeroComponent } from './dashboard-hero.component';
|
import { DashboardHeroComponent } from './dashboard-hero.component';
|
||||||
|
@ -53,10 +53,22 @@ describe('DashboardHeroComponent when tested directly', () => {
|
||||||
let selectedHero: Hero;
|
let selectedHero: Hero;
|
||||||
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
|
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
|
||||||
|
|
||||||
|
// #docregion trigger-event-handler
|
||||||
heroEl.triggerEventHandler('click', null);
|
heroEl.triggerEventHandler('click', null);
|
||||||
|
// #enddocregion trigger-event-handler
|
||||||
expect(selectedHero).toBe(expectedHero);
|
expect(selectedHero).toBe(expectedHero);
|
||||||
});
|
});
|
||||||
// #enddocregion click-test
|
// #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', () => {
|
it('should raise selected event when clicked', () => {
|
||||||
heroEl.triggerEventHandler('click', null);
|
click(heroEl);
|
||||||
// selected hero should be the same data bound hero
|
// selected hero should be the same data bound hero
|
||||||
expect(testHost.selectedHero).toBe(testHost.hero);
|
expect(testHost.selectedHero).toBe(testHost.hero);
|
||||||
});
|
});
|
||||||
|
@ -102,8 +114,7 @@ import { Component } from '@angular/core';
|
||||||
// #docregion test-host
|
// #docregion test-host
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<dashboard-hero [hero]="hero" (selected)="onSelected($event)">
|
<dashboard-hero [hero]="hero" (selected)="onSelected($event)"></dashboard-hero>`
|
||||||
</dashboard-hero>`
|
|
||||||
})
|
})
|
||||||
class TestHostComponent {
|
class TestHostComponent {
|
||||||
hero = new Hero(42, 'Test Name');
|
hero = new Hero(42, 'Test Name');
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { async, inject, ComponentFixture, TestBed
|
import { async, inject, ComponentFixture, TestBed
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { addMatchers } from '../../testing';
|
import { addMatchers, click } from '../../testing';
|
||||||
import { HeroService } from '../model';
|
import { HeroService } from '../model';
|
||||||
import { FakeHeroService } from '../model/testing';
|
import { FakeHeroService } from '../model/testing';
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ describe('DashboardComponent (deep)', () => {
|
||||||
function clickForDeep() {
|
function clickForDeep() {
|
||||||
// get first <div class="hero"> DebugElement
|
// get first <div class="hero"> DebugElement
|
||||||
const heroEl = fixture.debugElement.query(By.css('.hero'));
|
const heroEl = fixture.debugElement.query(By.css('.hero'));
|
||||||
heroEl.triggerEventHandler('click', null);
|
click(heroEl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { By } from '@angular/platform-browser';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActivatedRoute, ActivatedRouteStub, newEvent, Router, RouterStub
|
ActivatedRoute, ActivatedRouteStub, click, newEvent, Router, RouterStub
|
||||||
} from '../../testing';
|
} from '../../testing';
|
||||||
|
|
||||||
import { Hero } from '../model';
|
import { Hero } from '../model';
|
||||||
|
@ -103,7 +103,7 @@ function overrideSetup() {
|
||||||
expect(comp.hero.name).toBe(newName, 'component hero has new name');
|
expect(comp.hero.name).toBe(newName, 'component hero has new name');
|
||||||
expect(hds.testHero.name).toBe(origName, 'service hero unchanged before save');
|
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
|
tick(); // wait for async save to complete
|
||||||
expect(hds.testHero.name).toBe(newName, 'service hero has new name after save');
|
expect(hds.testHero.name).toBe(newName, 'service hero has new name after save');
|
||||||
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||||
|
@ -159,18 +159,18 @@ function heroModuleSetup() {
|
||||||
// #enddocregion route-good-id
|
// #enddocregion route-good-id
|
||||||
|
|
||||||
it('should navigate when click cancel', () => {
|
it('should navigate when click cancel', () => {
|
||||||
page.cancelBtn.triggerEventHandler('click', null);
|
click(page.cancelBtn);
|
||||||
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save when click save but not navigate immediately', () => {
|
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.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
|
||||||
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
|
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate when click save and save resolves', fakeAsync(() => {
|
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
|
tick(); // wait for async save to complete
|
||||||
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
import { tick, ComponentFixture } from '@angular/core/testing';
|
import { tick, ComponentFixture } from '@angular/core/testing';
|
||||||
|
|
||||||
export * from './jasmine-matchers';
|
export * from './jasmine-matchers';
|
||||||
export * from './router-stubs';
|
export * from './router-stubs';
|
||||||
|
|
||||||
// Short utilities
|
///// Short utilities /////
|
||||||
|
|
||||||
|
/** Wait a tick, then detect changes */
|
||||||
|
export function advance(f: ComponentFixture<any>): void {
|
||||||
|
tick();
|
||||||
|
f.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create custom DOM event the old fashioned way
|
* Create custom DOM event the old fashioned way
|
||||||
*
|
*
|
||||||
|
@ -16,8 +24,20 @@ export function newEvent(eventName: string, bubbles = false, cancelable = false)
|
||||||
return evt;
|
return evt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wait a tick, then detect changes */
|
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||||
export function advance(f: ComponentFixture<any>): void {
|
// #docregion click-event
|
||||||
tick();
|
/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
|
||||||
f.detectChanges();
|
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
|
||||||
|
|
|
@ -43,7 +43,7 @@ block includes
|
||||||
- [_async_](#async-in-before-each) in `beforeEach`
|
- [_async_](#async-in-before-each) in `beforeEach`
|
||||||
- [_compileComponents_](#compile-components)
|
- [_compileComponents_](#compile-components)
|
||||||
1. [Test a component with inputs and outputs](#component-with-inputs-output)
|
1. [Test a component with inputs and outputs](#component-with-inputs-output)
|
||||||
<br><br>
|
- [_triggerEventHandler_](#trigger-event-handler)
|
||||||
1. [Test a component inside a test host component](#component-inside-test-host)
|
1. [Test a component inside a test host component](#component-inside-test-host)
|
||||||
<br><br>
|
<br><br>
|
||||||
1. [Test a routed component](#routed-component)
|
1. [Test a routed component](#routed-component)
|
||||||
|
@ -965,14 +965,51 @@ a(href="#top").to-top Back to top
|
||||||
:marked
|
:marked
|
||||||
The component exposes an `EventEmitter` property. The test subscribes to it just as the host component would do.
|
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_.
|
The `heroEl` is a `DebugElement` that represents the hero `<div>`.
|
||||||
In this example, the component's template binds to the hero `<div>`.
|
The test calls `triggerEventHandler` with the "click" event name.
|
||||||
|
The "click" event binding responds by calling `DashboardHeroComponent.click()`.
|
||||||
|
|
||||||
The test has a reference to that `<div>` in `heroEl` so triggering the `heroEl` click event should cause Angular
|
If the component behaves as expected, `click()` tells the component's `selected` property to emit the `hero` object,
|
||||||
to call `DashboardHeroComponent.click`.
|
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)
|
||||||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button" target="_blank">left-button mouse event object</a>
|
||||||
|
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='.')
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
.l-hr
|
.l-hr
|
||||||
|
|
||||||
|
@ -2135,8 +2172,10 @@ table
|
||||||
:marked
|
:marked
|
||||||
Triggers the event by its name if there is a corresponding listener
|
Triggers the event by its name if there is a corresponding listener
|
||||||
in the element's `listeners` collection.
|
in the element's `listeners` collection.
|
||||||
|
The second parameter is the _event object_ expected by the handler.
|
||||||
|
See [above](#trigger-event-handler).
|
||||||
|
|
||||||
If the event lacks a listner or there's some other problem,
|
If the event lacks a listener or there's some other problem,
|
||||||
consider calling `nativeElement.dispatchEvent(eventObject)`
|
consider calling `nativeElement.dispatchEvent(eventObject)`
|
||||||
|
|
||||||
tr
|
tr
|
||||||
|
|
Loading…
Reference in New Issue