Skip to content

Instantly share code, notes, and snippets.

@BryanWilhite
Last active April 6, 2018 21:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BryanWilhite/f7885f10e8b39233a32d196c2c56bb78 to your computer and use it in GitHub Desktop.
Save BryanWilhite/f7885f10e8b39233a32d196c2c56bb78 to your computer and use it in GitHub Desktop.
Angular Tour of Heroes Tests [https://angular.io/guide/testing]
import { async, ComponentFixture, TestBed
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { addMatchers, click } from '../../testing';
import { Hero } from '../model/hero';
import { DashboardHeroComponent } from './dashboard-hero.component';
beforeEach( addMatchers );
describe('DashboardHeroComponent class only', () => {
it('raises the selected event when clicked', () => {
const comp = new DashboardHeroComponent();
const hero: Hero = { id: 42, name: 'Test' };
comp.hero = hero;
comp.selected.subscribe(selectedHero => expect(selectedHero).toBe(hero));
comp.click();
});
});
describe('DashboardHeroComponent when tested directly', () => {
let comp: DashboardHeroComponent;
let expectedHero: Hero;
let fixture: ComponentFixture<DashboardHeroComponent>;
let heroDe: DebugElement;
let heroEl: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DashboardHeroComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DashboardHeroComponent);
comp = fixture.componentInstance;
// find the hero's DebugElement and element
heroDe = fixture.debugElement.query(By.css('.hero'));
heroEl = heroDe.nativeElement;
// mock the hero supplied by the parent component
expectedHero = { id: 42, name: 'Test Name' };
// simulate the parent setting the input property with that hero
comp.hero = expectedHero;
// trigger initial data binding
fixture.detectChanges();
});
it('should display hero name in uppercase', () => {
const expectedPipedName = expectedHero.name.toUpperCase();
expect(heroEl.textContent).toContain(expectedPipedName);
});
it('should raise selected event when clicked (triggerEventHandler)', () => {
let selectedHero: Hero;
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
heroDe.triggerEventHandler('click', null);
expect(selectedHero).toBe(expectedHero);
});
it('should raise selected event when clicked (element.click)', () => {
let selectedHero: Hero;
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
heroEl.click();
expect(selectedHero).toBe(expectedHero);
});
it('should raise selected event when clicked (click helper)', () => {
let selectedHero: Hero;
comp.selected.subscribe(hero => selectedHero = hero);
click(heroDe); // click helper with DebugElement
click(heroEl); // click helper with native element
expect(selectedHero).toBe(expectedHero);
});
});
//////////////////
describe('DashboardHeroComponent when inside a test host', () => {
let testHost: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
let heroEl: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DashboardHeroComponent, TestHostComponent ]
})
.compileComponents();
}));
beforeEach(() => {
// create TestHostComponent instead of DashboardHeroComponent
fixture = TestBed.createComponent(TestHostComponent);
testHost = fixture.componentInstance;
heroEl = fixture.nativeElement.querySelector('.hero');
fixture.detectChanges(); // trigger initial data binding
});
it('should display hero name', () => {
const expectedPipedName = testHost.hero.name.toUpperCase();
expect(heroEl.textContent).toContain(expectedPipedName);
});
it('should raise selected event when clicked', () => {
click(heroEl);
// selected hero should be the same data bound hero
expect(testHost.selectedHero).toBe(testHost.hero);
});
});
////// Test Host Component //////
import { Component } from '@angular/core';
@Component({
template: `
<dashboard-hero
[hero]="hero" (selected)="onSelected($event)">
</dashboard-hero>`
})
class TestHostComponent {
hero: Hero = {id: 42, name: 'Test Name' };
selectedHero: Hero;
onSelected(hero: Hero) { this.selectedHero = hero; }
}
import { Router } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { Hero } from '../model/hero';
import { addMatchers } from '../../testing';
import { TestHeroService, HeroService } from '../model/testing/test-hero.service';
class FakeRouter {
navigateByUrl(url: string) { return url; }
}
describe('DashboardComponent class only', () => {
let comp: DashboardComponent;
let heroService: TestHeroService;
let router: Router;
beforeEach(() => {
addMatchers();
router = new FakeRouter() as any as Router;
heroService = new TestHeroService();
comp = new DashboardComponent(router, heroService);
});
it('should NOT have heroes before calling OnInit', () => {
expect(comp.heroes.length).toBe(0,
'should not have heroes before OnInit');
});
it('should NOT have heroes immediately after OnInit', () => {
comp.ngOnInit(); // ngOnInit -> getHeroes
expect(comp.heroes.length).toBe(0,
'should not have heroes until service promise resolves');
});
it('should HAVE heroes after HeroService gets them', (done: DoneFn) => {
comp.ngOnInit(); // ngOnInit -> getHeroes
heroService.lastResult // the one from getHeroes
.subscribe(
() => {
// throw new Error('deliberate error'); // see it fail gracefully
expect(comp.heroes.length).toBeGreaterThan(0,
'should have heroes after service promise resolves');
done();
},
done.fail);
});
it('should tell ROUTER to navigate by hero id', () => {
const hero: Hero = {id: 42, name: 'Abbracadabra' };
const spy = spyOn(router, 'navigateByUrl');
comp.gotoDetail(hero);
const navArgs = spy.calls.mostRecent().args[0];
expect(navArgs).toBe('/heroes/42', 'should nav to HeroDetail for Hero 42');
});
});
import { async, inject, ComponentFixture, TestBed
} from '@angular/core/testing';
import { addMatchers, asyncData, click } from '../../testing';
import { HeroService } from '../model/hero.service';
import { getTestHeroes } from '../model/testing/test-heroes';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { DashboardModule } from './dashboard.module';
beforeEach ( addMatchers );
let comp: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
//////// Deep ////////////////
describe('DashboardComponent (deep)', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ DashboardModule ]
});
});
compileAndCreate();
tests(clickForDeep);
function clickForDeep() {
// get first <div class="hero">
const heroEl: HTMLElement = fixture.nativeElement.querySelector('.hero');
click(heroEl);
}
});
//////// Shallow ////////////////
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('DashboardComponent (shallow)', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ DashboardComponent ],
schemas: [NO_ERRORS_SCHEMA]
});
});
compileAndCreate();
tests(clickForShallow);
function clickForShallow() {
// get first <dashboard-hero> DebugElement
const heroDe = fixture.debugElement.query(By.css('dashboard-hero'));
heroDe.triggerEventHandler('selected', comp.heroes[0]);
}
});
/** Add TestBed providers, compile, and create DashboardComponent */
function compileAndCreate() {
beforeEach(async(() => {
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
const heroServiceSpy = jasmine.createSpyObj('HeroService', ['getHeroes']);
TestBed.configureTestingModule({
providers: [
{ provide: HeroService, useValue: heroServiceSpy },
{ provide: Router, useValue: routerSpy }
]
})
.compileComponents().then(() => {
fixture = TestBed.createComponent(DashboardComponent);
comp = fixture.componentInstance;
// getHeroes spy returns observable of test heroes
heroServiceSpy.getHeroes.and.returnValue(asyncData(getTestHeroes()));
});
}));
}
/**
* The (almost) same tests for both.
* Only change: the way that the first hero is clicked
*/
function tests(heroClick: Function) {
it('should NOT have heroes before ngOnInit', () => {
expect(comp.heroes.length).toBe(0,
'should not have heroes before ngOnInit');
});
it('should NOT have heroes immediately after ngOnInit', () => {
fixture.detectChanges(); // runs initial lifecycle hooks
expect(comp.heroes.length).toBe(0,
'should not have heroes until service promise resolves');
});
describe('after get dashboard heroes', () => {
let router: Router;
// Trigger component so it gets heroes and binds to them
beforeEach(async(() => {
router = fixture.debugElement.injector.get(Router);
fixture.detectChanges(); // runs ngOnInit -> getHeroes
fixture.whenStable() // No need for the `lastPromise` hack!
.then(() => fixture.detectChanges()); // bind to heroes
}));
it('should HAVE heroes', () => {
expect(comp.heroes.length).toBeGreaterThan(0,
'should have heroes after service promise resolves');
});
it('should DISPLAY heroes', () => {
// Find and examine the displayed heroes
// Look for them in the DOM by css class
const heroes = fixture.nativeElement.querySelectorAll('dashboard-hero');
expect(heroes.length).toBe(4, 'should display 4 heroes');
});
it('should tell ROUTER to navigate when hero clicked', () => {
heroClick(); // trigger click on first inner <div class="hero">
// args passed to router.navigateByUrl() spy
const spy = router.navigateByUrl as jasmine.Spy;
const navArgs = spy.calls.first().args[0];
// expecting to navigate to id of the component's first hero
const id = comp.heroes[0].id;
expect(navArgs).toBe('/heroes/' + id,
'should nav to HeroDetail for first hero');
});
});
}
import { asyncData, ActivatedRouteStub } from '../../testing';
import { HeroDetailComponent } from './hero-detail.component';
import { Hero } from '../model/hero';
////////// Tests ////////////////////
describe('HeroDetailComponent - no TestBed', () => {
let activatedRoute: ActivatedRouteStub;
let comp: HeroDetailComponent;
let expectedHero: Hero;
let hds: any;
let router: any;
beforeEach((done: DoneFn) => {
expectedHero = {id: 42, name: 'Bubba' };
const activatedRoute = new ActivatedRouteStub({ id: expectedHero.id });
router = jasmine.createSpyObj('router', ['navigate']);
hds = jasmine.createSpyObj('HeroDetailService', ['getHero', 'saveHero']);
hds.getHero.and.returnValue(asyncData(expectedHero));
hds.saveHero.and.returnValue(asyncData(expectedHero));
comp = new HeroDetailComponent(hds, <any> activatedRoute, router);
comp.ngOnInit();
// OnInit calls HDS.getHero; wait for it to get the fake hero
hds.getHero.calls.first().returnValue.subscribe(done);
});
it('should expose the hero retrieved from the service', () => {
expect(comp.hero).toBe(expectedHero);
});
it('should navigate when click cancel', () => {
comp.cancel();
expect(router.navigate.calls.any()).toBe(true, 'router.navigate called');
});
it('should save when click save', () => {
comp.save();
expect(hds.saveHero.calls.any()).toBe(true, 'HeroDetailService.save called');
expect(router.navigate.calls.any()).toBe(false, 'router.navigate not called yet');
});
it('should navigate when click save resolves', (done: DoneFn) => {
comp.save();
// waits for async save to complete before navigating
hds.saveHero.calls.first().returnValue
.subscribe(() => {
expect(router.navigate.calls.any()).toBe(true, 'router.navigate called');
done();
});
});
});
import {
async, ComponentFixture, fakeAsync, inject, TestBed, tick
} from '@angular/core/testing';
import { Router } from '@angular/router';
import {
ActivatedRoute, ActivatedRouteStub, asyncData, click, newEvent
} from '../../testing';
import { Hero } from '../model/hero';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroDetailService } from './hero-detail.service';
import { HeroModule } from './hero.module';
////// Testing Vars //////
let activatedRoute: ActivatedRouteStub;
let component: HeroDetailComponent;
let fixture: ComponentFixture<HeroDetailComponent>;
let page: Page;
////// Tests //////
describe('HeroDetailComponent', () => {
beforeEach(() => {
activatedRoute = new ActivatedRouteStub();
});
describe('with HeroModule setup', heroModuleSetup);
describe('when override its provided HeroDetailService', overrideSetup);
describe('with FormsModule setup', formsModuleSetup);
describe('with SharedModule setup', sharedModuleSetup);
});
///////////////////
function overrideSetup() {
class HeroDetailServiceSpy {
testHero: Hero = {id: 42, name: 'Test Hero' };
/* emit cloned test hero */
getHero = jasmine.createSpy('getHero').and.callFake(
() => asyncData(Object.assign({}, this.testHero))
);
/* emit clone of test hero, with changes merged in */
saveHero = jasmine.createSpy('saveHero').and.callFake(
(hero: Hero) => asyncData(Object.assign(this.testHero, hero))
);
}
// the `id` value is irrelevant because ignored by service stub
beforeEach(() => activatedRoute.setParamMap({ id: 99999 }));
beforeEach(async(() => {
const routerSpy = createRouterSpy();
TestBed.configureTestingModule({
imports: [ HeroModule ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: Router, useValue: routerSpy},
// HeroDetailService at this level is IRRELEVANT!
{ provide: HeroDetailService, useValue: {} }
]
})
// Override component's own provider
.overrideComponent(HeroDetailComponent, {
set: {
providers: [
{ provide: HeroDetailService, useClass: HeroDetailServiceSpy }
]
}
})
.compileComponents();
}));
let hdsSpy: HeroDetailServiceSpy;
beforeEach(async(() => {
createComponent();
// get the component's injected HeroDetailServiceSpy
hdsSpy = fixture.debugElement.injector.get(HeroDetailService) as any;
}));
it('should have called `getHero`', () => {
expect(hdsSpy.getHero.calls.count()).toBe(1, 'getHero called once');
});
it('should display stub hero\'s name', () => {
expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name);
});
it('should save stub hero change', fakeAsync(() => {
const origName = hdsSpy.testHero.name;
const newName = 'New Name';
page.nameInput.value = newName;
page.nameInput.dispatchEvent(newEvent('input')); // tell Angular
expect(component.hero.name).toBe(newName, 'component hero has new name');
expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save');
click(page.saveBtn);
expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once');
tick(); // wait for async save to complete
expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save');
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
}));
it('fixture injected service is not the component injected service',
// inject gets the service from the fixture
inject([HeroDetailService], (fixtureService: HeroDetailService) => {
// use `fixture.debugElement.injector` to get service from component
const componentService = fixture.debugElement.injector.get(HeroDetailService);
expect(fixtureService).not.toBe(componentService, 'service injected from fixture');
}));
}
////////////////////
import { getTestHeroes, TestHeroService, HeroService } from '../model/testing/test-hero.service';
const firstHero = getTestHeroes()[0];
function heroModuleSetup() {
beforeEach(async(() => {
const routerSpy = createRouterSpy();
TestBed.configureTestingModule({
imports: [ HeroModule ],
// declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: TestHeroService },
{ provide: Router, useValue: routerSpy},
]
})
.compileComponents();
}));
describe('when navigate to existing hero', () => {
let expectedHero: Hero;
beforeEach(async(() => {
expectedHero = firstHero;
activatedRoute.setParamMap({ id: expectedHero.id });
createComponent();
}));
it('should display that hero\'s name', () => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});
it('should navigate when click cancel', () => {
click(page.cancelBtn);
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
});
it('should save when click save but not navigate immediately', () => {
// Get service injected into component and spy on its`saveHero` method.
// It delegates to fake `HeroService.updateHero` which delivers a safe test result.
const hds = fixture.debugElement.injector.get(HeroDetailService);
const saveSpy = spyOn(hds, 'saveHero').and.callThrough();
click(page.saveBtn);
expect(saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
expect(page.navigateSpy.calls.any()).toBe(false, 'router.navigate not called');
});
it('should navigate when click save and save resolves', fakeAsync(() => {
click(page.saveBtn);
tick(); // wait for async save to complete
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
}));
it('should convert hero name to Title Case', () => {
// get the name's input and display elements from the DOM
const hostElement = fixture.nativeElement;
const nameInput: HTMLInputElement = hostElement.querySelector('input');
const nameDisplay: HTMLElement = hostElement.querySelector('span');
// simulate user entering a new name into the input box
nameInput.value = 'quick BROWN fOx';
// dispatch a DOM event so that Angular learns of input value change.
nameInput.dispatchEvent(newEvent('input'));
// Tell Angular to update the display binding through the title pipe
fixture.detectChanges();
expect(nameDisplay.textContent).toBe('Quick Brown Fox');
});
});
describe('when navigate with no hero id', () => {
beforeEach(async( createComponent ));
it('should have hero.id === 0', () => {
expect(component.hero.id).toBe(0);
});
it('should display empty hero name', () => {
expect(page.nameDisplay.textContent).toBe('');
});
});
describe('when navigate to non-existent hero id', () => {
beforeEach(async(() => {
activatedRoute.setParamMap({ id: 99999 });
createComponent();
}));
it('should try to navigate back to hero list', () => {
expect(page.gotoListSpy.calls.any()).toBe(true, 'comp.gotoList called');
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
});
});
// Why we must use `fixture.debugElement.injector` in `Page()`
it('cannot use `inject` to get component\'s provided HeroDetailService', () => {
let service: HeroDetailService;
fixture = TestBed.createComponent(HeroDetailComponent);
expect(
// Throws because `inject` only has access to TestBed's injector
// which is an ancestor of the component's injector
inject([HeroDetailService], (hds: HeroDetailService) => service = hds )
)
.toThrowError(/No provider for HeroDetailService/);
// get `HeroDetailService` with component's own injector
service = fixture.debugElement.injector.get(HeroDetailService);
expect(service).toBeDefined('debugElement.injector');
});
}
/////////////////////
import { FormsModule } from '@angular/forms';
import { TitleCasePipe } from '../shared/title-case.pipe';
function formsModuleSetup() {
beforeEach(async(() => {
const routerSpy = createRouterSpy();
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ HeroDetailComponent, TitleCasePipe ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: TestHeroService },
{ provide: Router, useValue: routerSpy},
]
})
.compileComponents();
}));
it('should display 1st hero\'s name', async(() => {
const expectedHero = firstHero;
activatedRoute.setParamMap({ id: expectedHero.id });
createComponent().then(() => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});
}));
}
///////////////////////
import { SharedModule } from '../shared/shared.module';
function sharedModuleSetup() {
beforeEach(async(() => {
const routerSpy = createRouterSpy();
TestBed.configureTestingModule({
imports: [ SharedModule ],
declarations: [ HeroDetailComponent ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: TestHeroService },
{ provide: Router, useValue: routerSpy},
]
})
.compileComponents();
}));
it('should display 1st hero\'s name', async(() => {
const expectedHero = firstHero;
activatedRoute.setParamMap({ id: expectedHero.id });
createComponent().then(() => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});
}));
}
/////////// Helpers /////
/** Create the HeroDetailComponent, initialize it, set test variables */
function createComponent() {
fixture = TestBed.createComponent(HeroDetailComponent);
component = fixture.componentInstance;
page = new Page(fixture);
// 1st change detection triggers ngOnInit which gets a hero
fixture.detectChanges();
return fixture.whenStable().then(() => {
// 2nd change detection displays the async-fetched hero
fixture.detectChanges();
});
}
class Page {
// getter properties wait to query the DOM until called.
get buttons() { return this.queryAll<HTMLButtonElement>('button'); }
get saveBtn() { return this.buttons[0]; }
get cancelBtn() { return this.buttons[1]; }
get nameDisplay() { return this.query<HTMLElement>('span'); }
get nameInput() { return this.query<HTMLInputElement>('input'); }
gotoListSpy: jasmine.Spy;
navigateSpy: jasmine.Spy;
constructor(fixture: ComponentFixture<HeroDetailComponent>) {
// get the navigate spy from the injected router spy object
const routerSpy = <any> fixture.debugElement.injector.get(Router);
this.navigateSpy = routerSpy.navigate;
// spy on component's `gotoList()` method
const component = fixture.componentInstance;
this.gotoListSpy = spyOn(component, 'gotoList').and.callThrough();
}
//// query helpers ////
private query<T>(selector: string): T {
return fixture.nativeElement.querySelector(selector);
}
private queryAll<T>(selector: string): T[] {
return fixture.nativeElement.querySelectorAll(selector);
}
}
function createRouterSpy() {
return jasmine.createSpyObj('Router', ['navigate']);
}
import { async, ComponentFixture, fakeAsync, TestBed, tick
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { Router } from '@angular/router';
import { addMatchers, newEvent } from '../../testing';
import { getTestHeroes, TestHeroService } from '../model/testing/test-hero.service';
import { HeroModule } from './hero.module';
import { HeroListComponent } from './hero-list.component';
import { HighlightDirective } from '../shared/highlight.directive';
import { HeroService } from '../model/hero.service';
const HEROES = getTestHeroes();
let comp: HeroListComponent;
let fixture: ComponentFixture<HeroListComponent>;
let page: Page;
/////// Tests //////
describe('HeroListComponent', () => {
beforeEach(async(() => {
addMatchers();
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
TestBed.configureTestingModule({
imports: [HeroModule],
providers: [
{ provide: HeroService, useClass: TestHeroService },
{ provide: Router, useValue: routerSpy}
]
})
.compileComponents()
.then(createComponent);
}));
it('should display heroes', () => {
expect(page.heroRows.length).toBeGreaterThan(0);
});
it('1st hero should match 1st test hero', () => {
const expectedHero = HEROES[0];
const actualHero = page.heroRows[0].textContent;
expect(actualHero).toContain(expectedHero.id.toString(), 'hero.id');
expect(actualHero).toContain(expectedHero.name, 'hero.name');
});
it('should select hero on click', fakeAsync(() => {
const expectedHero = HEROES[1];
const li = page.heroRows[1];
li.dispatchEvent(newEvent('click'));
tick();
// `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService
expect(comp.selectedHero).toEqual(expectedHero);
}));
it('should navigate to selected hero detail on click', fakeAsync(() => {
const expectedHero = HEROES[1];
const li = page.heroRows[1];
li.dispatchEvent(newEvent('click'));
tick();
// should have navigated
expect(page.navSpy.calls.any()).toBe(true, 'navigate called');
// composed hero detail will be URL like 'heroes/42'
// expect link array with the route path and hero id
// first argument to router.navigate is link array
const navArgs = page.navSpy.calls.first().args[0];
expect(navArgs[0]).toContain('heroes', 'nav to heroes detail URL');
expect(navArgs[1]).toBe(expectedHero.id, 'expected hero.id');
}));
it('should find `HighlightDirective` with `By.directive', () => {
// Can find DebugElement either by css selector or by directive
const h2 = fixture.debugElement.query(By.css('h2'));
const directive = fixture.debugElement.query(By.directive(HighlightDirective));
expect(h2).toBe(directive);
});
it('should color header with `HighlightDirective`', () => {
const h2 = page.highlightDe.nativeElement as HTMLElement;
const bgColor = h2.style.backgroundColor;
// different browsers report color values differently
const isExpectedColor = bgColor === 'gold' || bgColor === 'rgb(255, 215, 0)';
expect(isExpectedColor).toBe(true, 'backgroundColor');
});
it('the `HighlightDirective` is among the element\'s providers', () => {
expect(page.highlightDe.providerTokens).toContain(HighlightDirective, 'HighlightDirective');
});
});
/////////// Helpers /////
/** Create the component and set the `page` test variables */
function createComponent() {
fixture = TestBed.createComponent(HeroListComponent);
comp = fixture.componentInstance;
// change detection triggers ngOnInit which gets a hero
fixture.detectChanges();
return fixture.whenStable().then(() => {
// got the heroes and updated component
// change detection updates the view
fixture.detectChanges();
page = new Page();
});
}
class Page {
/** Hero line elements */
heroRows: HTMLLIElement[];
/** Highlighted DebugElement */
highlightDe: DebugElement;
/** Spy on router navigate method */
navSpy: jasmine.Spy;
constructor() {
const heroRowNodes = fixture.nativeElement.querySelectorAll('li');
this.heroRows = Array.from(heroRowNodes);
// Find the first element with an attached HighlightDirective
this.highlightDe = fixture.debugElement.query(By.directive(HighlightDirective));
// Get the component's injected router navigation spy
const routerSpy = fixture.debugElement.injector.get(Router);
this.navSpy = routerSpy.navigate as jasmine.Spy;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment