From 803414ae806b5b4192a8efed1e06d66828079f80 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Thu, 24 Mar 2016 19:30:26 +0000 Subject: [PATCH] docs(testing): update testing to use toh-5 closes #1003 --- .../testing/ts/app/app.component.css | 31 +++ .../_examples/testing/ts/app/app.component.ts | 59 ++++++ .../testing/ts/app/backend.service.ts | 14 -- .../_examples/testing/ts/app/bootstrap.ts | 11 - .../testing/ts/app/dashboard.component.css | 63 ++++++ .../testing/ts/app/dashboard.component.html | 11 + .../testing/ts/app/dashboard.component.ts | 44 ++++ .../_examples/testing/ts/app/decorators.ts | 16 -- .../testing/ts/app/hero-detail.component.css | 33 ++- .../testing/ts/app/hero-detail.component.html | 36 ++-- .../testing/ts/app/hero-detail.component.ts | 70 ++++--- .../_examples/testing/ts/app/hero.service.ts | 49 ++--- .../_examples/testing/ts/app/hero.spec.ts | 10 +- public/docs/_examples/testing/ts/app/hero.ts | 20 +- public/docs/_examples/testing/ts/app/hero2.ts | 19 -- .../testing/ts/app/heroes.component.css | 75 +++++-- .../testing/ts/app/heroes.component.html | 36 ++-- .../testing/ts/app/heroes.component.ts | 77 ++++--- .../testing/ts/app/init-caps-pipe.spec.ts | 39 ---- .../testing/ts/app/init-caps-pipe.ts | 15 -- public/docs/_examples/testing/ts/app/main.ts | 4 + .../_examples/testing/ts/app/mock-heroes.ts | 82 ++------ .../testing/ts/app/my-uppercase.pipe.1.ts | 9 + .../testing/ts/app/my-uppercase.pipe.spec.ts | 41 ++++ .../testing/ts/app/my-uppercase.pipe.ts | 13 ++ .../hero-detail.component.spec.ts.not-yet | 0 ...il.component.wrapped-tests.spec.ts.not-yet | 0 .../hero.service.ng.spec.ts.not-yet} | 0 .../hero.service.no-ng.1.spec.ts.not-yet} | 0 .../hero.service.no-ng.spec.ts.not-yet} | 0 .../heroes.component.ng.spec.ts.not-yet | 0 .../heroes.component.no-ng.spec.ts.not-yet} | 0 .../user.spec.ts.not-yet} | 0 public/docs/_examples/testing/ts/app/user.ts | 6 - public/docs/_examples/testing/ts/index.html | 54 +++-- .../_examples/testing/ts/unit-tests-4.html | 10 +- .../_examples/testing/ts/unit-tests-5.html | 50 +++++ .../testing/ts/unit-tests-5.html.not-yet | 44 ---- .../testing/ts/unit-tests-6.html.not-yet | 48 +++-- .../testing/ts/unit-tests-7.html.not-yet | 46 +++++ public/docs/ts/latest/guide/testing.jade | 17 +- .../testing/application-under-test.jade | 35 +--- .../ts/latest/testing/first-app-tests.jade | 26 +-- .../testing/testing-an-angular-pipe.jade | 194 ++++++++++++------ .../big-time-fail-screen.png | Bin 9275 -> 2091 bytes .../testing-an-angular-pipe/two-failures.png | Bin 0 -> 3056 bytes .../testing-an-angular-pipe/zero-failures.png | Bin 0 -> 5713 bytes 47 files changed, 835 insertions(+), 572 deletions(-) create mode 100644 public/docs/_examples/testing/ts/app/app.component.css create mode 100644 public/docs/_examples/testing/ts/app/app.component.ts delete mode 100644 public/docs/_examples/testing/ts/app/backend.service.ts delete mode 100644 public/docs/_examples/testing/ts/app/bootstrap.ts create mode 100644 public/docs/_examples/testing/ts/app/dashboard.component.css create mode 100644 public/docs/_examples/testing/ts/app/dashboard.component.html create mode 100644 public/docs/_examples/testing/ts/app/dashboard.component.ts delete mode 100644 public/docs/_examples/testing/ts/app/decorators.ts delete mode 100644 public/docs/_examples/testing/ts/app/hero2.ts delete mode 100644 public/docs/_examples/testing/ts/app/init-caps-pipe.spec.ts delete mode 100644 public/docs/_examples/testing/ts/app/init-caps-pipe.ts create mode 100644 public/docs/_examples/testing/ts/app/main.ts create mode 100644 public/docs/_examples/testing/ts/app/my-uppercase.pipe.1.ts create mode 100644 public/docs/_examples/testing/ts/app/my-uppercase.pipe.spec.ts create mode 100644 public/docs/_examples/testing/ts/app/my-uppercase.pipe.ts rename public/docs/_examples/testing/ts/app/{ => old-specs}/hero-detail.component.spec.ts.not-yet (100%) rename public/docs/_examples/testing/ts/app/{ => old-specs}/hero-detail.component.wrapped-tests.spec.ts.not-yet (100%) rename public/docs/_examples/testing/ts/app/{hero.service.ng.spec.ts => old-specs/hero.service.ng.spec.ts.not-yet} (100%) rename public/docs/_examples/testing/ts/app/{hero.service.no-ng.1.spec.ts => old-specs/hero.service.no-ng.1.spec.ts.not-yet} (100%) rename public/docs/_examples/testing/ts/app/{hero.service.no-ng.spec.ts => old-specs/hero.service.no-ng.spec.ts.not-yet} (100%) rename public/docs/_examples/testing/ts/app/{ => old-specs}/heroes.component.ng.spec.ts.not-yet (100%) rename public/docs/_examples/testing/ts/app/{heroes.component.no-ng.spec.ts => old-specs/heroes.component.no-ng.spec.ts.not-yet} (100%) rename public/docs/_examples/testing/ts/app/{user.spec.ts => old-specs/user.spec.ts.not-yet} (100%) delete mode 100644 public/docs/_examples/testing/ts/app/user.ts create mode 100644 public/docs/_examples/testing/ts/unit-tests-5.html delete mode 100644 public/docs/_examples/testing/ts/unit-tests-5.html.not-yet create mode 100644 public/docs/_examples/testing/ts/unit-tests-7.html.not-yet create mode 100644 public/resources/images/devguide/testing-an-angular-pipe/two-failures.png create mode 100644 public/resources/images/devguide/testing-an-angular-pipe/zero-failures.png diff --git a/public/docs/_examples/testing/ts/app/app.component.css b/public/docs/_examples/testing/ts/app/app.component.css new file mode 100644 index 0000000000..137e9be7be --- /dev/null +++ b/public/docs/_examples/testing/ts/app/app.component.css @@ -0,0 +1,31 @@ +/* #docplaster */ +/* #docregion css */ +h1 { + font-size: 1.2em; + color: #999; + margin-bottom: 0; +} +h2 { + font-size: 2em; + margin-top: 0; + padding-top: 0; +} +nav a { + padding: 5px 10px; + text-decoration: none; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.router-link-active { + color: #039be5; +} +/* #enddocregion css */ diff --git a/public/docs/_examples/testing/ts/app/app.component.ts b/public/docs/_examples/testing/ts/app/app.component.ts new file mode 100644 index 0000000000..df9985b5ee --- /dev/null +++ b/public/docs/_examples/testing/ts/app/app.component.ts @@ -0,0 +1,59 @@ +// #docplaster +// #docregion +import { Component } from 'angular2/core'; +import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router'; + +import { HeroService } from './hero.service'; +import { DashboardComponent } from './dashboard.component'; +import { HeroesComponent } from './heroes.component'; +// #docregion hero-detail-import +import { HeroDetailComponent } from './hero-detail.component'; +// #enddocregion hero-detail-import + +@Component({ + selector: 'my-app', +// #docregion template + template: ` +

