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

Commit 84b5297

Browse files
authored
docs(testing): feedback from Joe Eames/add more scenarios (#2391)
1 parent b9733fd commit 84b5297

14 files changed

+396
-229
lines changed

public/docs/_examples/testing/ts/app-specs.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
'app/model/hero.spec',
4343
'app/model/http-hero.service.spec',
4444
'app/shared/title-case.pipe.spec',
45-
'app/twain.component.spec',
45+
'app/shared/twain.component.spec',
4646
'app/welcome.component.spec'
4747
];
4848
</script>

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AppComponent } from './app.component';
88
import { BannerComponent } from './banner.component';
99
import { SharedModule } from './shared/shared.module';
1010

11-
import { Router, FakeRouter, FakeRouterLinkDirective, FakeRouterOutletComponent
11+
import { Router, RouterStub, RouterLinkDirectiveStub, RouterOutletStubComponent
1212
} from '../testing';
1313

1414

@@ -20,9 +20,9 @@ describe('AppComponent & TestModule', () => {
2020
TestBed.configureTestingModule({
2121
declarations: [
2222
AppComponent, BannerComponent,
23-
FakeRouterLinkDirective, FakeRouterOutletComponent
23+
RouterLinkDirectiveStub, RouterOutletStubComponent
2424
],
25-
providers: [{ provide: Router, useClass: FakeRouter }],
25+
providers: [{ provide: Router, useClass: RouterStub }],
2626
schemas: [NO_ERRORS_SCHEMA]
2727
})
2828

@@ -49,9 +49,9 @@ function tests() {
4949

5050
const links = fixture.debugElement
5151
// find all elements with an attached FakeRouterLink directive
52-
.queryAll(By.directive(FakeRouterLinkDirective))
52+
.queryAll(By.directive(RouterLinkDirectiveStub))
5353
// use injector to get the RouterLink directive instance attached to each element
54-
.map(de => de.injector.get(FakeRouterLinkDirective) as FakeRouterLinkDirective);
54+
.map(de => de.injector.get(RouterLinkDirectiveStub) as RouterLinkDirectiveStub);
5555

5656
expect(links.length).toBe(3, 'should have 3 links');
5757
expect(links[0].linkParams).toBe('/dashboard', '1st link should go to Dashboard');
@@ -63,11 +63,11 @@ function tests() {
6363

6464
// Heroes RouterLink DebugElement
6565
const heroesLinkDe = fixture.debugElement
66-
.queryAll(By.directive(FakeRouterLinkDirective))[1];
66+
.queryAll(By.directive(RouterLinkDirectiveStub))[1];
6767

6868
expect(heroesLinkDe).toBeDefined('should have a 2nd RouterLink');
6969

70-
const link = heroesLinkDe.injector.get(FakeRouterLinkDirective) as FakeRouterLinkDirective;
70+
const link = heroesLinkDe.injector.get(RouterLinkDirectiveStub) as RouterLinkDirectiveStub;
7171

7272
expect(link.navigatedTo).toBeNull('link should not have navigate yet');
7373

@@ -101,8 +101,8 @@ describe('AppComponent & AppModule', () => {
101101
// Separate override because cannot both `set` and `add/remove` in same override
102102
.overrideModule(AppModule, {
103103
add: {
104-
declarations: [ FakeRouterLinkDirective, FakeRouterOutletComponent ],
105-
providers: [{ provide: Router, useClass: FakeRouter }]
104+
declarations: [ RouterLinkDirectiveStub, RouterOutletStubComponent ],
105+
providers: [{ provide: Router, useClass: RouterStub }]
106106
}
107107
})
108108

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import { Router } from '@angular/router';
1212
import { DashboardComponent } from './dashboard.component';
1313
import { DashboardModule } from './dashboard.module';
1414

15-
// #docregion fake-router
16-
class FakeRouter {
15+
// #docregion router-stub
16+
class RouterStub {
1717
navigateByUrl(url: string) { return url; }
1818
}
19-
// #enddocregion fake-router
19+
// #enddocregion router-stub
2020

2121
beforeEach ( addMatchers );
2222

@@ -73,7 +73,7 @@ function compileAndCreate() {
7373
TestBed.configureTestingModule({
7474
providers: [
7575
{ provide: HeroService, useClass: FakeHeroService },
76-
{ provide: Router, useClass: FakeRouter }
76+
{ provide: Router, useClass: RouterStub }
7777
]
7878
})
7979
.compileComponents().then(() => {

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
<!-- #docregion -->
12
<div *ngIf="hero">
23
<h2><span>{{hero.name | titlecase}}</span> Details</h2>
34
<div>
45
<label>id: </label>{{hero.id}}</div>
56
<div>
6-
<label>name: </label>
7-
<input [(ngModel)]="hero.name" placeholder="name" />
7+
<label for="name">name: </label>
8+
<input id="name" [(ngModel)]="hero.name" placeholder="name" />
89
</div>
910
<button (click)="save()">Save</button>
1011
<button (click)="cancel()">Cancel</button>

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { HeroDetailComponent } from './hero-detail.component';
22
import { Hero } from '../model';
33

4-
import { FakeActivatedRoute } from '../../testing';
4+
import { ActivatedRouteStub } from '../../testing';
55

66
////////// Tests ////////////////////
77

88
describe('HeroDetailComponent - no TestBed', () => {
9-
let activatedRoute: FakeActivatedRoute;
9+
let activatedRoute: ActivatedRouteStub;
1010
let comp: HeroDetailComponent;
1111
let expectedHero: Hero;
1212
let hds: any;
1313
let router: any;
1414

1515
beforeEach( done => {
1616
expectedHero = new Hero(42, 'Bubba');
17-
activatedRoute = new FakeActivatedRoute();
17+
activatedRoute = new ActivatedRouteStub();
1818
activatedRoute.testParams = { id: expectedHero.id };
1919

2020
router = jasmine.createSpyObj('router', ['navigate']);

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

+42-33
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// #docplaster
12
import {
23
async, ComponentFixture, fakeAsync, inject, TestBed, tick
34
} from '@angular/core/testing';
@@ -7,7 +8,7 @@ import { DebugElement } from '@angular/core';
78

89
import {
910
addMatchers, newEvent,
10-
ActivatedRoute, FakeActivatedRoute, Router, FakeRouter
11+
ActivatedRoute, ActivatedRouteStub, Router, RouterStub
1112
} from '../../testing';
1213

1314
import { HEROES, FakeHeroService } from '../model/testing';
@@ -18,7 +19,7 @@ import { HeroDetailService } from './hero-detail.service';
1819
import { Hero, HeroService } from '../model';
1920

2021
////// Testing Vars //////
21-
let activatedRoute: FakeActivatedRoute;
22+
let activatedRoute: ActivatedRouteStub;
2223
let comp: HeroDetailComponent;
2324
let fixture: ComponentFixture<HeroDetailComponent>;
2425
let page: Page;
@@ -29,7 +30,7 @@ describe('HeroDetailComponent', () => {
2930

3031
beforeEach( async(() => {
3132
addMatchers();
32-
activatedRoute = new FakeActivatedRoute();
33+
activatedRoute = new ActivatedRouteStub();
3334

3435
TestBed.configureTestingModule({
3536
imports: [ HeroModule ],
@@ -40,12 +41,13 @@ describe('HeroDetailComponent', () => {
4041
providers: [
4142
{ provide: ActivatedRoute, useValue: activatedRoute },
4243
{ provide: HeroService, useClass: FakeHeroService },
43-
{ provide: Router, useClass: FakeRouter},
44+
{ provide: Router, useClass: RouterStub},
4445
]
4546
})
4647
.compileComponents();
4748
}));
4849

50+
// #docregion route-good-id
4951
describe('when navigate to hero id=' + HEROES[0].id, () => {
5052
let expectedHero: Hero;
5153

@@ -55,51 +57,53 @@ describe('HeroDetailComponent', () => {
5557
createComponent();
5658
}));
5759

60+
// #docregion selected-tests
5861
it('should display that hero\'s name', () => {
5962
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
6063
});
64+
// #enddocregion route-good-id
6165

6266
it('should navigate when click cancel', () => {
6367
page.cancelBtn.triggerEventHandler('click', null);
6468
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
6569
});
6670

67-
it('should save when click save', () => {
71+
it('should save when click save but not navigate immediately', () => {
6872
page.saveBtn.triggerEventHandler('click', null);
6973
expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
74+
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
7075
});
7176

72-
it('should navigate when click click save resolves', fakeAsync(() => {
77+
it('should navigate when click save and save resolves', fakeAsync(() => {
7378
page.saveBtn.triggerEventHandler('click', null);
74-
tick(); // waits for async save to "complete" before navigating
79+
tick(); // wait for async save to "complete" before navigating
7580
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
7681
}));
7782

78-
7983
// #docregion title-case-pipe
80-
it('should convert original hero name to Title Case', () => {
81-
expect(page.nameDisplay.textContent).toBe(comp.hero.name);
82-
});
83-
// #enddocregion title-case-pipe
84-
8584
it('should convert hero name to Title Case', fakeAsync(() => {
8685
const inputName = 'quick BROWN fox';
87-
const expectedName = 'Quick Brown Fox';
86+
const titleCaseName = 'Quick Brown Fox';
8887

89-
// simulate user entering new name in input
88+
// simulate user entering new name into the input box
9089
page.nameInput.value = inputName;
9190

9291
// dispatch a DOM event so that Angular learns of input value change.
93-
// detectChanges() makes ngModel push input value to component property
94-
// and Angular updates the output span
9592
page.nameInput.dispatchEvent(newEvent('input'));
93+
94+
// detectChanges() makes [(ngModel)] push input value to component property
95+
// and Angular updates the output span through the title pipe
9696
fixture.detectChanges();
97-
expect(page.nameDisplay.textContent).toBe(expectedName, 'hero name display');
98-
expect(comp.hero.name).toBe(inputName, 'comp.hero.name');
99-
}));
10097

98+
expect(page.nameDisplay.textContent).toBe(titleCaseName);
99+
}));
100+
// #enddocregion title-case-pipe
101+
// #enddocregion selected-tests
102+
// #docregion route-good-id
101103
});
104+
// #enddocregion route-good-id
102105

106+
// #docregion route-no-id
103107
describe('when navigate with no hero id', () => {
104108
beforeEach( async( createComponent ));
105109

@@ -111,7 +115,9 @@ describe('HeroDetailComponent', () => {
111115
expect(page.nameDisplay.textContent).toBe('');
112116
});
113117
});
118+
// #enddocregion route-no-id
114119

120+
// #docregion route-bad-id
115121
describe('when navigate to non-existant hero id', () => {
116122
beforeEach( async(() => {
117123
activatedRoute.testParams = { id: 99999 };
@@ -123,6 +129,7 @@ describe('HeroDetailComponent', () => {
123129
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
124130
});
125131
});
132+
// #enddocregion route-bad-id
126133

127134
///////////////////////////
128135

@@ -145,22 +152,24 @@ describe('HeroDetailComponent', () => {
145152

146153
/////////// Helpers /////
147154

155+
// #docregion create-component
148156
/** Create the HeroDetailComponent, initialize it, set test variables */
149157
function createComponent() {
150158
fixture = TestBed.createComponent(HeroDetailComponent);
151159
comp = fixture.componentInstance;
152160
page = new Page();
153161

154-
// change detection triggers ngOnInit which gets a hero
162+
// 1st change detection triggers ngOnInit which gets a hero
155163
fixture.detectChanges();
156164
return fixture.whenStable().then(() => {
157-
// got the hero and updated component
158-
// change detection updates the view
165+
// 2nd change detection displays the async-fetched hero
159166
fixture.detectChanges();
160167
page.addPageElements();
161168
});
162169
}
170+
// #enddocregion create-component
163171

172+
// #docregion page
164173
class Page {
165174
gotoSpy: jasmine.Spy;
166175
navSpy: jasmine.Spy;
@@ -173,24 +182,24 @@ class Page {
173182

174183
constructor() {
175184
// Use component's injector to see the services it injected.
176-
let compInjector = fixture.debugElement.injector;
177-
let hds = compInjector.get(HeroDetailService);
178-
let router = compInjector.get(Router);
179-
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
180-
this.saveSpy = spyOn(hds, 'saveHero').and.callThrough();
181-
this.navSpy = spyOn(router, 'navigate').and.callThrough();
185+
const compInjector = fixture.debugElement.injector;
186+
const hds = compInjector.get(HeroDetailService);
187+
const router = compInjector.get(Router);
188+
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
189+
this.saveSpy = spyOn(hds, 'saveHero').and.callThrough();
190+
this.navSpy = spyOn(router, 'navigate').and.callThrough();
182191
}
183192

184-
/** Add page elements after page initializes */
193+
/** Add page elements after hero arrives */
185194
addPageElements() {
186195
if (comp.hero) {
187-
// have a hero so these DOM elements can be reached
188-
let buttons = fixture.debugElement.queryAll(By.css('button'));
196+
// have a hero so these elements are now in the DOM
197+
const buttons = fixture.debugElement.queryAll(By.css('button'));
189198
this.saveBtn = buttons[0];
190199
this.cancelBtn = buttons[1];
191200
this.nameDisplay = fixture.debugElement.query(By.css('span')).nativeElement;
192201
this.nameInput = fixture.debugElement.query(By.css('input')).nativeElement;
193202
}
194203
}
195204
}
196-
205+
// #enddocregion page

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

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Component, Input, OnInit } from '@angular/core';
22
import { ActivatedRoute, Router } from '@angular/router';
3+
import 'rxjs/add/operator/pluck';
34

45
import { Hero } from '../model';
56
import { HeroDetailService } from './hero-detail.service';
@@ -16,31 +17,34 @@ import { HeroDetailService } from './hero-detail.service';
1617
export class HeroDetailComponent implements OnInit {
1718
@Input() hero: Hero;
1819

20+
// #docregion ctor
1921
constructor(
2022
private heroDetailService: HeroDetailService,
2123
private route: ActivatedRoute,
2224
private router: Router) {
2325
}
24-
25-
ngOnInit() {
26-
let id = this.route.snapshot.params['id'];
27-
28-
// tslint:disable-next-line:triple-equals
29-
if (id == undefined) {
30-
// no id; act as if is new
31-
this.hero = new Hero();
32-
} else {
33-
this.heroDetailService.getHero(id).then(hero => {
34-
if (hero) {
35-
this.hero = hero;
36-
} else {
37-
this.gotoList(); // id not found; navigate to list
38-
}
39-
});
40-
}
26+
// #enddocregion ctor
27+
28+
// #docregion ng-on-init
29+
ngOnInit(): void {
30+
// get hero when `id` param changes
31+
this.route.params.pluck<string>('id')
32+
.forEach(id => this.getHero(id))
33+
.catch(() => this.hero = new Hero()); // no id; should edit new hero
34+
}
35+
// #enddocregion ng-on-init
36+
37+
private getHero(id: string): void {
38+
this.heroDetailService.getHero(id).then(hero => {
39+
if (hero) {
40+
this.hero = hero;
41+
} else {
42+
this.gotoList(); // id not found; navigate to list
43+
}
44+
});
4145
}
4246

43-
save() {
47+
save(): void {
4448
this.heroDetailService.saveHero(this.hero).then(() => this.gotoList());
4549
}
4650

0 commit comments

Comments
 (0)