Merge remote-tracking branch 'remotes/angular.io/master'

# Conflicts:
#	README.md
#	public/contribute.jade
#	public/docs/_includes/_side-nav.jade
#	public/docs/_includes/_ts-temp.jade
#	public/docs/index.jade
#	public/docs/js/latest/cookbook/ts-to-js.jade
#	public/docs/ts/latest/_data.json
#	public/docs/ts/latest/cookbook/component-relative-paths.jade
#	public/docs/ts/latest/cookbook/index.jade
#	public/docs/ts/latest/cookbook/set-document-title.jade
#	public/docs/ts/latest/cookbook/visual-studio-2015.jade
#	public/docs/ts/latest/glossary.jade
#	public/docs/ts/latest/guide/animations.jade
#	public/docs/ts/latest/guide/architecture.jade
#	public/docs/ts/latest/guide/browser-support.jade
#	public/docs/ts/latest/guide/component-styles.jade
#	public/docs/ts/latest/guide/forms.jade
#	public/docs/ts/latest/guide/index.jade
#	public/docs/ts/latest/guide/pipes.jade
#	public/docs/ts/latest/guide/router.jade
#	public/docs/ts/latest/guide/style-guide.jade
#	public/docs/ts/latest/guide/template-syntax.jade
#	public/docs/ts/latest/guide/testing.jade
#	public/docs/ts/latest/guide/typescript-configuration.jade
#	public/docs/ts/latest/guide/upgrade.jade
#	public/docs/ts/latest/guide/webpack.jade
#	public/docs/ts/latest/index.jade
#	public/docs/ts/latest/quickstart.jade
#	public/docs/ts/latest/tutorial/index.jade
#	public/support.jade
This commit is contained in:
rexebin 2016-09-23 22:13:02 +01:00
commit d9af771f8d
249 changed files with 1323 additions and 1008 deletions

View File

@ -461,7 +461,7 @@ gulp.task('_copy-example-boilerplate', function (done) {
return argv.fast ? done() : buildStyles(copyExampleBoilerplate, done);
});
//Builds Angular 2 Docs CSS file from Bootstrap npm LESS source
//Builds Angular Docs CSS file from Bootstrap npm LESS source
//and copies the result to the _examples folder to be included as
//part of the example boilerplate.
function buildStyles(cb, done){

View File

@ -85,7 +85,7 @@
"picture": "/resources/images/bios/tobias.jpg",
"twitter": "tbosch1009",
"website": "https://plus.google.com/+TobiasBosch",
"bio": "Tobias Bosch is a software engineer at Google. He is part of the Angular core team and works on Angular 2.",
"bio": "Tobias Bosch is a software engineer at Google. He is part of the Angular core team and works on Angular.",
"type": "Google"
},
@ -184,7 +184,7 @@
"picture": "/resources/images/bios/hansl.jpg",
"twitter": "hanslatwork",
"website": "http://www.codingatwork.com/",
"bio": "Hans is a software engineer at Google on the Angular team and was previously at Slack. He works everyday to help make it easier for everyone to create beautiful, consistent web applications using Angular2, using Material Design components and the CLI tool.",
"bio": "Hans is a software engineer at Google on the Angular team and was previously at Slack. He works everyday to help make it easier for everyone to create beautiful, consistent web applications using Angular, using Material Design components and the CLI tool.",
"type": "Google"
},
@ -316,6 +316,12 @@
"bio": "Max Sills is Angular's Open Source lawyer.",
"type": "Google"
},
"shannon": {
"name": "Shannon Ayres",
"picture": "/resources/images/bios/shannon.jpg",
"bio": "Shannon is a technical editor in Developer Relations at Google. She loves movies, especially Sunset Boulevard, and her favorite TV show is The Walking Dead. Her mission: Righting wrong writing!",
"type": "Google"
},
"pawel": {
"name": "Pawel Kozlowski",
@ -354,7 +360,7 @@
"picture": "/resources/images/bios/marclaval.jpg",
"twitter": "marclaval",
"website": "https://github.com/mlaval",
"bio": "Marc is a manager at Amadeus where he leads the team in charge of developing and recommending UI frameworks for the company. He is also an open source developer and a contributor to Angular 2.",
"bio": "Marc is a manager at Amadeus where he leads the team in charge of developing and recommending UI frameworks for the company. He is also an open source developer and a contributor to Angular.",
"type": "Community"
},
@ -372,7 +378,7 @@
"picture": "/resources/images/bios/patrick-stapleton.jpg",
"twitter": "gdi2290",
"website": "https://angularclass.com",
"bio": "Also know as PatrickJS where JS stands for his middle and last names. Patrick is very active in Open-Source with over 4,300+ contributions in the last year alone on projects such as Angular2, AngularJS, FalcorJS, Docker, Bootstrap, gulp, and redis to name a few. He is also working on the development of Angular 2 server-side rendering as Universal Angular 2 and teaching Modern Web Development at AngularClass. He was previously the CTO of Keychain Logistics, a HackReactor Instructor and Alum.",
"bio": "Also know as PatrickJS where JS stands for his middle and last names. Patrick is very active in Open-Source with over 4,300+ contributions in the last year alone on projects such as Angular2, AngularJS, FalcorJS, Docker, Bootstrap, gulp, and redis to name a few. He is also working on the development of Angular server-side rendering as Universal Angular and teaching Modern Web Development at AngularClass. He was previously the CTO of Keychain Logistics, a HackReactor Instructor and Alum.",
"type": "Community"
},

View File

@ -2,7 +2,7 @@
"name": "angular.io",
"version": "0.0.0",
"private": true,
"description": "Angular 2 documentation",
"description": "Angular documentation",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
@ -48,7 +48,7 @@
"gulp-tslint": "^5.0.0",
"gulp-util": "^3.0.6",
"gulp-watch": "^4.3.4",
"harp": "git://github.com/filipesilva/harp.git#8da8d3497ddbfcbcbadd8be63e0fd731d7310cc4",
"harp": "0.21.0-pre.1",
"html2jade": "^0.8.4",
"indent-string": "^2.1.0",
"jasmine-core": "^2.3.4",
@ -77,4 +77,4 @@
"jstransformer-marked": "^1.0.1"
},
"homepage": "http://angular.io/"
}
}

View File

@ -18,7 +18,7 @@ else
h3.text-headline 资源库
ul.text-body
// TODO: (ericjim) make a libraries page to showcase all angular 2 libraries
// TODO: (ericjim) make a libraries page to showcase all angular libraries
//li <a href="/libraries.html">Libraries</a>
li <a href="/about/">About</a>
li <a href="/about/">关于</a>

View File

@ -12,7 +12,7 @@ if title == "Angular"
else if language
title #{title} - #{language}
else
title #{title} - Angular 2
title #{title} - Angular
meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible" content="IE=edge")
@ -22,14 +22,14 @@ meta(name="robots" content="all")
meta(name="referrer" content="origin")
meta(name="viewport" id="viewport" content="width=device-width, initial-scale=1")
meta(property="og:title" content="Angular 2")
meta(property="og:title" content="Angular")
meta(property="og:image" content="/resources/images/logos/standard/shield-large.png")
meta(property="og:image:type" content="image/png")
meta(property="og:image:width" content="184")
meta(property="og:image:height" content="200")
meta(property="og:description" content="#{description}")
meta(itemprop="name" content="Angular 2")
meta(itemprop="name" content="Angular")
meta(itemprop="description" content="#{description}")
meta(itemprop="image" content="/resources/images/logos/standard/shield-large.png")

View File