{{title}}

+ + + `, +// #enddocregion template +// #docregion style-urls + styleUrls: ['app/app.component.css'], +// #enddocregion style-urls + directives: [ROUTER_DIRECTIVES], + providers: [ + ROUTER_PROVIDERS, + HeroService + ] +}) +@RouteConfig([ +// #docregion dashboard-route + { + path: '/dashboard', + name: 'Dashboard', + component: DashboardComponent, + useAsDefault: true + }, +// #enddocregion dashboard-route +// #docregion hero-detail-route + { + path: '/detail/:id', + name: 'HeroDetail', + component: HeroDetailComponent + }, +// #enddocregion hero-detail-route + { + path: '/heroes', + name: 'Heroes', + component: HeroesComponent + } +]) +export class AppComponent { + title = 'Tour of Heroes'; +} +// #enddocregion diff --git a/public/docs/_examples/testing/ts/app/backend.service.ts b/public/docs/_examples/testing/ts/app/backend.service.ts deleted file mode 100644 index 9135af7f5e..0000000000 --- a/public/docs/_examples/testing/ts/app/backend.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Hero} from './hero'; -import {HEROES} from './mock-heroes'; - -let delay = 1000; // ms delay in return of data - -export class BackendService { - - fetchAllHeroesAsync(): Promise { - return new Promise((resolve, reject) => { - // simulate latency by resolving promise after a delay - setTimeout(() => resolve(HEROES.map(h => h.clone())), delay) - }) - } -} diff --git a/public/docs/_examples/testing/ts/app/bootstrap.ts b/public/docs/_examples/testing/ts/app/bootstrap.ts deleted file mode 100644 index f9e6e3e729..0000000000 --- a/public/docs/_examples/testing/ts/app/bootstrap.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {bootstrap} from 'angular2/platform/browser'; - -// Application root component -import {HeroesComponent} from './heroes.component'; - -// Application-wide "injectables"" -import {BackendService} from './backend.service'; -import {HeroService} from './hero.service'; -import {User} from './user'; - -bootstrap(HeroesComponent, [BackendService, HeroService, User]); diff --git a/public/docs/_examples/testing/ts/app/dashboard.component.css b/public/docs/_examples/testing/ts/app/dashboard.component.css new file mode 100644 index 0000000000..ce6e963a5f --- /dev/null +++ b/public/docs/_examples/testing/ts/app/dashboard.component.css @@ -0,0 +1,63 @@ +/* #docplaster */ +/* #docregion */ +[class*='col-'] { + float: left; +} +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +h3 { + text-align: center; margin-bottom: 0; +} +[class*='col-'] { + padding-right: 20px; + padding-bottom: 20px; +} +[class*='col-']:last-of-type { + padding-right: 0; +} +.grid { + margin: 0; +} +.col-1-4 { + width: 25%; +} +.module { + padding: 20px; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #607D8B; + border-radius: 2px; +} +h4 { + position: relative; +} +.module:hover { + background-color: #EEE; + cursor: pointer; + color: #607d8b; +} +.grid-pad { + padding: 10px 0; +} +.grid-pad > [class*='col-']:last-of-type { + padding-right: 20px; +} +@media (max-width: 600px) { + .module { + font-size: 10px; + max-height: 75px; } +} +@media (max-width: 1024px) { + .grid { + margin: 0; + } + .module { + min-width: 60px; + } +} +/* #enddocregion */ diff --git a/public/docs/_examples/testing/ts/app/dashboard.component.html b/public/docs/_examples/testing/ts/app/dashboard.component.html new file mode 100644 index 0000000000..a5bafd7702 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/dashboard.component.html @@ -0,0 +1,11 @@ + +

Top Heroes

+
+ +
+ +
+

{{hero.name}}

+
+
+
diff --git a/public/docs/_examples/testing/ts/app/dashboard.component.ts b/public/docs/_examples/testing/ts/app/dashboard.component.ts new file mode 100644 index 0000000000..c4c516a864 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/dashboard.component.ts @@ -0,0 +1,44 @@ +// #docplaster +// #docregion +import { Component, OnInit } from 'angular2/core'; +// #docregion import-router +import { Router } from 'angular2/router'; +// #enddocregion import-router + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + selector: 'my-dashboard', + // #docregion template-url + templateUrl: 'app/dashboard.component.html', + // #enddocregion template-url + // #docregion css + styleUrls: ['app/dashboard.component.css'] + // #enddocregion css +}) +// #docregion component +export class DashboardComponent implements OnInit { + + heroes: Hero[] = []; + +// #docregion ctor + constructor( + private _router: Router, + private _heroService: HeroService) { + } +// #enddocregion ctor + + ngOnInit() { + this._heroService.getHeroes() + .then(heroes => this.heroes = heroes.slice(1,5)); + } + + // #docregion goto-detail + gotoDetail(hero: Hero) { + let link = ['HeroDetail', { id: hero.id }]; + this._router.navigate(link); + } + // #enddocregion goto-detail +} +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/decorators.ts b/public/docs/_examples/testing/ts/app/decorators.ts deleted file mode 100644 index 09f6cd0b15..0000000000 --- a/public/docs/_examples/testing/ts/app/decorators.ts +++ /dev/null @@ -1,16 +0,0 @@ -// @Injectable is a placeholder decorator -// whose sole purpose is to trigger the TS compiler to -// generate the metadata that Angular DI needs for injection. -// -// Metadata generation happens IFF the class has a decorator ... any decorator -// See the `"emitDecoratorMetadata": true` flag in tsconfig.json -// -// For Angular-agnostic classes we can avoid importing from Angular -// and get the metadata generation side-effect -// by creating our own @Injectable decorator - -// for the hip Functional Programmer: -export const Injectable = () => (cls:any) => cls; - -// for everyone else, this is the same thing -//export function Injectable() { return (cls:any) => cls; } \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/hero-detail.component.css b/public/docs/_examples/testing/ts/app/hero-detail.component.css index 53d6789226..ab2437efd8 100644 --- a/public/docs/_examples/testing/ts/app/hero-detail.component.css +++ b/public/docs/_examples/testing/ts/app/hero-detail.component.css @@ -1,3 +1,30 @@ -.hero-detail div {padding:0.2em;} -.hero-detail div input {position: absolute; left:9em; } -.hero-id {position: absolute; left:7.5em; } \ No newline at end of file +/* #docregion */ +label { + display: inline-block; + width: 3em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} +button { + margin-top: 20px; + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #ccc; + cursor: auto; +} diff --git a/public/docs/_examples/testing/ts/app/hero-detail.component.html b/public/docs/_examples/testing/ts/app/hero-detail.component.html index 0de3139b52..cf96fc2169 100644 --- a/public/docs/_examples/testing/ts/app/hero-detail.component.html +++ b/public/docs/_examples/testing/ts/app/hero-detail.component.html @@ -1,24 +1,14 @@ + -
- -

{{hero.name | initCaps}} is {{userName}}'s current super hero!

- -
- - -
-
- {{hero.id}}
-
- - -
-
- - -
-
- - -
-
+
+

{{hero.name}} details!

+
+ {{hero.id}}
+
+ + +
+ + + +
\ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/hero-detail.component.ts b/public/docs/_examples/testing/ts/app/hero-detail.component.ts index 620eba9a29..1c06a4ee46 100644 --- a/public/docs/_examples/testing/ts/app/hero-detail.component.ts +++ b/public/docs/_examples/testing/ts/app/hero-detail.component.ts @@ -1,35 +1,57 @@ -import {Component, Directive, EventEmitter , ElementRef} from 'angular2/core'; +// #docplaster +// #docregion +// #docregion v2 +// #docregion import-oninit +import { Component, OnInit } from 'angular2/core'; +// #enddocregion import-oninit +// #docregion import-route-params +import {RouteParams} from 'angular2/router'; +// #enddocregion import-route-params -import {Hero} from './hero'; -import {InitCapsPipe} from './init-caps-pipe'; +import { Hero } from './hero'; +// #docregion import-hero-service +import { HeroService } from './hero.service'; +// #enddocregion import-hero-service -@Directive({selector: 'button'}) -class DecoratorDirective { - constructor(el: ElementRef){ - console.log(el) - } -} +// #docregion extract-template @Component({ selector: 'my-hero-detail', + // #docregion template-url templateUrl: 'app/hero-detail.component.html', - inputs: ['hero', 'userName'], // inputs - outputs: ['delete'], // outputs - directives: [DecoratorDirective], + // #enddocregion template-url +// #enddocregion v2 styleUrls: ['app/hero-detail.component.css'], - pipes: [InitCapsPipe] + inputs: ['hero'] +// #docregion v2 }) -export class HeroDetailComponent { - +// #enddocregion extract-template +// #docregion implement +export class HeroDetailComponent implements OnInit { +// #enddocregion implement hero: Hero; - delete = new EventEmitter(); - - onDelete() { this.delete.next(this.hero) } - - onUpdate() { - if (this.hero) { - this.hero.name += 'x'; - } +// #docregion ctor + constructor( + private _heroService: HeroService, + private _routeParams: RouteParams) { } - userName: string; +// #enddocregion ctor + +// #docregion ng-oninit + ngOnInit() { + // #docregion get-id + let id = +this._routeParams.get('id'); + // #enddocregion get-id + this._heroService.getHero(id) + .then(hero => this.hero = hero); + } +// #enddocregion ng-oninit + +// #docregion go-back + goBack() { + window.history.back(); + } +// #enddocregion go-back } +// #enddocregion v2 +// #enddocregion diff --git a/public/docs/_examples/testing/ts/app/hero.service.ts b/public/docs/_examples/testing/ts/app/hero.service.ts index 9483e225e7..fb7d0829cb 100644 --- a/public/docs/_examples/testing/ts/app/hero.service.ts +++ b/public/docs/_examples/testing/ts/app/hero.service.ts @@ -1,33 +1,28 @@ -//import {Injectable} from 'angular2/angular2'; // Don't get it from Angular -import {Injectable} from './decorators'; // Use the app's version -import {Hero} from './hero'; -import {BackendService} from './backend.service'; +// #docplaster +// #docregion +import { Hero } from './hero'; +import { HEROES } from './mock-heroes'; +import { Injectable } from 'angular2/core'; @Injectable() export class HeroService { + getHeroes() { + return Promise.resolve(HEROES); + } - heroes: Hero[] = []; // cache of heroes + // See the "Take it slow" appendix + getHeroesSlowly() { + return new Promise(resolve => + setTimeout(()=>resolve(HEROES), 2000) // 2 seconds + ); + } - constructor(protected _backend: BackendService) { } - - refresh() : Promise { // refresh heroes w/ latest from the server - this.heroes.length = 0; - return > this._backend.fetchAllHeroesAsync() - .then(heroes => { - this.heroes.push(...heroes); - return this.heroes; - }) - .catch(e => this._fetchFailed(e)); - } - - protected _fetchFailed(error:any) { - console.error(error); - return Promise.reject(error); - } -} - -// FOR DOCUMENTATION ONLY. NOT USED -interface IHeroService { - heroes : Hero[]; - refresh() : Promise; + //#docregion get-hero + getHero(id: number) { + return Promise.resolve(HEROES).then( + heroes => heroes.filter(hero => hero.id === id)[0] + ); + } + //#enddocregion get-hero } +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/hero.spec.ts b/public/docs/_examples/testing/ts/app/hero.spec.ts index 103915a9d5..78a73ad6b0 100644 --- a/public/docs/_examples/testing/ts/app/hero.spec.ts +++ b/public/docs/_examples/testing/ts/app/hero.spec.ts @@ -1,17 +1,17 @@ // #docregion // #docplaster // #docregion base-hero-spec -import {Hero} from './hero'; +import { Hero } from './hero'; describe('Hero', () => { - it('has name given in the constructor', () => { - let hero = new Hero(1, 'Super Cat'); + it('has name', () => { + let hero: Hero = {id: 1, name: 'Super Cat'}; expect(hero.name).toEqual('Super Cat'); }); - it('has id given in the constructor', () => { - let hero = new Hero(1, 'Super Cat'); + it('has id', () => { + let hero: Hero = {id: 1, name: 'Super Cat'}; expect(hero.id).toEqual(1); }); // #enddocregion base-hero-spec diff --git a/public/docs/_examples/testing/ts/app/hero.ts b/public/docs/_examples/testing/ts/app/hero.ts index a2ff8002e7..2b89781da5 100644 --- a/public/docs/_examples/testing/ts/app/hero.ts +++ b/public/docs/_examples/testing/ts/app/hero.ts @@ -1,19 +1,5 @@ // #docregion -let nextId = 30; - -export class Hero { - constructor( - public id?: number, - public name?: string, - public power?: string, - public alterEgo?: string - ) { - this.id = id || nextId++; - } - - clone() { return Hero.clone(this); } - - static clone = (h:any) => new Hero(h.id, h.name, h.alterEgo, h.power); - - static setNextId(next:number) { nextId = next; } +export interface Hero { + id: number; + name: string; } diff --git a/public/docs/_examples/testing/ts/app/hero2.ts b/public/docs/_examples/testing/ts/app/hero2.ts deleted file mode 100644 index 840564c4c9..0000000000 --- a/public/docs/_examples/testing/ts/app/hero2.ts +++ /dev/null @@ -1,19 +0,0 @@ -// #docregion -let nextId = 30; - -class Hero { - constructor( - public id?: number, - public name?: string, - public power?: string, - public alterEgo?: string - ) { - this.id = id || nextId++; - } - - clone() { return Hero.clone(this); } - - static clone = (h:any) => new Hero(h.id, h.name, h.alterEgo, h.power); - - static setNextId(next:number) { nextId = next; } -} diff --git a/public/docs/_examples/testing/ts/app/heroes.component.css b/public/docs/_examples/testing/ts/app/heroes.component.css index 38474e6763..d939ab565d 100644 --- a/public/docs/_examples/testing/ts/app/heroes.component.css +++ b/public/docs/_examples/testing/ts/app/heroes.component.css @@ -1,18 +1,59 @@ -.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;} - -.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } - -.heroes li:hover {color: #369; background-color: #EEE; left: .2em;} - -.heroes .badge { - font-size: small; - color: white; - padding: 0.1em 0.7em; - background-color: #369; - line-height: 1em; - position: relative; - left: -1px; - top: -1px; +.selected { + background-color: #CFD8DC !important; + color: white; +} +.heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 10em; +} +.heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; +} +.heroes .text { + position: relative; + top: -3px; +} +.heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; } -.selected { background-color: lightblue; color: #369; } -.message {padding: 0.4em 0; font-size: 20px; color: #888} \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/heroes.component.html b/public/docs/_examples/testing/ts/app/heroes.component.html index 169dc96b51..9bcd2ba4e8 100644 --- a/public/docs/_examples/testing/ts/app/heroes.component.html +++ b/public/docs/_examples/testing/ts/app/heroes.component.html @@ -1,17 +1,21 @@ -
-

{{userName}}'s Super Heroes

- -
Loading heroes...
-
Pick a hero
    -
  • - {{hero.id}} {{hero.name}} -
  • -
-
-
- - -
+ + +

My Heroes

+
    +
  • + {{hero.id}} {{hero.name}} +
  • +
+ +
+

+ + {{selectedHero.name | uppercase}} is my hero + +

+
+ + diff --git a/public/docs/_examples/testing/ts/app/heroes.component.ts b/public/docs/_examples/testing/ts/app/heroes.component.ts index 9043cbf9a1..6cdb00bc83 100644 --- a/public/docs/_examples/testing/ts/app/heroes.component.ts +++ b/public/docs/_examples/testing/ts/app/heroes.component.ts @@ -1,53 +1,50 @@ -import {Component, OnInit} from 'angular2/core'; -import {HeroDetailComponent} from './hero-detail.component'; -import {HeroService} from './hero.service'; -import {Hero} from './hero'; -import {User} from './user'; +// #docplaster +// #docregion +import { Component, OnInit } from 'angular2/core'; +import { Router } from 'angular2/router'; +import { Hero } from './hero'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroService } from './hero.service'; + +// #docregion metadata +// #docregion heroes-component-renaming @Component({ selector: 'my-heroes', +// #enddocregion heroes-component-renaming templateUrl: 'app/heroes.component.html', - directives: [HeroDetailComponent], - styleUrls: ['app/heroes.component.css'] + styleUrls: ['app/heroes.component.css'], + directives: [HeroDetailComponent] +// #docregion heroes-component-renaming }) +// #enddocregion heroes-component-renaming +// #enddocregion metadata +// #docregion class +// #docregion heroes-component-renaming export class HeroesComponent implements OnInit { - heroes: Hero[] = []; - currentHero: Hero; - userName: string; +// #enddocregion heroes-component-renaming + heroes: Hero[]; + selectedHero: Hero; - constructor(private _heroService: HeroService, private _user: User) { - this.userName = this._user.name || 'someone'; + constructor( + private _router: Router, + private _heroService: HeroService) { } + + getHeroes() { + this._heroService.getHeroes().then(heroes => this.heroes = heroes); } - getSelectedClass(hero: Hero) {return { selected: hero === this.currentHero }}; - - onDelete(hero?: Hero) { - hero = hero || this.currentHero; - let i = this.heroes.indexOf(hero); - if (i > -1) { - this.heroes.splice(i, 1); - } - this.currentHero = this.heroes[i] || this.heroes[i - 1]; + ngOnInit() { + this.getHeroes(); } - ngOnInit(){ - this.heroes = this.onRefresh(); - } - - onRefresh() { - //console.log('Refreshing heroes'); - // clear the decks - this.currentHero = undefined; - this.heroes = []; - - this._heroService.refresh() - .then(heroes => this.heroes = heroes); - - return this.heroes; - } - - onSelect(hero: Hero) { - this.currentHero = hero; - console.log(`Hero selected: ` + JSON.stringify(hero)); + onSelect(hero: Hero) { this.selectedHero = hero; } + + gotoDetail() { + this._router.navigate(['HeroDetail', { id: this.selectedHero.id }]); } +// #docregion heroes-component-renaming } +// #enddocregion heroes-component-renaming +// #enddocregion class +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/init-caps-pipe.spec.ts b/public/docs/_examples/testing/ts/app/init-caps-pipe.spec.ts deleted file mode 100644 index 2dd78f22ca..0000000000 --- a/public/docs/_examples/testing/ts/app/init-caps-pipe.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -// #docregion -// #docplaster -// #docregion base-pipe-spec -import {InitCapsPipe} from './init-caps-pipe'; - -describe('InitCapsPipe', () => { - let pipe:InitCapsPipe; - - beforeEach(() => { - pipe = new InitCapsPipe(); - }); - - it('transforms "abc" to "Abc"', () => { - expect(pipe.transform('abc')).toEqual('Abc'); - }); - - it('transforms "abc def" to "Abc Def"', () => { - expect(pipe.transform('abc def')).toEqual('Abc Def'); - }); - - it('leaves "Abc Def" unchanged', () => { - expect(pipe.transform('Abc Def')).toEqual('Abc Def'); - }); - // #enddocregion base-pipe-spec - - /* more tests we could run - - it('transforms "abc-def" to "Abc-def"', () => { - expect(pipe.transform('abc-def')).toEqual('Abc-def'); - }); - - it('transforms " abc def" to " Abc Def" (preserves spaces) ', () => { - expect(pipe.transform(' abc def')).toEqual(' Abc Def'); - }); - - */ - // #docregion base-pipe-spec -}); -// #enddocregion base-pipe-spec diff --git a/public/docs/_examples/testing/ts/app/init-caps-pipe.ts b/public/docs/_examples/testing/ts/app/init-caps-pipe.ts deleted file mode 100644 index cceb823c19..0000000000 --- a/public/docs/_examples/testing/ts/app/init-caps-pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -// #docregion -// #docregion depends-on-angular -import {Pipe, PipeTransform} from 'angular2/core'; - -@Pipe({ name: 'initCaps' }) -export class InitCapsPipe implements PipeTransform { - // #enddocregion depends-on-angular - transform(value: string) { - return value.toLowerCase().replace(/(?:^|\s)[a-z]/g, function(m) { - return m.toUpperCase(); - }); - } - // #docregion depends-on-angular -} -// #enddocregion depends-on-angular diff --git a/public/docs/_examples/testing/ts/app/main.ts b/public/docs/_examples/testing/ts/app/main.ts new file mode 100644 index 0000000000..c469e18fd0 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/main.ts @@ -0,0 +1,4 @@ +import { bootstrap } from 'angular2/platform/browser'; +import { AppComponent } from './app.component'; + +bootstrap(AppComponent); \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/mock-heroes.ts b/public/docs/_examples/testing/ts/app/mock-heroes.ts index 11b9e8ff73..cdcba35097 100644 --- a/public/docs/_examples/testing/ts/app/mock-heroes.ts +++ b/public/docs/_examples/testing/ts/app/mock-heroes.ts @@ -1,70 +1,16 @@ -import {Hero} from './hero'; +// #docregion +import { Hero } from './hero'; export var HEROES: Hero[] = [ - { - "id": 11, - "name": "Mr. Nice", - "alterEgo": "Walter Meek", - "power": "Empathy" - }, - { - "id": 12, - "name": "Narco", - "alterEgo": "Nancy Knight", - "power": "Drowsiness" - }, - { - "id": 13, - "name": "Bombasto", - "alterEgo": "Bob LaRue", - "power": "Hypersound" - }, - { - "id": 14, - "name": "Celeritas", - "alterEgo": "Larry Plodder", - "power": "Super speed" - }, - { - "id": 15, - "name": "Magneta", - "alterEgo": "Julie Ohm", - "power": "Master of electro-magnetic fields" - }, - { - "id": 16, - "name": "Rubber Man", - "alterEgo": "Jimmy Longfellow", - "power": "Super flexible" - }, - { - "id": 17, - "name": "Dynama", - "alterEgo": "Shirley Knots", - "power": "Incredible strength" - }, - { - "id": 18, - "name": "Dr IQ", - "alterEgo": "Chuck Overstreet", - "power": "Really smart" - }, - { - "id": 19, - "name": "Magma", - "alterEgo": "Harvey Klue", - "power": "Super hot" - }, - { - "id": 20, - "name": "Tornado", - "alterEgo": "Ted Baxter", - "power": "Weather changer" - }, - { - "id": 21, - "name": "eeny weenie", - "alterEgo": "Ima Small", - "power": "shrink to infinitesimal size" - } - ].map(h => Hero.clone(h)); \ No newline at end of file + {"id": 11, "name": "Mr. Nice"}, + {"id": 12, "name": "Narco"}, + {"id": 13, "name": "Bombasto"}, + {"id": 14, "name": "Celeritas"}, + {"id": 15, "name": "Magneta"}, + {"id": 16, "name": "RubberMan"}, + {"id": 17, "name": "Dynama"}, + {"id": 18, "name": "Dr IQ"}, + {"id": 19, "name": "Magma"}, + {"id": 20, "name": "Tornado"} +]; +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/app/my-uppercase.pipe.1.ts b/public/docs/_examples/testing/ts/app/my-uppercase.pipe.1.ts new file mode 100644 index 0000000000..4914151579 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/my-uppercase.pipe.1.ts @@ -0,0 +1,9 @@ +// #docregion +import {Pipe, PipeTransform} from 'angular2/core'; + +@Pipe({ name: 'my-uppercase' }) +export class MyUppercasePipe implements PipeTransform { + transform(value: string) { + return value; + } +} diff --git a/public/docs/_examples/testing/ts/app/my-uppercase.pipe.spec.ts b/public/docs/_examples/testing/ts/app/my-uppercase.pipe.spec.ts new file mode 100644 index 0000000000..fc9952f6b2 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/my-uppercase.pipe.spec.ts @@ -0,0 +1,41 @@ +// #docregion +// #docplaster +// #docregion base-pipe-spec +import { MyUppercasePipe } from './my-uppercase.pipe'; + +describe('MyUppercasePipe', () => { + let pipe : MyUppercasePipe; + + beforeEach(() => { + pipe = new MyUppercasePipe(); + }); + + // #docregion expectations + it('transforms "abc" to "ABC"', () => { + expect(pipe.transform('abc')).toEqual('ABC'); + }); + + it('transforms "abc def" to "ABC DEF"', () => { + expect(pipe.transform('abc def')).toEqual('ABC DEF'); + }); + + it('leaves "ABC DEF" unchanged', () => { + expect(pipe.transform('ABC DEF')).toEqual('ABC DEF'); + }); + // #enddocregion expectations + // #enddocregion base-pipe-spec + + /* more tests we could run + + it('transforms "abc-def" to "Abc-def"', () => { + expect(pipe.transform('abc-def')).toEqual('Abc-def'); + }); + + it('transforms " abc def" to " Abc Def" (preserves spaces) ', () => { + expect(pipe.transform(' abc def')).toEqual(' Abc Def'); + }); + + */ + // #docregion base-pipe-spec +}); +// #enddocregion base-pipe-spec diff --git a/public/docs/_examples/testing/ts/app/my-uppercase.pipe.ts b/public/docs/_examples/testing/ts/app/my-uppercase.pipe.ts new file mode 100644 index 0000000000..4b51e14091 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/my-uppercase.pipe.ts @@ -0,0 +1,13 @@ +// #docregion +// #docregion depends-on-angular +import {Pipe, PipeTransform} from 'angular2/core'; +// #enddocregion depends-on-angular + +@Pipe({ name: 'my-uppercase' }) +export class MyUppercasePipe implements PipeTransform { + // #docregion uppercase + transform(value: string) { + return value.toUpperCase(); + } + // #enddocregion uppercase +} diff --git a/public/docs/_examples/testing/ts/app/hero-detail.component.spec.ts.not-yet b/public/docs/_examples/testing/ts/app/old-specs/hero-detail.component.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/hero-detail.component.spec.ts.not-yet rename to public/docs/_examples/testing/ts/app/old-specs/hero-detail.component.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/hero-detail.component.wrapped-tests.spec.ts.not-yet b/public/docs/_examples/testing/ts/app/old-specs/hero-detail.component.wrapped-tests.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/hero-detail.component.wrapped-tests.spec.ts.not-yet rename to public/docs/_examples/testing/ts/app/old-specs/hero-detail.component.wrapped-tests.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/hero.service.ng.spec.ts b/public/docs/_examples/testing/ts/app/old-specs/hero.service.ng.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/hero.service.ng.spec.ts rename to public/docs/_examples/testing/ts/app/old-specs/hero.service.ng.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/hero.service.no-ng.1.spec.ts b/public/docs/_examples/testing/ts/app/old-specs/hero.service.no-ng.1.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/hero.service.no-ng.1.spec.ts rename to public/docs/_examples/testing/ts/app/old-specs/hero.service.no-ng.1.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/hero.service.no-ng.spec.ts b/public/docs/_examples/testing/ts/app/old-specs/hero.service.no-ng.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/hero.service.no-ng.spec.ts rename to public/docs/_examples/testing/ts/app/old-specs/hero.service.no-ng.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/heroes.component.ng.spec.ts.not-yet b/public/docs/_examples/testing/ts/app/old-specs/heroes.component.ng.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/heroes.component.ng.spec.ts.not-yet rename to public/docs/_examples/testing/ts/app/old-specs/heroes.component.ng.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/heroes.component.no-ng.spec.ts b/public/docs/_examples/testing/ts/app/old-specs/heroes.component.no-ng.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/heroes.component.no-ng.spec.ts rename to public/docs/_examples/testing/ts/app/old-specs/heroes.component.no-ng.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/user.spec.ts b/public/docs/_examples/testing/ts/app/old-specs/user.spec.ts.not-yet similarity index 100% rename from public/docs/_examples/testing/ts/app/user.spec.ts rename to public/docs/_examples/testing/ts/app/old-specs/user.spec.ts.not-yet diff --git a/public/docs/_examples/testing/ts/app/user.ts b/public/docs/_examples/testing/ts/app/user.ts deleted file mode 100644 index 247979c59a..0000000000 --- a/public/docs/_examples/testing/ts/app/user.ts +++ /dev/null @@ -1,6 +0,0 @@ -// imagine this is the result of a login -export class User { - id = 42; - name = 'Bongo'; - email = 'bongo@amazing.io' -}; diff --git a/public/docs/_examples/testing/ts/index.html b/public/docs/_examples/testing/ts/index.html index 44e37b274f..0e91f05760 100644 --- a/public/docs/_examples/testing/ts/index.html +++ b/public/docs/_examples/testing/ts/index.html @@ -1,27 +1,39 @@ + + + + + + Angular 2 Tour of Heroes + - - - - - - - - - - + + + + + + + + + - - - - + + + + + + + + + + + + Loading... + diff --git a/public/docs/_examples/testing/ts/unit-tests-4.html b/public/docs/_examples/testing/ts/unit-tests-4.html index fffc849ea0..51fc798a7e 100644 --- a/public/docs/_examples/testing/ts/unit-tests-4.html +++ b/public/docs/_examples/testing/ts/unit-tests-4.html @@ -12,16 +12,8 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-5.html.not-yet b/public/docs/_examples/testing/ts/unit-tests-5.html.not-yet deleted file mode 100644 index df8e3704ba..0000000000 --- a/public/docs/_examples/testing/ts/unit-tests-5.html.not-yet +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - Ng App Unit Tests - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet b/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet index d5449711ee..df8e3704ba 100644 --- a/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet +++ b/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet @@ -9,37 +9,35 @@ - - - - - - + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-7.html.not-yet b/public/docs/_examples/testing/ts/unit-tests-7.html.not-yet new file mode 100644 index 0000000000..d5449711ee --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-7.html.not-yet @@ -0,0 +1,46 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/docs/ts/latest/guide/testing.jade b/public/docs/ts/latest/guide/testing.jade index 848eaf1b8f..4859c9551c 100644 --- a/public/docs/ts/latest/guide/testing.jade +++ b/public/docs/ts/latest/guide/testing.jade @@ -17,15 +17,16 @@ a(id="top") 1. [The Application Under Test](#aut) - 1. [Test a class](#first-app-tests) - - test a simple application class outside of Angular + 1. [First app test](#first-app-tests) + - test a simple application interface outside of Angular - where to put the test file - load a test file with systemJS - 1. [Test a Pipe](#pipe-testing) - - test a simple Angular Pipe class - - add the Angular 2 library to the test harness - - load multiple test files using system.js + 1. [Pipe driven development](#pipe-testing) + - create a test before creating a class + - load multiple test files in our test harness, using system.js + - add the Angular 2 library to our test harness + - watch the new test fail, and fix it 1. Test an Asynchronous Service (forthcoming) - test an asynchronous service class outside of Angular @@ -79,14 +80,14 @@ a(href="#top").to-top Back to top .l-hr a(id="first-app-tests") :marked - # Testing a Class + # First app test != partial("../testing/first-app-tests") a(href="#top").to-top Back to top .l-hr a(id="pipe-testing") :marked - # Testing an Angular Pipe + # Pipe driven development != partial("../testing/testing-an-angular-pipe") a(href="#top").to-top Back to top diff --git a/public/docs/ts/latest/testing/application-under-test.jade b/public/docs/ts/latest/testing/application-under-test.jade index c50ee23ff9..01bdb48c27 100644 --- a/public/docs/ts/latest/testing/application-under-test.jade +++ b/public/docs/ts/latest/testing/application-under-test.jade @@ -1,33 +1,10 @@ include ../_util-fns :marked - We’ll need an Angular application to test, one as simple as possible while having all the angular features we want to test. + We’ll need an Angular application to test, one as simple as possible while having most of the angular features we want to test. - It’s a one-screen variation on the “Tour of Heroes” that should be familiar to you as a reader of this Developers Guide. - - Our test app displays a list of heroes - all favorites of the user named “Bongo”. It looks like this: - -figure.image-display - img(src='/resources/images/devguide/application-under-test/bongos-heroes.png' - style="width:250px;" alt="Bongo's Heroes") - -:marked - At the top is a master list of heroes; at the bottom the detail for the current hero. Click a hero in the list to change the current hero. Change the name in the textbox and that name updates everywhere. The *Update* button modifies the `Hero.name` in an arbitrary way and that change also propagates everywhere on screen. The *Delete* button deletes the hero from the list and a new hero becomes current. *Refresh* clears both the list and detail, then restores the original list of heroes. - - - - This simple app illustrates a number of Angular features that we’d like to test. - - - A simple service that presents the `username` (“Bongo”) - - A dataservice that fetches and caches the list of heroes. - - The dataservice depends in turn on another “backend” service that handles the interaction with a remote web api - - A master `HeroesComponent` presents the list - - The master communicates with a detail component `HeroDetailComponent` about the current hero both through an attribute and an event. - - The detail’s template is nested within the master component’s template. - - The `name` textbox illustrates two-way databinding - - The update button demonstrates that a programmatic change to the databound model propagates to both component views - - The delete button triggers an event that is caught by the parent component - - - - We’ll examine the implementation details as we evolve our tests. + What better app than our own [The Tour of Heroes](../tutorial/toh-pt5)? We're already quite familiar with it and it fits our criteria, so let's try to test what we've done there. + + We might end up modifying it a bit, because it doesn't have everything we want to test, but it's the perfect starting point. + + Create a copy of the Tour of Heroes app so that we can fiddle without fear. diff --git a/public/docs/ts/latest/testing/first-app-tests.jade b/public/docs/ts/latest/testing/first-app-tests.jade index faa57bbf73..3df9bf04bb 100644 --- a/public/docs/ts/latest/testing/first-app-tests.jade +++ b/public/docs/ts/latest/testing/first-app-tests.jade @@ -3,7 +3,7 @@ include ../_util-fns :marked In this chapter we'll setup the environment for testing our sample application and write a few easy Jasmine tests of the app's simplest parts. We'll learn: - - to test one of our application classes + - to test one of our application files - why we prefer our test files to be next to their corresponding source files - to run tests with an `npm` command - load the test file with SystemJS @@ -21,9 +21,7 @@ include ../_util-fns :marked ## Create the test-runner HTML - Step away from the Jasmine 101 folder and turn to the root folder of the application that we downloaded in the previous chapter. - - Locate the `src` folder that contains the application `index.html` + Locate the folder that contains the application `index.html` for your testing copy of Tour of Heroes. Create a new, sibling HTML file, ** `unit-tests.html` ** and copy over the same basic material from the `unit-tests.html` in the [Jasmine 101](./jasmine-testing-101.html) chapter. @@ -36,10 +34,6 @@ include ../_util-fns :marked ## Update `package.json` for testing - We'll assume that the application has `package.json` file that looks more or less like - the one we prescribed in the in the "Install npm packages locally" section of the - [QuickStart](../quickstart.html). - We must install the Jasmine package as well: pre.prettyprint.lang-bash @@ -50,17 +44,19 @@ pre.prettyprint.lang-bash :marked Let's make one more change to the `package.json` script commands. - **Open the `package.json` ** and scroll to the `scripts` node. Look for the command named `test`. Change it to: + **Open the `package.json` ** and scroll to the `scripts` node and add in a new one: - "test": "live-server --open=src/unit-tests.html" +code-example(format=""). + "test": "live-server --open=unit-tests.html" +:marked That command will launch `live-server` and open a browser to the `unit-tests.html` page we just wrote. .l-main-section :marked ## First app tests - We can start testing *some* of our app right away. For example, we can test the `Hero` class: + We can start testing *some* of our app right away. For example, we can test the `Hero` interface: +makeExample('testing/ts/app/hero.ts') @@ -104,7 +100,7 @@ pre.prettyprint.lang-bash :marked ## First spec file - **Create** a new file, ** `hero.spec.ts` ** in `src/app` next to `hero.ts`. + **Create** a new file, ** `hero.spec.ts` ** in `app` next to `hero.ts`. Notice the ".spec" suffix in the test file's filename, appended to the name of the file holding the application part we're testing. @@ -120,7 +116,7 @@ pre.prettyprint.lang-bash We have an `import {Hero} from './hero' ` statement. - If we forgot this import, a TypeScript-aware editor would warn us, with a squiggly red underline, that it can't find the definition of the `Hero` class. + If we forgot this import, a TypeScript-aware editor would warn us, with a squiggly red underline, that it can't find the definition of the `Hero` interface. ### Update unit-tests.html @@ -150,7 +146,7 @@ code-example(format="" language="html"). The immediate cause of the error is the `export` statement in `hero.ts`. That error was there all along. - It wasn't a problem until we tried to `import` the `Hero` class in our tests. + It wasn't a problem until we tried to `import` the `Hero` interface in our tests. Our test environment lacks support for module loading. Apparently we can't simply load our application and test scripts like we do with 3rd party JavaScript libraries. @@ -211,7 +207,7 @@ figure.image-display :marked ## What's Next? We are able to test a part of our application with simple Jasmine tests. - The part was a stand-alone class that made no mention or use of Angular. + The part was a stand-alone interface that made no mention or use of Angular. That's not rare but it's not typical either. Most of our application parts make some use of the Angular framework. diff --git a/public/docs/ts/latest/testing/testing-an-angular-pipe.jade b/public/docs/ts/latest/testing/testing-an-angular-pipe.jade index 0702ff8723..fbbe2f13e5 100644 --- a/public/docs/ts/latest/testing/testing-an-angular-pipe.jade +++ b/public/docs/ts/latest/testing/testing-an-angular-pipe.jade @@ -1,26 +1,45 @@ include ../_util-fns :marked - We’ll test an Angular pipe in this chapter + We’ll test an Angular pipe in this chapter. An Angular pipe is a declarative way in HTML to transform some input into some displayable output. + + We don't have a pipe though, since in Tour of Heroes we didn't create any pipes. It uses a pipe though, the `uppercase` pipe that comes with Angular 2. + + We can make our own `my-uppercase` pipe that does exactly the same as the `uppercase` pipe and test that. + + Since we're getting ready to write some code we want to test, let's take this opportunity to talk just a little bit about [Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development). There's a lot written about this topic so we don't want to have an exhaustive description here, but rather a practical application. + + We already know *exactly* what we want the `uppercase` pipe to do. We could say our ...expectations... of it are very well defined. + + We always use expectations our expectations to guide development, but sometimes it's hard to see the forest for the trees when we're right in the middle of coding. This is especially evident in larger tasks. + + So one thing we can do is put those expectations down as cold hard test code. We were going to test things manually anyway, so doing it *before* we have even one line of code isn't going to hurt. + + Worst thing that can happen is have that test fail, but on the way to fixing it we'll end up creating our pipe. So in a sense, the failing test will *tell you what it wants* to pass. + + We're just putting down expectations, nothing more. If we were to put them down on paper, they would look like this: - We’ll look at our app’s custom `InitCapsPipe` that converts a string of words into a string of capitalized words. - - We use it our `hero-detail.component.html` template to turn a hero name like “eeny weenie” into “Eeny Weenie” - -+makeExample('testing/ts/app/hero-detail.component.html', 'pipe-usage') - -:marked - The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief: - -+makeExample('testing/ts/app/init-caps-pipe.ts') + ``` + MyUppercasePipe + transforms "abc" to "ABC" + transforms "abc def" to "ABC DEF" + leaves "ABC DEF" unchanged + ``` + + All we need to know to put down our expectations as code is how a pipe class looks like from the outside. From the [pipe developer guide](pipes#custom-pipes) we know that a pipe implements a `transform` method. + + Putting it down as Jasmine expectations, they would look something like this: + ++makeExample('testing/ts/app/my-uppercase.pipe.spec.ts', 'expectations') :marked In this chapter we will: - - add the Angular 2 library to our test harness - - test this custom Angular pipe class + - create a test before creating a class - load multiple test files in our test harness, using system.js + - add the Angular 2 library to our test harness + - watch the new test fail, and fix it .callout.is-helpful header Prior Knowledge @@ -31,53 +50,16 @@ include ../_util-fns the [Tour of Heroes](../tutorial/) tutorial such as npm, gulp, and live-server. -:marked - ## Add the Angular library - Looking back at `unit-tests.html` we realize that we have not loaded the Angular library. - Yet we were able to load and test the application’s `Hero` class. - - **We were lucky!** The `Hero` class has no dependence on Angular. - If it had depended on Angular, we’d still be staring at the Jasmine “big-time fail” screen: - -figure.image-display - img(src='/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png' - style="width:400px;" alt="Jasmine's' big time fail screen") - -:marked - If we then opened the browser’s Developer Tools (F12, Ctrl-Shift-I) and looked - in the console window, we would see that SystemJS - tried to load Angular and couldn't find it. - -code-example(format="" language="html" escape="html"). - GET http://127.0.0.1:8080/src/angular2/core 404 (Not Found) - -:marked - We are writing an Angular application afterall and - we were going to need Angular sooner or later. That time has come. - - The `InitCapsPipe` depends on Angular as is clear in the first few lines: - -+makeExample('testing/ts/app/init-caps-pipe.ts', 'depends-on-angular')(format=".") - -:marked - **Open** `unit-tests.html` - - **Find** the `src="../node_modules/systemjs/dist/system.src.js">` - - **Replace** Step #1 with these two scripts: - -+makeExample('testing/ts/unit-tests-4.html', 'import-angular')(format=".") - :marked ## Add another spec file - **Create** an `init-caps-pipe.spec.ts` next to `init-caps-pipes.ts` in `src/app` + **Create** a `my-uppercase.pipe.spec.ts` in `app/`. **Stop and restart the TypeScript compiler** to ensure we compile the new file. - **Add** the following lines of rather obvious Jasmine test code + **Add** the following lines of rather obvious Jasmine test code. -+makeExample('testing/ts/app/init-caps-pipe.spec.ts', 'base-pipe-spec') ++makeExample('testing/ts/app/my-uppercase.pipe.spec.ts', 'base-pipe-spec', 'app/my-uppercase.pipe.spec.ts') :marked Note that each test is short (one line in our case). @@ -95,7 +77,7 @@ code-example(format="" language="html" escape="html"). Open `unit-tests.html`. Find `System.import('app/hero.spec')`. - Hmm. We can’t just add `System.import('app/init-caps-pipe.spec')`. + Hmm. We can’t just add `System.import('app/my-uppercase.pipe.spec')`. The first `System.import` returns a promise as does this second import. We can’t run any of the Jasmine tests until **both imports are finished**. @@ -105,19 +87,109 @@ code-example(format="" language="html" escape="html"). +makeExample('testing/ts/unit-tests-4.html', 'promise-all')(format=".") -:marked - Try it. The browser should refresh and show - -figure.image-display - img(src='/resources/images/devguide/testing-an-angular-pipe/5-specs-0-failures.png' - style="width:400px;" alt="import promises 5 specs, 0 failures") - :marked We have a pattern for adding new tests. In future, when we add a new spec, we add another `System.import('app/some.spec')` to the array argument passed to `Promise.all`. + + Try it. The browser should refresh and show the following in the console: +code-example(format="" language="html" escape="html"). + GET http://localhost:8080/app/my-uppercase.pipe.js 404 (Not Found) + +:marked + Our test failed, as expected. We're importing something that doesn't exist and our test fails saying that. All is going according to plan. + +:marked + ## The pipe, if you please + + The test is asking for a pipe, and we shall deliver. + + **Create** a `my-uppercase.pipe.ts` in `app/`. + + **Stop and restart the TypeScript compiler** to ensure we compile the new file. + + **Add** a basic pipe that doesn't do anything. We know how to make strings uppercase, but we since we're letting the test take the lead let's wait for it to tell us what's next. Maybe it'll surprise us. + ++makeExample('testing/ts/app/my-uppercase.pipe.1.ts', null, 'app/my-uppercase.pipe.ts') + +:marked + Reload our test page and... + +code-example(format="" language="html" escape="html"). + GET http://localhost:8080/angular2/core 404 (Not Found) + +:marked + ## The Angular library, if you please + Looking back at `unit-tests.html` we realize that we have not loaded the Angular library. + Yet we were able to load and test the application’s `Hero` interface. + + **We were lucky!** The `Hero` interface has no dependence on Angular. + If it had depended on Angular, we’d still be staring at the Jasmine “big-time fail” screen: + +figure.image-display + img(src='/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png' + alt="Jasmine's' big time fail screen") + +:marked + We are writing an Angular application after all and + we were going to need Angular sooner or later. That time has come. + + `MyUppercasePipe` depends on Angular as is clear in the first few lines: + ++makeExample('testing/ts/app/my-uppercase.pipe.ts', 'depends-on-angular')(format=".") + +:marked + **Open** `unit-tests.html` + + **Find** the `` comment. + + **Replace** the scripts tags beneath it with the all the needed angular scripts: + ++makeExample('testing/ts/unit-tests-5.html', 'import-angular')(format=".") + +:marked + We should now be ready to see our 3 expectations fail when reloading our test page. + +figure.image-display + img(src='/resources/images/devguide/testing-an-angular-pipe/two-failures.png' alt="2 failed tests") + +:marked + ## Uppercase, if you please + + The first two tests that passed were our old `hero` interface tests, so it makes sense that those passed. Of our three new expectations, one still passed though. + + ``` + MyUppercasePipe + transforms "abc" to "ABC" + transforms "abc def" to "ABC DEF" + leaves "ABC DEF" unchanged + ``` + + Ah but of course! Our simple pipe doesn't transform the input at all, and the third test expected + input to not be changed. + + All we have to do now is actually transform text to uppercase in our pipe. + ++makeExample('testing/ts/app/my-uppercase.pipe.ts', 'uppercase')(format=".") + +:marked + Are we done now? + +figure.image-display + img(src='/resources/images/devguide/testing-an-angular-pipe/zero-failures.png' alt="0 failed tests") + +:marked + The glorious green is back with us again! + + We tried a bit of test driven development and it seems to have guided us to success. + + But it's not always feasible. For instance, sometimes we need to write tests for existing functionality, like what we're about to do with the rest of Tour of Heroes. + + If we are writing new code though, writing tests might just be what we need to help us track our progress and keep the end result in sight at all times. + +:marked ## What’s Next? Now we can test parts of our application that we *load* asynchronously with system.js. diff --git a/public/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png b/public/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png index 65f51f051e407167734012e389f791d740257965..f78f1efc052074acbe8b2291cfd4154fd4ace4c2 100644 GIT binary patch literal 2091 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 SRn>mH1%NNw-?MrTp88+3r`7BL literal 9275 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 diff --git a/public/resources/images/devguide/testing-an-angular-pipe/two-failures.png b/public/resources/images/devguide/testing-an-angular-pipe/two-failures.png new file mode 100644 index 0000000000000000000000000000000000000000..e21b8e7e78ee4999e031efb3042b52c6f765e448 GIT binary patch literal 3056 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 QQ##1h(9)m?^Lxy{0O5PH(*OVf literal 0 HcmV?d00001 diff --git a/public/resources/images/devguide/testing-an-angular-pipe/zero-failures.png b/public/resources/images/devguide/testing-an-angular-pipe/zero-failures.png new file mode 100644 index 0000000000000000000000000000000000000000..e7bb6a475c521a4a05abb4af5389c170f164df19 GIT binary patch literal 5713 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