parent
45bc6ed844
commit
803414ae80
|
@ -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 */
|
|
@ -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: `
|
||||
<h1>{{title}}</h1>
|
||||
<nav>
|
||||
<a [routerLink]="['Dashboard']">Dashboard</a>
|
||||
<a [routerLink]="['Heroes']">Heroes</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
// #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
|
|
@ -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<Hero[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// simulate latency by resolving promise after a delay
|
||||
setTimeout(() => resolve(HEROES.map(h => h.clone())), delay)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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]);
|
|
@ -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 */
|
|
@ -0,0 +1,11 @@
|
|||
<!-- #docregion -->
|
||||
<h3>Top Heroes</h3>
|
||||
<div class="grid grid-pad">
|
||||
<!-- #docregion click -->
|
||||
<div *ngFor="#hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
|
||||
<!-- #enddocregion click -->
|
||||
<div class="module hero">
|
||||
<h4>{{hero.name}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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
|
|
@ -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; }
|
|
@ -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; }
|
||||
/* #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;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,14 @@
|
|||
<!-- #docplaster -->
|
||||
<!-- #docregion -->
|
||||
<div class="hero-detail">
|
||||
<!-- #docregion pipe-usage -->
|
||||
<h2>{{hero.name | initCaps}} is {{userName}}'s current super hero!</h2>
|
||||
<!-- #enddocregion pipe-usage -->
|
||||
<div>
|
||||
<button (click)="onDelete()" [disabled]="!hero">Delete</button>
|
||||
<button (click)="onUpdate()" [disabled]="!hero">Update</button>
|
||||
</div>
|
||||
<div>
|
||||
<label>Id: </label><span class="hero-id">{{hero.id}}</span></div>
|
||||
<div>
|
||||
<label>Name: </label>
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
</div>
|
||||
<div>
|
||||
<label>Power: </label>
|
||||
<input [(ngModel)]="hero.power" placeholder="super power">
|
||||
</div>
|
||||
<div>
|
||||
<label>Alter Ego: </label>
|
||||
<input [(ngModel)]="hero.alterEgo" placeholder="alter ego">
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="hero">
|
||||
<h2>{{hero.name}} details!</h2>
|
||||
<div>
|
||||
<label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="hero.name" placeholder="name" />
|
||||
</div>
|
||||
<!-- #docregion back-button -->
|
||||
<button (click)="goBack()">Back</button>
|
||||
<!-- #enddocregion back-button -->
|
||||
</div>
|
|
@ -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
|
||||
|
|
|
@ -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<Hero[]>(resolve =>
|
||||
setTimeout(()=>resolve(HEROES), 2000) // 2 seconds
|
||||
);
|
||||
}
|
||||
|
||||
constructor(protected _backend: BackendService) { }
|
||||
|
||||
refresh() : Promise<Hero[]> { // refresh heroes w/ latest from the server
|
||||
this.heroes.length = 0;
|
||||
return <Promise<Hero[]>> 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<Hero[]>;
|
||||
//#docregion get-hero
|
||||
getHero(id: number) {
|
||||
return Promise.resolve(HEROES).then(
|
||||
heroes => heroes.filter(hero => hero.id === id)[0]
|
||||
);
|
||||
}
|
||||
//#enddocregion get-hero
|
||||
}
|
||||
// #enddocregion
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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}
|
|
@ -1,17 +1,21 @@
|
|||
<div class="container">
|
||||
<h1>{{userName}}'s Super Heroes</h1>
|
||||
<button (click)="onRefresh(h)">Refresh</button>
|
||||
<div class="message" *ngIf="!heroes.length" class="heroes">Loading heroes...</div>
|
||||
<div class="message" *ngIf="heroes.length" class="heroes">Pick a hero</div> <ul class="heroes">
|
||||
<li *ngFor="#hero of heroes"
|
||||
[ngClass]="getSelectedClass(hero)"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="currentHero">
|
||||
<hr/>
|
||||
<my-hero-detail [hero]="currentHero" [user-name]="userName" (delete)="onDelete()">
|
||||
</my-hero-detail>
|
||||
</div>
|
||||
<!-- #docplaster -->
|
||||
<!-- #docregion -->
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="#hero of heroes"
|
||||
[class.selected]="hero === selectedHero"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<!-- #docregion mini-detail -->
|
||||
<div *ngIf="selectedHero">
|
||||
<h2>
|
||||
<!-- #docregion pipe -->
|
||||
{{selectedHero.name | uppercase}} is my hero
|
||||
<!-- #enddocregion pipe -->
|
||||
</h2>
|
||||
<button (click)="gotoDetail()">View Details</button>
|
||||
</div>
|
||||
<!-- #enddocregion mini-detail -->
|
||||
<!-- #enddocregion -->
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
import { bootstrap } from 'angular2/platform/browser';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent);
|
|
@ -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));
|
||||
{"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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// imagine this is the result of a login
|
||||
export class User {
|
||||
id = 42;
|
||||
name = 'Bongo';
|
||||
email = 'bongo@amazing.io'
|
||||
};
|
|
@ -1,27 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- #docregion head -->
|
||||
<!-- #docregion base-href -->
|
||||
<head>
|
||||
<base href="/">
|
||||
<!-- #enddocregion base-href -->
|
||||
<title>Angular 2 Tour of Heroes</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
</head>
|
||||
<!-- #enddocregion head -->
|
||||
<!-- #docregion css -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<!-- #enddocregion css -->
|
||||
<!-- #docregion head -->
|
||||
<!-- IE required polyfills, in this exact order -->
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
|
||||
|
||||
<body>
|
||||
<my-heroes></my-heroes>
|
||||
<script>
|
||||
System.config({
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'}
|
||||
}
|
||||
});
|
||||
System.import('app/bootstrap.js');
|
||||
</script>
|
||||
</body>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<!-- #docregion router -->
|
||||
<script src="node_modules/angular2/bundles/router.dev.js"></script>
|
||||
<!-- #enddocregion router -->
|
||||
<script>
|
||||
System.config({packages: {app: {format: 'register', defaultExtension: 'js'}}});
|
||||
System.import('app/main')
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
<!-- #enddocregion head -->
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,16 +12,8 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<!-- #docregion import-angular -->
|
||||
<!-- #1. add the system.js and angular libraries -->
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<!-- #enddocregion import-angular -->
|
||||
|
||||
<!-- #docregion promise-all -->
|
||||
<script>
|
||||
|
@ -36,7 +28,7 @@
|
|||
// #3. Import the spec files explicitly
|
||||
Promise.all([
|
||||
System.import('app/hero.spec'),
|
||||
System.import('app/init-caps-pipe.spec')
|
||||
System.import('app/my-uppercase.pipe.spec')
|
||||
])
|
||||
|
||||
// #4. wait for all imports to load ...
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<title>Ng App Unit Tests</title>
|
||||
<link rel="stylesheet" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- #docregion import-angular -->
|
||||
<!-- #1. add the system.js and angular libraries -->
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<!-- #enddocregion import-angular -->
|
||||
|
||||
<script>
|
||||
// #2. Configure systemjs to use the .js extension
|
||||
// for imports from the app folder
|
||||
System.config({
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'}
|
||||
}
|
||||
});
|
||||
|
||||
// #3. Import the spec files explicitly
|
||||
Promise.all([
|
||||
System.import('app/hero.spec'),
|
||||
System.import('app/my-uppercase.pipe.spec')
|
||||
])
|
||||
|
||||
// #4. wait for all imports to load ...
|
||||
// then re-execute `window.onload` which
|
||||
// triggers the Jasmine test-runner start
|
||||
// or explain what went wrong.
|
||||
.then(window.onload)
|
||||
.catch(console.error.bind(console));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,44 +0,0 @@
|
|||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<title>Ng App Unit Tests</title>
|
||||
<link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- #1. add the system.js and angular libraries -->
|
||||
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
|
||||
<script>
|
||||
// #2. Configure systemjs to use the .js extension
|
||||
// for imports from the app folder
|
||||
System.config({
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'}
|
||||
}
|
||||
});
|
||||
|
||||
// #3. Import the spec files explicitly
|
||||
Promise.all([
|
||||
System.import('app/hero.spec'),
|
||||
System.import('app/init-caps-pipe.spec'),
|
||||
System.import('app/hero.service.no-ng.1.spec')
|
||||
])
|
||||
|
||||
// #4. wait for all imports to load ...
|
||||
// then re-execute `window.onload` which
|
||||
// triggers the Jasmine test-runner start
|
||||
// or explain what went wrong.
|
||||
.then(window.onload)
|
||||
.catch(console.error.bind(console));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -9,37 +9,35 @@
|
|||
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
|
||||
|
||||
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
|
||||
<script src="../node_modules/angular2/bundles/testing.js"></script>
|
||||
<script src="../node_modules/zone.js/dist/jasmine-patch.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- #1. add the system.js and angular libraries -->
|
||||
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;
|
||||
// #2. Configure systemjs to use the .js extension
|
||||
// for imports from the app folder
|
||||
System.config({
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'}
|
||||
}
|
||||
});
|
||||
|
||||
System.config({
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'},
|
||||
'test-helpers': {defaultExtension: 'js'}
|
||||
}
|
||||
});
|
||||
// #3. Import the spec files explicitly
|
||||
Promise.all([
|
||||
System.import('app/hero.spec'),
|
||||
System.import('app/init-caps-pipe.spec'),
|
||||
System.import('app/hero.service.no-ng.1.spec')
|
||||
])
|
||||
|
||||
var imports = [
|
||||
'app/hero.spec',
|
||||
'app/init-caps-pipe.spec',
|
||||
'app/hero.service.no-ng.spec',
|
||||
'app/hero.service.ng.spec',
|
||||
].map(function(spec) {return System.import(spec);});
|
||||
|
||||
Promise.all(imports)
|
||||
.then(window.onload) // re-execute Jasmine's buildup
|
||||
.catch(console.error.bind(console));
|
||||
})();
|
||||
// #4. wait for all imports to load ...
|
||||
// then re-execute `window.onload` which
|
||||
// triggers the Jasmine test-runner start
|
||||
// or explain what went wrong.
|
||||
.then(window.onload)
|
||||
.catch(console.error.bind(console));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<title>Ng App Unit Tests</title>
|
||||
<link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||
<script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
|
||||
|
||||
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
|
||||
<script src="../node_modules/angular2/bundles/testing.js"></script>
|
||||
<script src="../node_modules/zone.js/dist/jasmine-patch.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
(function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;
|
||||
|
||||
System.config({
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'},
|
||||
'test-helpers': {defaultExtension: 'js'}
|
||||
}
|
||||
});
|
||||
|
||||
var imports = [
|
||||
'app/hero.spec',
|
||||
'app/init-caps-pipe.spec',
|
||||
'app/hero.service.no-ng.spec',
|
||||
'app/hero.service.ng.spec',
|
||||
].map(function(spec) {return System.import(spec);});
|
||||
|
||||
Promise.all(imports)
|
||||
.then(window.onload) // re-execute Jasmine's buildup
|
||||
.catch(console.error.bind(console));
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
<!-- TODO We have such an app that you can download [here](./#). -->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.
|
||||
|
||||
<!-- TODO You can see a short video of the app in action [here](./#) -->
|
||||
|
||||
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
|
||||
<!-- TODO - [TBD: need to add a filter and a directive to this sample] -->
|
||||
<!-- TODO - [TBD: need to shoehorn the router in somehow] -->
|
||||
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||
|
||||
: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"></script>`
|
||||
|
||||
**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 `<!-- #1. add the system.js library -->` 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.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
Loading…
Reference in New Issue