@ -44,20 +44,20 @@ mixin tree(directory, urlPrefix, name, latest)
//- BUTTON TITLE GENERATION
if language == 'ts'
if version == "latest"
- var title = 'Angular 2 for TypeScript'
- var title = 'Angular for TypeScript'
else
- var title = 'Angular ' + version + ' for TypeScript'
if language == 'js'
if version == "latest"
- var title = 'Angular 2 for JavaScript'
- var title = 'Angular for JavaScript'
else
- var title = 'Angular ' + version + ' for JavaScript'
if language == 'dart'
if version == "latest"
- var title = 'Angular 2 for Dart'
- var title = 'Angular for Dart'
else
- var title = 'Angular ' + version + ' for Dart'
@ -69,8 +69,8 @@ nav.dropdown
<!-- DROPDOWN MENU -->
ul(class="dropdown-menu" ng-class="appCtrl.showMenu ? 'is-visible' : ''")
mixin tree(public.docs.ts, "/docs/ts", "Angular 2 for TypeScript")
mixin tree(public.docs.js, "/docs/js", "Angular 2 for JavaScript")
mixin tree(public.docs.ts, "/docs/ts", "Angular for TypeScript")
mixin tree(public.docs.js, "/docs/js", "Angular for JavaScript")
//- Disable cross-language link for API entry pages (but keep for top API search page):
if ! (current.path[3] === 'api' && public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]])
mixin tree(public.docs.dart, "/docs/dart", "Angular 2 for Dart")
mixin tree(public.docs.dart, "/docs/dart", "Angular for Dart")

View File

@ -5,14 +5,14 @@
p 我们希望你能给我们的源代码做出贡献让Angular项目变得更好。
.l-sub-section
h3 Angular 2
h3 Angular
p Angular 2 is a next generation mobile and desktop application development platform.
p Angular is a next generation mobile and desktop application development platform.
p Angular 2是下一代移动与桌面应用开发平台。
p Angular是下一代移动与桌面应用开发平台。
a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) Contribute to Angular 2
a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) 为Angular 2做贡献
a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) Contribute to Angular
a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) 为Angular做贡献
.l-sub-section
h3 Angular for JavaScript or Dart

View File

@ -26,7 +26,7 @@ exports.config = {
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// For angular2 tests
// For angular tests
useAllAngular2AppRoots: true,
baseUrl: 'http://localhost:8080',

View File

@ -4,7 +4,7 @@
* The tests here basically just checking that the end styles
* of each animation are in effect.
*
* Relies on the Angular 2 testability only becoming stable once
* Relies on the Angular testability only becoming stable once
* animation(s) have finished.
*
* Ideally we'd use https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations

View File

@ -1,5 +1,5 @@
{
"description": "Angular 2 Animations",
"description": "Angular Animations",
"files":[
"!**/*.d.ts",
"!**/*.js"

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Architecture of Angular 2</title>
<title>Architecture of Angular</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -10,7 +10,7 @@ class Hero {
describe('Architecture', () => {
const expectedTitle = 'Architecture of Angular 2';
const expectedTitle = 'Architecture of Angular';
const expectedH2 = ['Hero List', 'Sales Tax Calculator'];
beforeAll(() => browser.get(''));

View File

@ -13,7 +13,7 @@ import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: 'Welcome to Angular 2'
template: 'Welcome to Angular'
})
export class AppComponent {
constructor(logger: Logger) {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Architecture of Angular 2</title>
<title>Architecture of Angular</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Architecture of Angular 2</title>
<title>Architecture of Angular</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -1,5 +1,5 @@
{
"description": "Intro to Angular2",
"description": "Intro to Angular",
"files":[
"!**/*.d.ts",
"!**/*.js",

View File

@ -9,7 +9,7 @@ describe('AOT Compilation', function () {
it('should load page and click button', function (done) {
let headingSelector = element.all(by.css('h1')).get(0);
expect(headingSelector.getText()).toEqual('My First Angular 2 App');
expect(headingSelector.getText()).toEqual('My First Angular App');
expect(element.all(by.xpath('//div[text()="Magneta"]')).get(0).isPresent()).toBe(true);
expect(element.all(by.xpath('//div[text()="Bombasto"]')).get(0).isPresent()).toBe(true);

View File

@ -1,6 +1,6 @@
<!-- #docregion -->
<button (click)="toggleHeading()">Toggle Heading</button>
<h1 *ngIf="showHeading">My First Angular 2 App</h1>
<h1 *ngIf="showHeading">My First Angular App</h1>
<h3>List of Heroes</h3>
<div *ngFor="let hero of heroes">{{hero}}</div>

View File

@ -1,5 +1,5 @@
{
"description": "Set The Document Title In Angular 2",
"description": "Set The Document Title In Angular",
"files": [
"!**/*.d.ts",
"!**/*.js",

View File

@ -7,6 +7,6 @@ describe('cli-quickstart App', () => {
it('should display message saying app works', () => {
let pageTitle = element(by.css('cli-quickstart-app h1')).getText();
expect(pageTitle).toEqual('My First Angular 2 App');
expect(pageTitle).toEqual('My First Angular App');
});
});

View File

@ -12,6 +12,6 @@ import { Component } from '@angular/core';
// #enddocregion metadata
// #docregion title, class
export class CliQuickstartAppComponent {
title = 'My First Angular 2 App';
title = 'My First Angular App';
}
// #enddocregion title, class

View File

@ -48,5 +48,5 @@
// #docregion first, final
});
// #enddocregion first, final
})(window.app || (window.app = {}));
// #enddocregion first, final

View File

@ -2,7 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Hello World</title>
<title>Angular Hello World</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Hello World</title>
<title>Angular Hello World</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tabs</title>
<title>Angular Tabs</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tabs</title>
<title>Angular Tabs</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">

View File

@ -2,7 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Todos</title>
<title>Angular Todos</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Todos</title>
<title>Angular Todos</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">

View File

@ -2,7 +2,7 @@
<!-- #docregion -->
<html>
<head>
<title>Angular 2 Lifecycle Hooks</title>
<title>Angular Lifecycle Hooks</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -1,2 +1,2 @@
### Angular 2 Documentation Example
### Angular Documentation Example

View File

@ -2,7 +2,7 @@
'use strict';
describe('QuickStart E2E Tests', function () {
let expectedMsg = 'My First Angular 2 App';
let expectedMsg = 'My First Angular App';
beforeEach(function () {
browser.get('');

View File

@ -10,7 +10,7 @@
ng.core.Component({
// #enddocregion ng-namespace-funcs
selector: 'my-app',
template: '<h1>My First Angular 2 App</h1>'
template: '<h1>My First Angular App</h1>'
// #docregion ng-namespace-funcs
})
// #enddocregion component

View File

@ -2,7 +2,7 @@
<!-- #docregion -->
<html>
<head>
<title>Angular 2 QuickStart JS</title>
<title>Angular QuickStart JS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -6,7 +6,7 @@ import { Component } from '@angular/core';
// #docregion metadata
@Component({
selector: 'my-app',
template: '<h1>My First Angular 2 App</h1>'
template: '<h1>My First Angular App</h1>'
})
// #enddocregion metadata
// #docregion class

View File

@ -2,7 +2,7 @@
<!-- #docregion -->
<html>
<head>
<title>Angular 2 QuickStart</title>
<title>Angular QuickStart</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -1,5 +1,5 @@
{
"name": "angular2-quickstart",
"name": "angular-quickstart",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",

View File

@ -1,6 +1,6 @@
// #docregion
/**
* System configuration for Angular 2 samples
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {

View File

@ -2,7 +2,7 @@
<!-- #docregion -->
<html>
<head>
<title>Angular 2 Http Demo</title>
<title>Angular Http Demo</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
<!-- #docregion -->
<html>
<head>
<title>Angular 2 Structural Directives</title>
<title>Angular Structural Directives</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
'use strict';
describe('Documentation StyleGuide E2E Tests', function() {
let expectedMsg = 'My First Angular 2 App';
let expectedMsg = 'My First Angular App';
beforeEach(function () {
browser.get('');

View File

@ -8,7 +8,7 @@ app.AppComponent =
selector: 'my-app',
// #enddocregion
// #docregion view
template: '<h1 id="output">My First Angular 2 App</h1>'
template: '<h1 id="output">My First Angular App</h1>'
})
// #enddocregion
// #docregion class
@ -48,7 +48,7 @@ app.AppComponent = function AppComponent () {}
app.AppComponent.annotations = [
new ng.core.Component({
selector: 'my-app',
template: '<h1 id="output">My First Angular 2 App</h1>'
template: '<h1 id="output">My First Angular App</h1>'
})
];
// #enddocregion

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1 id="output">My First Angular 2 App</h1>'
template: '<h1 id="output">My First Angular App</h1>'
})
export class AppComponent { }

View File

@ -1,5 +1,5 @@
/**
* System configuration for Angular 2 samples
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {

View File

@ -1,7 +1,7 @@
/**
* PLUNKER VERSION FOR CURRENT ANGULAR BUILD
* (based on systemjs.config.js in angular.io)
* System configuration for Angular 2 samples
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*
* UNTESTED !

View File

@ -1,7 +1,7 @@
/**
* PLUNKER VERSION
* (based on systemjs.config.js in angular.io)
* System configuration for Angular 2 samples
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {

View File

@ -30,6 +30,7 @@
<script>
var __spec_files__ = [
'app/about.component.spec',
'app/app.component.spec',
'app/app.component.router.spec',
'app/banner.component.spec',
@ -42,7 +43,7 @@
'app/model/hero.spec',
'app/model/http-hero.service.spec',
'app/shared/title-case.pipe.spec',
'app/twain.component.spec',
'app/shared/twain.component.spec',
'app/welcome.component.spec'
];
</script>

View File

@ -0,0 +1,26 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AboutComponent } from './about.component';
import { HighlightDirective } from './shared/highlight.directive';
let fixture: ComponentFixture<AboutComponent>;
describe('AboutComponent (highlightDirective)', () => {
// #docregion tests
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ AboutComponent, HighlightDirective],
schemas: [ NO_ERRORS_SCHEMA ]
})
.createComponent(AboutComponent);
fixture.detectChanges(); // initial binding
});
it('should have skyblue <h2>', () => {
const de = fixture.debugElement.query(By.css('h2'));
expect(de.styles['backgroundColor']).toBe('skyblue');
});
// #enddocregion tests
});

View File

@ -1,12 +1,9 @@
// #docregion
import { Component } from '@angular/core';
import { Component } from '@angular/core';
@Component({
template: `
<h2 highlight="skyblue">About</h2>
<twain-quote></twain-quote>
<p>All about this sample</p>
`,
styleUrls: ['app/shared/styles.css']
<p>All about this sample</p>`
})
export class AboutComponent { }

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AboutComponent } from './about.component';
@NgModule({
imports: [
RouterModule.forRoot([
{ path: '', redirectTo: 'dashboard', pathMatch: 'full'},
{ path: 'about', component: AboutComponent },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule'}
])
],
exports: [ RouterModule ] // re-export the module declarations
})
export class AppRoutingModule { };

View File

@ -1,3 +1,4 @@
<!-- #docregion -->
<app-banner></app-banner>
<app-welcome></app-welcome>

View File

@ -7,9 +7,7 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick,
import { RouterTestingModule } from '@angular/router/testing';
import { SpyLocation } from '@angular/common/testing';
// tslint:disable:no-unused-variable
import { newEvent } from '../testing';
// tslint:enable:no-unused-variable
import { click } from '../testing';
// r - for relatively obscure router symbols
import * as r from '@angular/router';
@ -48,9 +46,8 @@ describe('AppComponent & RouterTestingModule', () => {
it('should navigate to "About" on click', fakeAsync(() => {
createComponent();
// page.aboutLinkDe.triggerEventHandler('click', null); // fails
// page.aboutLinkDe.nativeElement.dispatchEvent(newEvent('click')); // fails
page.aboutLinkDe.nativeElement.click(); // fails in phantom
click(page.aboutLinkDe);
// page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom
advance();
expectPathToBe('/about');

View File

@ -1,108 +1,94 @@
// #docplaster
import { async, ComponentFixture, TestBed
} from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { BannerComponent } from './banner.component';
import { SharedModule } from './shared/shared.module';
// #docregion setup-schemas
import { NO_ERRORS_SCHEMA } from '@angular/core';
// #enddocregion setup-schemas
// #docregion setup-stubs-w-imports
import { Component } from '@angular/core';
// #docregion setup-schemas
import { AppComponent } from './app.component';
// #enddocregion setup-schemas
import { BannerComponent } from './banner.component';
import { RouterLinkStubDirective } from '../testing';
// #docregion setup-schemas
import { RouterOutletStubComponent } from '../testing';
import { Router, FakeRouter, FakeRouterLinkDirective, FakeRouterOutletComponent
} from '../testing';
// #enddocregion setup-schemas
@Component({selector: 'app-welcome', template: ''})
class WelcomeStubComponent {}
// #enddocregion setup-stubs-w-imports
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
describe('AppComponent & TestModule', () => {
// #docregion setup-stubs, setup-stubs-w-imports
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent, BannerComponent,
FakeRouterLinkDirective, FakeRouterOutletComponent
],
providers: [{ provide: Router, useClass: FakeRouter }],
schemas: [NO_ERRORS_SCHEMA]
AppComponent,
BannerComponent, WelcomeStubComponent,
RouterLinkStubDirective, RouterOutletStubComponent
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
});
}));
// #enddocregion setup-stubs, setup-stubs-w-imports
tests();
});
function tests() {
//////// Testing w/ NO_ERRORS_SCHEMA //////
describe('AppComponent & NO_ERRORS_SCHEMA', () => {
// #docregion setup-schemas
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations: [ AppComponent, RouterLinkStubDirective ],
schemas: [ NO_ERRORS_SCHEMA ]
})
it('can instantiate it', () => {
expect(comp).not.toBeNull();
});
it('can get RouterLinks from template', () => {
fixture.detectChanges();
const links = fixture.debugElement
// find all elements with an attached FakeRouterLink directive
.queryAll(By.directive(FakeRouterLinkDirective))
// use injector to get the RouterLink directive instance attached to each element
.map(de => de.injector.get(FakeRouterLinkDirective) as FakeRouterLinkDirective);
expect(links.length).toBe(3, 'should have 3 links');
expect(links[0].linkParams).toBe('/dashboard', '1st link should go to Dashboard');
expect(links[1].linkParams).toBe('/heroes', '1st link should go to Heroes');
});
it('can click Heroes link in template', () => {
fixture.detectChanges();
// Heroes RouterLink DebugElement
const heroesLinkDe = fixture.debugElement
.queryAll(By.directive(FakeRouterLinkDirective))[1];
expect(heroesLinkDe).toBeDefined('should have a 2nd RouterLink');
const link = heroesLinkDe.injector.get(FakeRouterLinkDirective) as FakeRouterLinkDirective;
expect(link.navigatedTo).toBeNull('link should not have navigate yet');
heroesLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(link.navigatedTo).toBe('/heroes');
});
}
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
});
}));
// #enddocregion setup-schemas
tests();
});
//////// Testing w/ real root module //////
// Best to avoid
// Tricky because we are disabling the router and its configuration
// Better to use RouterTestingModule
import { AppModule } from './app.module';
import { AppRoutingModule } from './app-routing.module';
describe('AppComponent & AppModule', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
imports: [ AppModule ],
imports: [ AppModule ]
})
// Get rid of app's Router configuration otherwise many failures.
// Doing so removes Router declarations; add the Router stubs
.overrideModule(AppModule, {
// Must get rid of `RouterModule.forRoot` to prevent attempt to configure a router
// Can't remove it because it doesn't have a known type (`forRoot` returns an object)
// therefore, must reset the entire `imports` with just the necessary stuff
set: { imports: [ SharedModule ]}
})
// Separate override because cannot both `set` and `add/remove` in same override
.overrideModule(AppModule, {
remove: {
imports: [ AppRoutingModule ]
},
add: {
declarations: [ FakeRouterLinkDirective, FakeRouterOutletComponent ],
providers: [{ provide: Router, useClass: FakeRouter }]
declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ]
}
})
@ -117,3 +103,46 @@ describe('AppComponent & AppModule', () => {
tests();
});
function tests() {
let links: RouterLinkStubDirective[];
let linkDes: DebugElement[];
// #docregion test-setup
beforeEach(() => {
// trigger initial data binding
fixture.detectChanges();
// find DebugElements with an attached RouterLinkStubDirective
linkDes = fixture.debugElement
.queryAll(By.directive(RouterLinkStubDirective));
// get the attached link directive instances using the DebugElement injectors
links = linkDes
.map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
});
// #enddocregion test-setup
it('can instantiate it', () => {
expect(comp).not.toBeNull();
});
// #docregion tests
it('can get RouterLinks from template', () => {
expect(links.length).toBe(3, 'should have 3 links');
expect(links[0].linkParams).toBe('/dashboard', '1st link should go to Dashboard');
expect(links[1].linkParams).toBe('/heroes', '1st link should go to Heroes');
});
it('can click Heroes link in template', () => {
const heroesLinkDe = linkDes[1];
const heroesLink = links[1];
expect(heroesLink.navigatedTo).toBeNull('link should not have navigated yet');
heroesLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(heroesLink.navigatedTo).toBe('/heroes');
});
// #docregion tests
}

View File

@ -1,6 +1,5 @@
// #docregion
import { Component } from '@angular/core';
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: 'app/app.component.html'

View File

@ -1,9 +1,9 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { AboutComponent } from './about.component';
import { BannerComponent } from './banner.component';
import { HeroService,
@ -19,11 +19,7 @@ import { SharedModule } from './shared/shared.module';
imports: [
BrowserModule,
DashboardModule,
RouterModule.forRoot([
{ path: '', redirectTo: 'dashboard', pathMatch: 'full'},
{ path: 'about', component: AboutComponent },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule'}
]),
AppRoutingModule,
SharedModule
],
providers: [ HeroService, TwainService, UserService ],

View File

@ -26,7 +26,7 @@ import { NgModel, NgControl } from '@angular/forms';
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick
} from '@angular/core/testing';
import { addMatchers, newEvent } from '../../testing';
import { addMatchers, newEvent, click } from '../../testing';
beforeEach( addMatchers );
@ -180,7 +180,7 @@ describe('TestBed Component Tests', () => {
const comp = fixture.componentInstance;
const hero = comp.heroes[0];
heroes[0].triggerEventHandler('click', null);
click(heroes[0]);
fixture.detectChanges();
const selected = fixture.debugElement.query(By.css('p'));
@ -213,7 +213,7 @@ describe('TestBed Component Tests', () => {
fixture.detectChanges();
expect(span.textContent).toMatch(/is off/i, 'before click');
btn.triggerEventHandler('click', null);
click(btn);
fixture.detectChanges();
expect(span.textContent).toMatch(/is on/i, 'after click');
});
@ -610,7 +610,7 @@ describe('Lifecycle hooks w/ MyIfParentComp', () => {
getChild();
const btn = fixture.debugElement.query(By.css('button'));
btn.triggerEventHandler('click', null);
click(btn);
fixture.detectChanges();
expect(child.ngOnDestroyCalled).toBe(true);

View File

@ -4,7 +4,7 @@ import { async, ComponentFixture, TestBed
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { addMatchers } from '../../testing';
import { addMatchers, click } from '../../testing';
import { Hero } from '../model/hero';
import { DashboardHeroComponent } from './dashboard-hero.component';
@ -53,10 +53,22 @@ describe('DashboardHeroComponent when tested directly', () => {
let selectedHero: Hero;
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
// #docregion trigger-event-handler
heroEl.triggerEventHandler('click', null);
// #enddocregion trigger-event-handler
expect(selectedHero).toBe(expectedHero);
});
// #enddocregion click-test
// #docregion click-test-2
it('should raise selected event when clicked', () => {
let selectedHero: Hero;
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
click(heroEl); // triggerEventHandler helper
expect(selectedHero).toBe(expectedHero);
});
// #enddocregion click-test-2
});
//////////////////
@ -89,7 +101,7 @@ describe('DashboardHeroComponent when inside a test host', () => {
});
it('should raise selected event when clicked', () => {
heroEl.triggerEventHandler('click', null);
click(heroEl);
// selected hero should be the same data bound hero
expect(testHost.selectedHero).toBe(testHost.hero);
});
@ -102,8 +114,7 @@ import { Component } from '@angular/core';
// #docregion test-host
@Component({
template: `
<dashboard-hero [hero]="hero" (selected)="onSelected($event)">
</dashboard-hero>`
<dashboard-hero [hero]="hero" (selected)="onSelected($event)"></dashboard-hero>`
})
class TestHostComponent {
hero = new Hero(42, 'Test Name');

View File

@ -2,9 +2,9 @@
import { async, inject, ComponentFixture, TestBed
} from '@angular/core/testing';
import { addMatchers } from '../../testing';
import { HeroService } from '../model';
import { FakeHeroService } from '../model/testing';
import { addMatchers, click } from '../../testing';
import { HeroService } from '../model';
import { FakeHeroService } from '../model/testing';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
@ -12,11 +12,11 @@ import { Router } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { DashboardModule } from './dashboard.module';
// #docregion fake-router
class FakeRouter {
navigateByUrl(url: string) { return url; }
// #docregion router-stub
class RouterStub {
navigateByUrl(url: string) { return url; }
}
// #enddocregion fake-router
// #enddocregion router-stub
beforeEach ( addMatchers );
@ -39,7 +39,7 @@ describe('DashboardComponent (deep)', () => {
function clickForDeep() {
// get first <div class="hero"> DebugElement
const heroEl = fixture.debugElement.query(By.css('.hero'));
heroEl.triggerEventHandler('click', null);
click(heroEl);
}
});
@ -73,7 +73,7 @@ function compileAndCreate() {
TestBed.configureTestingModule({
providers: [
{ provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: FakeRouter }
{ provide: Router, useClass: RouterStub }
]
})
.compileComponents().then(() => {

View File

@ -7,10 +7,7 @@ import { Hero, HeroService } from '../model';
@Component({
selector: 'app-dashboard',
templateUrl: 'app/dashboard/dashboard.component.html',
styleUrls: [
'app/shared/styles.css',
'app/dashboard/dashboard.component.css'
]
styleUrls: ['app/dashboard/dashboard.component.css']
})
export class DashboardComponent implements OnInit {

View File

@ -1,10 +1,11 @@
<!-- #docregion -->
<div *ngIf="hero">
<h2><span>{{hero.name | titlecase}}</span> Details</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name" />
<label for="name">name: </label>
<input id="name" [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="save()">Save</button>
<button (click)="cancel()">Cancel</button>

View File

@ -1,12 +1,12 @@
import { HeroDetailComponent } from './hero-detail.component';
import { Hero } from '../model';
import { FakeActivatedRoute } from '../../testing';
import { ActivatedRouteStub } from '../../testing';
////////// Tests ////////////////////
describe('HeroDetailComponent - no TestBed', () => {
let activatedRoute: FakeActivatedRoute;
let activatedRoute: ActivatedRouteStub;
let comp: HeroDetailComponent;
let expectedHero: Hero;
let hds: any;
@ -14,7 +14,7 @@ describe('HeroDetailComponent - no TestBed', () => {
beforeEach( done => {
expectedHero = new Hero(42, 'Bubba');
activatedRoute = new FakeActivatedRoute();
activatedRoute = new ActivatedRouteStub();
activatedRoute.testParams = { id: expectedHero.id };
router = jasmine.createSpyObj('router', ['navigate']);

View File

@ -1,3 +1,4 @@
// #docplaster
import {
async, ComponentFixture, fakeAsync, inject, TestBed, tick
} from '@angular/core/testing';
@ -6,100 +7,197 @@ import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import {
addMatchers, newEvent,
ActivatedRoute, FakeActivatedRoute, Router, FakeRouter
ActivatedRoute, ActivatedRouteStub, click, newEvent, Router, RouterStub
} from '../../testing';
import { HEROES, FakeHeroService } from '../model/testing';
import { HeroModule } from './hero.module';
import { Hero } from '../model';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroDetailService } from './hero-detail.service';
import { Hero, HeroService } from '../model';
import { HeroModule } from './hero.module';
////// Testing Vars //////
let activatedRoute: FakeActivatedRoute;
let activatedRoute: ActivatedRouteStub;
let comp: HeroDetailComponent;
let fixture: ComponentFixture<HeroDetailComponent>;
let page: Page;
////////// Tests ////////////////////
////// Tests //////
describe('HeroDetailComponent', () => {
beforeEach(() => {
activatedRoute = new ActivatedRouteStub();
});
describe('with HeroModule setup', heroModuleSetup);
describe('when override its provided HeroDetailService', overrideSetup);
describe('with FormsModule setup', formsModuleSetup);
describe('with SharedModule setup', sharedModuleSetup);
});
////////////////////
function overrideSetup() {
// #docregion stub-hds
class StubHeroDetailService {
testHero = new Hero(42, 'Test Hero');
getHero(id: number | string): Promise<Hero> {
return Promise.resolve(true).then(() => Object.assign({}, this.testHero) );
}
saveHero(hero: Hero): Promise<Hero> {
return Promise.resolve(true).then(() => Object.assign(this.testHero, hero) );
}
}
// #enddocregion stub-hds
// the `id` value is irrelevant because ignored by service stub
beforeEach(() => activatedRoute.testParams = { id: 99999 } );
// #docregion setup-override
beforeEach( async(() => {
TestBed.configureTestingModule({
imports: [ HeroModule ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: Router, useClass: RouterStub},
// #enddocregion setup-override
// HeroDetailService at this level is IRRELEVANT!
{ provide: HeroDetailService, useValue: {} }
// #docregion setup-override
]
})
// Override component's own provider
// #docregion override-component-method
.overrideComponent(HeroDetailComponent, {
set: {
providers: [
{ provide: HeroDetailService, useClass: StubHeroDetailService }
]
}
})
// #enddocregion override-component-method
.compileComponents();
}));
// #enddocregion setup-override
// #docregion override-tests
let hds: StubHeroDetailService;
beforeEach( async(() => {
addMatchers();
activatedRoute = new FakeActivatedRoute();
createComponent();
// get the component's injected StubHeroDetailService
hds = fixture.debugElement.injector.get(HeroDetailService);
}));
TestBed.configureTestingModule({
imports: [ HeroModule ],
it('should display stub hero\'s name', () => {
expect(page.nameDisplay.textContent).toBe(hds.testHero.name);
});
// DON'T RE-DECLARE because already declared in HeroModule
// declarations: [HeroDetailComponent, TitleCasePipe], // No!
it('should save stub hero change', fakeAsync(() => {
const origName = hds.testHero.name;
const newName = 'New Name';
page.nameInput.value = newName;
page.nameInput.dispatchEvent(newEvent('input')); // tell Angular
expect(comp.hero.name).toBe(newName, 'component hero has new name');
expect(hds.testHero.name).toBe(origName, 'service hero unchanged before save');
click(page.saveBtn);
tick(); // wait for async save to complete
expect(hds.testHero.name).toBe(newName, 'service hero has new name after save');
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
}));
// #enddocregion override-tests
it('fixture injected service is not the component injected service',
inject([HeroDetailService], (service: HeroDetailService) => {
expect(service).toEqual({}, 'service injected from fixture');
expect(hds).toBeTruthy('service injected into component');
}));
}
////////////////////
import { HEROES, FakeHeroService } from '../model/testing';
import { HeroService } from '../model';
const firstHero = HEROES[0];
function heroModuleSetup() {
// #docregion setup-hero-module
beforeEach( async(() => {
TestBed.configureTestingModule({
imports: [ HeroModule ],
// #enddocregion setup-hero-module
// declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION
// #docregion setup-hero-module
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: FakeRouter},
{ provide: Router, useClass: RouterStub},
]
})
.compileComponents();
}));
// #enddocregion setup-hero-module
describe('when navigate to hero id=' + HEROES[0].id, () => {
// #docregion route-good-id
describe('when navigate to existing hero', () => {
let expectedHero: Hero;
beforeEach( async(() => {
expectedHero = HEROES[0];
expectedHero = firstHero;
activatedRoute.testParams = { id: expectedHero.id };
createComponent();
}));
// #docregion selected-tests
it('should display that hero\'s name', () => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});
// #enddocregion route-good-id
it('should navigate when click cancel', () => {
page.cancelBtn.triggerEventHandler('click', null);
click(page.cancelBtn);
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
});
it('should save when click save', () => {
page.saveBtn.triggerEventHandler('click', null);
it('should save when click save but not navigate immediately', () => {
click(page.saveBtn);
expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
});
it('should navigate when click click save resolves', fakeAsync(() => {
page.saveBtn.triggerEventHandler('click', null);
tick(); // waits for async save to "complete" before navigating
it('should navigate when click save and save resolves', fakeAsync(() => {
click(page.saveBtn);
tick(); // wait for async save to complete
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
}));
// #docregion title-case-pipe
it('should convert original hero name to Title Case', () => {
expect(page.nameDisplay.textContent).toBe(comp.hero.name);
});
// #enddocregion title-case-pipe
it('should convert hero name to Title Case', fakeAsync(() => {
const inputName = 'quick BROWN fox';
const expectedName = 'Quick Brown Fox';
const titleCaseName = 'Quick Brown Fox';
// simulate user entering new name in input
// simulate user entering new name into the input box
page.nameInput.value = inputName;
// dispatch a DOM event so that Angular learns of input value change.
// detectChanges() makes ngModel push input value to component property
// and Angular updates the output span
page.nameInput.dispatchEvent(newEvent('input'));
// Tell Angular to update the output span through the title pipe
fixture.detectChanges();
expect(page.nameDisplay.textContent).toBe(expectedName, 'hero name display');
expect(comp.hero.name).toBe(inputName, 'comp.hero.name');
expect(page.nameDisplay.textContent).toBe(titleCaseName);
}));
// #enddocregion title-case-pipe
// #enddocregion selected-tests
// #docregion route-good-id
});
// #enddocregion route-good-id
// #docregion route-no-id
describe('when navigate with no hero id', () => {
beforeEach( async( createComponent ));
@ -111,7 +209,9 @@ describe('HeroDetailComponent', () => {
expect(page.nameDisplay.textContent).toBe('');
});
});
// #enddocregion route-no-id
// #docregion route-bad-id
describe('when navigate to non-existant hero id', () => {
beforeEach( async(() => {
activatedRoute.testParams = { id: 99999 };
@ -123,11 +223,10 @@ describe('HeroDetailComponent', () => {
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
});
});
///////////////////////////
// #enddocregion route-bad-id
// Why we must use `fixture.debugElement.injector` in `Page()`
it('cannot use `inject` to get component\'s provided service', () => {
it('cannot use `inject` to get component\'s provided HeroDetailService', () => {
let service: HeroDetailService;
fixture = TestBed.createComponent(HeroDetailComponent);
expect(
@ -141,26 +240,85 @@ describe('HeroDetailComponent', () => {
service = fixture.debugElement.injector.get(HeroDetailService);
expect(service).toBeDefined('debugElement.injector');
});
});
}
/////////////////////
import { FormsModule } from '@angular/forms';
import { TitleCasePipe } from '../shared/title-case.pipe';
function formsModuleSetup() {
// #docregion setup-forms-module
beforeEach( async(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ HeroDetailComponent, TitleCasePipe ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: RouterStub},
]
})
.compileComponents();
}));
// #enddocregion setup-forms-module
it('should display 1st hero\'s name', fakeAsync(() => {
const expectedHero = firstHero;
activatedRoute.testParams = { id: expectedHero.id };
createComponent().then(() => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});
}));
}
///////////////////////
import { SharedModule } from '../shared/shared.module';
function sharedModuleSetup() {
// #docregion setup-shared-module
beforeEach( async(() => {
TestBed.configureTestingModule({
imports: [ SharedModule ],
declarations: [ HeroDetailComponent ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: RouterStub},
]
})
.compileComponents();
}));
// #enddocregion setup-shared-module
it('should display 1st hero\'s name', fakeAsync(() => {
const expectedHero = firstHero;
activatedRoute.testParams = { id: expectedHero.id };
createComponent().then(() => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});
}));
}
/////////// Helpers /////
// #docregion create-component
/** Create the HeroDetailComponent, initialize it, set test variables */
function createComponent() {
fixture = TestBed.createComponent(HeroDetailComponent);
comp = fixture.componentInstance;
page = new Page();
// change detection triggers ngOnInit which gets a hero
// 1st change detection triggers ngOnInit which gets a hero
fixture.detectChanges();
return fixture.whenStable().then(() => {
// got the hero and updated component
// change detection updates the view
// 2nd change detection displays the async-fetched hero
fixture.detectChanges();
page.addPageElements();
});
}
// #enddocregion create-component
// #docregion page
class Page {
gotoSpy: jasmine.Spy;
navSpy: jasmine.Spy;
@ -173,19 +331,20 @@ class Page {
constructor() {
// Use component's injector to see the services it injected.
let compInjector = fixture.debugElement.injector;
let hds = compInjector.get(HeroDetailService);
let router = compInjector.get(Router);
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
this.saveSpy = spyOn(hds, 'saveHero').and.callThrough();
this.navSpy = spyOn(router, 'navigate').and.callThrough();
const compInjector = fixture.debugElement.injector;
const hds = compInjector.get(HeroDetailService);
const router = compInjector.get(Router);
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
this.navSpy = spyOn(router, 'navigate');
this.saveSpy = spyOn(hds, 'saveHero').and.callThrough();
}
/** Add page elements after page initializes */
/** Add page elements after hero arrives */
addPageElements() {
if (comp.hero) {
// have a hero so these DOM elements can be reached
let buttons = fixture.debugElement.queryAll(By.css('button'));
// have a hero so these elements are now in the DOM
const buttons = fixture.debugElement.queryAll(By.css('button'));
this.saveBtn = buttons[0];
this.cancelBtn = buttons[1];
this.nameDisplay = fixture.debugElement.query(By.css('span')).nativeElement;
@ -193,4 +352,4 @@ class Page {
}
}
}
// #enddocregion page

View File

@ -1,46 +1,51 @@
/* tslint:disable:member-ordering */
// #docplaster
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import 'rxjs/add/operator/pluck';
import { Hero } from '../model';
import { HeroDetailService } from './hero-detail.service';
// #docregion prototype
@Component({
selector: 'app-hero-detail',
selector: 'app-hero-detail',
templateUrl: 'app/hero/hero-detail.component.html',
styleUrls: [
'app/shared/styles.css',
'app/hero/hero-detail.component.css'
],
styleUrls: ['app/hero/hero-detail.component.css'],
providers: [ HeroDetailService ]
})
export class HeroDetailComponent implements OnInit {
@Input() hero: Hero;
// #docregion ctor
constructor(
private heroDetailService: HeroDetailService,
private route: ActivatedRoute,
private route: ActivatedRoute,
private router: Router) {
}
// #enddocregion ctor
// #enddocregion prototype
ngOnInit() {
let id = this.route.snapshot.params['id'];
@Input() hero: Hero;
// tslint:disable-next-line:triple-equals
if (id == undefined) {
// no id; act as if is new
this.hero = new Hero();
} else {
this.heroDetailService.getHero(id).then(hero => {
if (hero) {
this.hero = hero;
} else {
this.gotoList(); // id not found; navigate to list
}
});
}
// #docregion ng-on-init
ngOnInit(): void {
// get hero when `id` param changes
this.route.params.pluck<string>('id')
.forEach(id => this.getHero(id))
.catch(() => this.hero = new Hero()); // no id; should edit new hero
}
// #enddocregion ng-on-init
private getHero(id: string): void {
this.heroDetailService.getHero(id).then(hero => {
if (hero) {
this.hero = hero;
} else {
this.gotoList(); // id not found; navigate to list
}
});
}
save() {
save(): void {
this.heroDetailService.saveHero(this.hero).then(() => this.gotoList());
}
@ -49,4 +54,6 @@ export class HeroDetailComponent implements OnInit {
gotoList() {
this.router.navigate(['../'], {relativeTo: this.route});
}
// #docregion prototype
}
// #enddocregion prototype

View File

@ -2,10 +2,13 @@ import { Injectable } from '@angular/core';
import { Hero, HeroService } from '../model';
// #docregion prototype
@Injectable()
export class HeroDetailService {
constructor(private heroService: HeroService) { }
// #enddocregion prototype
// Returns a clone which caller may modify safely
getHero(id: number | string): Promise<Hero> {
if (typeof id === 'string') {
id = parseInt(id as string, 10);
@ -18,4 +21,6 @@ export class HeroDetailService {
saveHero(hero: Hero) {
return this.heroService.updateHero(hero);
}
// #docregion prototype
}
// #enddocregion prototype

View File

@ -4,7 +4,7 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { addMatchers, newEvent, Router, FakeRouter
import { addMatchers, newEvent, Router, RouterStub
} from '../../testing';
import { HEROES, FakeHeroService } from '../model/testing';
@ -28,7 +28,7 @@ describe('HeroListComponent', () => {
imports: [HeroModule],
providers: [
{ provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: FakeRouter}
{ provide: Router, useClass: RouterStub}
]
})
.compileComponents()
@ -132,7 +132,7 @@ class Page {
// Get the component's injected router and spy on it
const router = fixture.debugElement.injector.get(Router);
this.navSpy = spyOn(router, 'navigate').and.callThrough();
this.navSpy = spyOn(router, 'navigate');
};
}

View File

@ -6,10 +6,7 @@ import { Hero, HeroService } from '../model';
@Component({
selector: 'app-heroes',
templateUrl: 'app/hero/hero-list.component.html',
styleUrls: [
'app/shared/styles.css',
'app/hero/hero-list.component.css'
]
styleUrls: ['app/hero/hero-list.component.css']
})
export class HeroListComponent implements OnInit {
heroes: Promise<Hero[]>;

View File

@ -4,7 +4,7 @@ import { Hero } from './hero';
import { HEROES } from './test-heroes';
@Injectable()
/** Dummy HeroService that pretends to be real */
/** Dummy HeroService. Pretend it makes real http requests */
export class HeroService {
getHeroes() {
return Promise.resolve(HEROES);
@ -21,9 +21,10 @@ export class HeroService {
updateHero(hero: Hero): Promise<Hero> {
return this.getHero(hero.id).then(h => {
return h ?
Object.assign(h, hero) :
Promise.reject(`Hero ${hero.id} not found`) as any as Promise<Hero>;
if (!h) {
throw new Error(`Hero ${hero.id} not found`);
}
return Object.assign(h, hero);
});
}
}

View File

@ -1,58 +1,100 @@
import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { By } from '@angular/platform-browser';
import { HighlightDirective } from './highlight.directive';
import { HighlightDirective } from './highlight.directive';
import { newEvent } from '../../testing';
// Component to test directive
// #docregion test-component
@Component({
template: `
<h2 highlight="yellow">Something Yellow</h2>
<h2 highlight>Something Gray</h2>
<h2>Something White</h2>
`
<h2 highlight>The Default (Gray)</h2>
<h2>No Highlight</h2>
<input #box [highlight]="box.value" value="cyan"/>`
})
class TestComponent { }
// #enddocregion test-component
////// Tests //////////
describe('HighlightDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let h2Des: DebugElement[];
let des: DebugElement[]; // the three elements w/ the directive
let bareH2: DebugElement; // the <h2> w/o the directive
// #docregion selected-tests
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ HighlightDirective, TestComponent ]
})
.createComponent(TestComponent);
h2Des = fixture.debugElement.queryAll(By.css('h2'));
fixture.detectChanges(); // initial binding
// all elements with an attached HighlightDirective
des = fixture.debugElement.queryAll(By.directive(HighlightDirective));
// the h2 without the HighlightDirective
bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
});
it('should have `HighlightDirective`', () => {
// The HighlightDirective listed in <h2> tokens means it is attached
expect(h2Des[0].providerTokens).toContain(HighlightDirective, 'HighlightDirective');
// color tests
it('should have three highlighted elements', () => {
expect(des.length).toBe(3);
});
it('should color first <h2> background "yellow"', () => {
fixture.detectChanges();
const h2 = h2Des[0].nativeElement as HTMLElement;
expect(h2.style.backgroundColor).toBe('yellow');
it('should color 1st <h2> background "yellow"', () => {
expect(des[0].styles['backgroundColor']).toBe('yellow');
});
it('should color second <h2> background w/ default color', () => {
fixture.detectChanges();
const h2 = h2Des[1].nativeElement as HTMLElement;
expect(h2.style.backgroundColor).toBe(HighlightDirective.defaultColor);
it('should color 2nd <h2> background w/ default color', () => {
const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;
expect(des[1].styles['backgroundColor']).toBe(dir.defaultColor);
});
it('should NOT color third <h2> (no directive)', () => {
// no directive
expect(h2Des[2].providerTokens).not.toContain(HighlightDirective, 'HighlightDirective');
it('should bind <input> background to value color', () => {
// easier to work with nativeElement
const input = des[2].nativeElement as HTMLInputElement;
expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor');
// dispatch a DOM event so that Angular responds to the input value change.
input.value = 'green';
input.dispatchEvent(newEvent('input'));
fixture.detectChanges();
const h2 = h2Des[2].nativeElement as HTMLElement;
expect(h2.style.backgroundColor).toBe('', 'backgroundColor');
expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');
});
// customProperty tests
it('all highlighted elements should have a true customProperty', () => {
const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true);
expect(allTrue).toBe(true);
});
it('bare <h2> should not have a customProperty', () => {
expect(bareH2.properties['customProperty']).toBeUndefined();
});
// #enddocregion selected-tests
// injected directive
// attached HighlightDirective can be injected
it('can inject `HighlightDirective` in 1st <h2>', () => {
const dir = des[0].injector.get(HighlightDirective);
expect(dir).toBeTruthy();
});
it('cannot inject `HighlightDirective` in 3rd <h2>', () => {
const dir = bareH2.injector.get(HighlightDirective, null);
expect(dir).toBe(null);
});
// DebugElement.providerTokens
// attached HighlightDirective should be listed in the providerTokens
it('should have `HighlightDirective` in 1st <h2> providerTokens', () => {
expect(des[0].providerTokens).toContain(HighlightDirective);
});
it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => {
expect(bareH2.providerTokens).not.toContain(HighlightDirective);
});
});

View File

@ -1,13 +1,12 @@
// #docregion
import { Directive, ElementRef, Input, OnChanges, Renderer } from '@angular/core';
@Directive({ selector: '[highlight]' })
/**
* Set backgroundColor for the attached element ton highlight color and
* set element `customProperty` = true
*/
/** Set backgroundColor for the attached element to highlight color
* and set the element's customProperty to true */
export class HighlightDirective implements OnChanges {
static defaultColor = 'rgb(211, 211, 211)'; // lightgray
defaultColor = 'rgb(211, 211, 211)'; // lightgray
@Input('highlight') bgColor: string;
@ -18,7 +17,6 @@ export class HighlightDirective implements OnChanges {
ngOnChanges() {
this.renderer.setElementStyle(
this.el.nativeElement, 'backgroundColor',
this.bgColor || HighlightDirective.defaultColor );
this.bgColor || this.defaultColor );
}
}

View File

@ -1 +0,0 @@
/* MISSING */

View File

@ -13,15 +13,20 @@ describe('WelcomeComponent', () => {
let userService: UserService; // the actually injected service
let welcomeEl: DebugElement; // the element with the welcome message
let userServiceStub: {
isLoggedIn: boolean;
user: { name: string}
};
// #docregion setup
beforeEach(() => {
// fake UserService for test purposes
// #docregion fake-userservice
const fakeUserService = {
// stub UserService for test purposes
// #docregion user-service-stub
userServiceStub = {
isLoggedIn: true,
user: { name: 'Test User'}
};
// #enddocregion fake-userservice
// #enddocregion user-service-stub
// #docregion config-test-module
TestBed.configureTestingModule({
@ -29,7 +34,7 @@ describe('WelcomeComponent', () => {
// #enddocregion setup
// providers: [ UserService ] // a real service would be a problem!
// #docregion setup
providers: [ {provide: UserService, useValue: fakeUserService } ]
providers: [ {provide: UserService, useValue: userServiceStub } ]
});
// #enddocregion config-test-module
@ -80,4 +85,8 @@ describe('WelcomeComponent', () => {
expect(content).toMatch(/log in/i, '"log in"');
});
// #enddocregion tests
it('orig stub and injected UserService are not the same object', () => {
expect(userServiceStub === userService).toBe(false);
});
});

View File

@ -9,7 +9,7 @@ Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing.
// Uncomment to get full stacktrace output. Sometimes helpful, usually not.
// Error.stackTraceLimit = Infinity; //
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
var baseURL = document.baseURI;
baseURL = baseURL + baseURL[baseURL.length-1] ? '' : '/';

View File

@ -1,49 +0,0 @@
// export for convenience.
export { ActivatedRoute, Router, RouterLink, RouterOutlet} from '@angular/router';
import { Component, Directive, Injectable, Input } from '@angular/core';
import { NavigationExtras } from '@angular/router';
@Directive({
selector: '[routerLink]',
host: {
'(click)': 'onClick()',
'[attr.href]': 'visibleHref',
'[class.router-link-active]': 'isRouteActive'
}
})
export class FakeRouterLinkDirective {
isRouteActive = false;
visibleHref: string; // the url displayed on the anchor element.
@Input('routerLink') linkParams: any;
navigatedTo: any = null;
onClick() {
this.navigatedTo = this.linkParams;
}
}
@Component({selector: 'router-outlet', template: ''})
export class FakeRouterOutletComponent { }
@Injectable()
export class FakeRouter {
lastCommand: any[];
navigate(commands: any[], extras?: NavigationExtras) {
this.lastCommand = commands;
return commands;
}
}
@Injectable()
export class FakeActivatedRoute {
testParams: {} = {};
get snapshot() {
return {
params: this.testParams
};
}
}

View File

@ -1,9 +1,17 @@
import { DebugElement } from '@angular/core';
import { tick, ComponentFixture } from '@angular/core/testing';
export * from './jasmine-matchers';
export * from './fake-router';
export * from './router-stubs';
///// Short utilities /////
/** Wait a tick, then detect changes */
export function advance(f: ComponentFixture<any>): void {
tick();
f.detectChanges();
}
// Short utilities
/**
* Create custom DOM event the old fashioned way
*
@ -16,8 +24,20 @@ export function newEvent(eventName: string, bubbles = false, cancelable = false)
return evt;
}
/** Wait a tick, then detect changes */
export function advance(f: ComponentFixture<any>): void {
tick();
f.detectChanges();
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
// #docregion click-event
/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
export const ButtonClickEvents = {
left: { button: 0 },
right: { button: 2 }
};
/** Simulate element click. Defaults to mouse left-button click event. */
export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void {
if (el instanceof HTMLElement) {
el.click();
} else {
el.triggerEventHandler('click', eventObj);
}
}
// #enddocregion click-event

View File

@ -0,0 +1,57 @@
// export for convenience.
export { ActivatedRoute, Router, RouterLink, RouterOutlet} from '@angular/router';
import { Component, Directive, Injectable, Input } from '@angular/core';
import { NavigationExtras } from '@angular/router';
// #docregion router-link
@Directive({
selector: '[routerLink]',
host: {
'(click)': 'onClick()'
}
})
export class RouterLinkStubDirective {
@Input('routerLink') linkParams: any;
navigatedTo: any = null;
onClick() {
this.navigatedTo = this.linkParams;
}
}
// #enddocregion router-link
@Component({selector: 'router-outlet', template: ''})
export class RouterOutletStubComponent { }
@Injectable()
export class RouterStub {
navigate(commands: any[], extras?: NavigationExtras) { }
}
// Only implements params and part of snapshot.params
// #docregion activated-route-stub
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class ActivatedRouteStub {
// ActivatedRoute.params is Observable
private subject = new BehaviorSubject(this.testParams);
params = this.subject.asObservable();
// Test parameters
private _testParams: {};
get testParams() { return this._testParams; }
set testParams(params: {}) {
this._testParams = params;
this.subject.next(params);
}
// ActivatedRoute.snapshot.params
get snapshot() {
return { params: this.testParams };
}
}
// #enddocregion activated-route-stub

View File

@ -4,7 +4,7 @@
type WPromise<T> = webdriver.promise.Promise<T>;
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedTitle = `Angular ${expectedH1}`;
class Hero {
id: number;

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tour of Heroes</title>
<title>Angular Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedTitle = `Angular ${expectedH1}`;
const expectedH2 = 'My Heroes';
const targetHero = { id: 16, name: 'RubberMan' };
const nameSuffix = 'X';

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tour of Heroes</title>
<title>Angular Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedTitle = `Angular ${expectedH1}`;
const expectedH2 = 'My Heroes';
const targetHero = { id: 16, name: 'RubberMan' };
const nameSuffix = 'X';

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tour of Heroes</title>
<title>Angular Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedTitle = `Angular ${expectedH1}`;
const expectedH2 = 'My Heroes';
const targetHero = { id: 16, name: 'RubberMan' };
const nameSuffix = 'X';

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 Tour of Heroes</title>
<title>Angular Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedTitle = `Angular ${expectedH1}`;
const targetHero = { id: 15, name: 'Magneta' };
const targetHeroDashboardIndex = 3;
const nameSuffix = 'X';

View File

@ -5,7 +5,7 @@
<head>
<base href="/">
<!-- #enddocregion base-href -->
<title>Angular 2 Tour of Heroes</title>
<title>Angular Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -23,9 +23,6 @@ void main() {
bootstrap(AppComponent, [
provide(BrowserClient, useFactory: () => new BrowserClient(), deps: [])
]);
// Simplify bootstrap provider list to [BrowserClient]
// once there is a fix for:
// https://github.com/dart-lang/angular2/issues/37
}
// #enddocregion v1
*/

View File

@ -2,7 +2,7 @@
'use strict';
const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`;
const expectedTitle = `Angular ${expectedH1}`;
const targetHero = { id: 15, name: 'Magneta' };
const targetHeroDashboardIndex = 3;
const nameSuffix = 'X';

View File

@ -2,7 +2,7 @@
<html>
<head>
<base href="/">
<title>Angular 2 Tour of Heroes</title>
<title>Angular Tour of Heroes</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">

View File

@ -2,7 +2,7 @@
'use strict';
describe('QuickStart E2E Tests', function () {
let expectedMsg = 'Hello from Angular 2 App with Webpack';
let expectedMsg = 'Hello from Angular App with Webpack';
beforeEach(function () {
browser.get('');

View File

@ -2,7 +2,7 @@
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('reflect-metadata');
require('core-js/es7/reflect');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');

View File

@ -1,7 +1,7 @@
{
"name": "angular2-webpack",
"version": "1.0.0",
"description": "A webpack starter for angular 2",
"description": "A webpack starter for Angular",
"scripts": {
"start": "webpack-dev-server --inline --progress --port 8080",
"test": "karma start",
@ -41,7 +41,6 @@
"raw-loader": "^0.5.1",
"rimraf": "^2.5.2",
"style-loader": "^0.13.1",
"ts-loader": "^0.8.1",
"typescript": "^2.0.2",
"typings": "^1.3.2",
"webpack": "^1.13.0",

View File

@ -1,6 +1,6 @@
<!-- #docregion -->
<main>
<h1>Hello from Angular 2 App with Webpack</h1>
<h1>Hello from Angular App with Webpack</h1>
<img src="../../public/images/angular.png">
</main>

View File

@ -1,5 +1,5 @@
// #docregion
// Angular 2
// Angular
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';

Some files were not shown because too many files have changed in this diff Show More