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); 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 //and copies the result to the _examples folder to be included as
//part of the example boilerplate. //part of the example boilerplate.
function buildStyles(cb, done){ function buildStyles(cb, done){

View File

@ -85,7 +85,7 @@
"picture": "/resources/images/bios/tobias.jpg", "picture": "/resources/images/bios/tobias.jpg",
"twitter": "tbosch1009", "twitter": "tbosch1009",
"website": "https://plus.google.com/+TobiasBosch", "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" "type": "Google"
}, },
@ -184,7 +184,7 @@
"picture": "/resources/images/bios/hansl.jpg", "picture": "/resources/images/bios/hansl.jpg",
"twitter": "hanslatwork", "twitter": "hanslatwork",
"website": "http://www.codingatwork.com/", "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" "type": "Google"
}, },
@ -316,6 +316,12 @@
"bio": "Max Sills is Angular's Open Source lawyer.", "bio": "Max Sills is Angular's Open Source lawyer.",
"type": "Google" "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": { "pawel": {
"name": "Pawel Kozlowski", "name": "Pawel Kozlowski",
@ -354,7 +360,7 @@
"picture": "/resources/images/bios/marclaval.jpg", "picture": "/resources/images/bios/marclaval.jpg",
"twitter": "marclaval", "twitter": "marclaval",
"website": "https://github.com/mlaval", "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" "type": "Community"
}, },
@ -372,7 +378,7 @@
"picture": "/resources/images/bios/patrick-stapleton.jpg", "picture": "/resources/images/bios/patrick-stapleton.jpg",
"twitter": "gdi2290", "twitter": "gdi2290",
"website": "https://angularclass.com", "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" "type": "Community"
}, },

View File

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

View File

@ -18,7 +18,7 @@ else
h3.text-headline 资源库 h3.text-headline 资源库
ul.text-body 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="/libraries.html">Libraries</a>
li <a href="/about/">About</a> li <a href="/about/">About</a>
li <a href="/about/">关于</a> li <a href="/about/">关于</a>

View File

@ -12,7 +12,7 @@ if title == "Angular"
else if language else if language
title #{title} - #{language} title #{title} - #{language}
else else
title #{title} - Angular 2 title #{title} - Angular
meta(charset="utf-8") meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible" content="IE=edge") 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="referrer" content="origin")
meta(name="viewport" id="viewport" content="width=device-width, initial-scale=1") 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" content="/resources/images/logos/standard/shield-large.png")
meta(property="og:image:type" content="image/png") meta(property="og:image:type" content="image/png")
meta(property="og:image:width" content="184") meta(property="og:image:width" content="184")
meta(property="og:image:height" content="200") meta(property="og:image:height" content="200")
meta(property="og:description" content="#{description}") meta(property="og:description" content="#{description}")
meta(itemprop="name" content="Angular 2") meta(itemprop="name" content="Angular")
meta(itemprop="description" content="#{description}") meta(itemprop="description" content="#{description}")
meta(itemprop="image" content="/resources/images/logos/standard/shield-large.png") 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 //- BUTTON TITLE GENERATION
if language == 'ts' if language == 'ts'
if version == "latest" if version == "latest"
- var title = 'Angular 2 for TypeScript' - var title = 'Angular for TypeScript'
else else
- var title = 'Angular ' + version + ' for TypeScript' - var title = 'Angular ' + version + ' for TypeScript'
if language == 'js' if language == 'js'
if version == "latest" if version == "latest"
- var title = 'Angular 2 for JavaScript' - var title = 'Angular for JavaScript'
else else
- var title = 'Angular ' + version + ' for JavaScript' - var title = 'Angular ' + version + ' for JavaScript'
if language == 'dart' if language == 'dart'
if version == "latest" if version == "latest"
- var title = 'Angular 2 for Dart' - var title = 'Angular for Dart'
else else
- var title = 'Angular ' + version + ' for Dart' - var title = 'Angular ' + version + ' for Dart'
@ -69,8 +69,8 @@ nav.dropdown
<!-- DROPDOWN MENU --> <!-- DROPDOWN MENU -->
ul(class="dropdown-menu" ng-class="appCtrl.showMenu ? 'is-visible' : ''") 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.ts, "/docs/ts", "Angular for TypeScript")
mixin tree(public.docs.js, "/docs/js", "Angular 2 for JavaScript") 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): //- 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]]) 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项目变得更好。 p 我们希望你能给我们的源代码做出贡献让Angular项目变得更好。
.l-sub-section .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) Contribute to Angular
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) 为Angular做贡献
.l-sub-section .l-sub-section
h3 Angular for JavaScript or Dart h3 Angular for JavaScript or Dart

View File

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

View File

@ -4,7 +4,7 @@
* The tests here basically just checking that the end styles * The tests here basically just checking that the end styles
* of each animation are in effect. * 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. * animation(s) have finished.
* *
* Ideally we'd use https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations * 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":[ "files":[
"!**/*.d.ts", "!**/*.d.ts",
"!**/*.js" "!**/*.js"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ describe('AOT Compilation', function () {
it('should load page and click button', function (done) { it('should load page and click button', function (done) {
let headingSelector = element.all(by.css('h1')).get(0); 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()="Magneta"]')).get(0).isPresent()).toBe(true);
expect(element.all(by.xpath('//div[text()="Bombasto"]')).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 --> <!-- #docregion -->
<button (click)="toggleHeading()">Toggle Heading</button> <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> <h3>List of Heroes</h3>
<div *ngFor="let hero of heroes">{{hero}}</div> <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": [ "files": [
"!**/*.d.ts", "!**/*.d.ts",
"!**/*.js", "!**/*.js",

View File

@ -7,6 +7,6 @@ describe('cli-quickstart App', () => {
it('should display message saying app works', () => { it('should display message saying app works', () => {
let pageTitle = element(by.css('cli-quickstart-app h1')).getText(); 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 // #enddocregion metadata
// #docregion title, class // #docregion title, class
export class CliQuickstartAppComponent { export class CliQuickstartAppComponent {
title = 'My First Angular 2 App'; title = 'My First Angular App';
} }
// #enddocregion title, class // #enddocregion title, class

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<!-- #docregion --> <!-- #docregion -->
<html> <html>
<head> <head>
<title>Angular 2 Lifecycle Hooks</title> <title>Angular Lifecycle Hooks</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css"> <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'; 'use strict';
describe('QuickStart E2E Tests', function () { describe('QuickStart E2E Tests', function () {
let expectedMsg = 'My First Angular 2 App'; let expectedMsg = 'My First Angular App';
beforeEach(function () { beforeEach(function () {
browser.get(''); browser.get('');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'my-app', 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 { } 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. * Adjust as necessary for your application needs.
*/ */
(function (global) { (function (global) {

View File

@ -1,7 +1,7 @@
/** /**
* PLUNKER VERSION FOR CURRENT ANGULAR BUILD * PLUNKER VERSION FOR CURRENT ANGULAR BUILD
* (based on systemjs.config.js in angular.io) * (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. * Adjust as necessary for your application needs.
* *
* UNTESTED ! * UNTESTED !

View File

@ -1,7 +1,7 @@
/** /**
* PLUNKER VERSION * PLUNKER VERSION
* (based on systemjs.config.js in angular.io) * (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. * Adjust as necessary for your application needs.
*/ */
(function (global) { (function (global) {

View File

@ -30,6 +30,7 @@
<script> <script>
var __spec_files__ = [ var __spec_files__ = [
'app/about.component.spec',
'app/app.component.spec', 'app/app.component.spec',
'app/app.component.router.spec', 'app/app.component.router.spec',
'app/banner.component.spec', 'app/banner.component.spec',
@ -42,7 +43,7 @@
'app/model/hero.spec', 'app/model/hero.spec',
'app/model/http-hero.service.spec', 'app/model/http-hero.service.spec',
'app/shared/title-case.pipe.spec', 'app/shared/title-case.pipe.spec',
'app/twain.component.spec', 'app/shared/twain.component.spec',
'app/welcome.component.spec' 'app/welcome.component.spec'
]; ];
</script> </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 // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
template: ` template: `
<h2 highlight="skyblue">About</h2> <h2 highlight="skyblue">About</h2>
<twain-quote></twain-quote> <twain-quote></twain-quote>
<p>All about this sample</p> <p>All about this sample</p>`
`,
styleUrls: ['app/shared/styles.css']
}) })
export class AboutComponent { } 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-banner></app-banner>
<app-welcome></app-welcome> <app-welcome></app-welcome>

View File

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

View File

@ -1,108 +1,94 @@
// #docplaster
import { async, ComponentFixture, TestBed import { async, ComponentFixture, TestBed
} from '@angular/core/testing'; } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component'; // #docregion setup-schemas
import { BannerComponent } from './banner.component'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { SharedModule } from './shared/shared.module'; // #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 // #enddocregion setup-schemas
} from '../testing'; @Component({selector: 'app-welcome', template: ''})
class WelcomeStubComponent {}
// #enddocregion setup-stubs-w-imports
let comp: AppComponent; let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>; let fixture: ComponentFixture<AppComponent>;
describe('AppComponent & TestModule', () => { describe('AppComponent & TestModule', () => {
// #docregion setup-stubs, setup-stubs-w-imports
beforeEach( async(() => { beforeEach( async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
AppComponent, BannerComponent, AppComponent,
FakeRouterLinkDirective, FakeRouterOutletComponent BannerComponent, WelcomeStubComponent,
], RouterLinkStubDirective, RouterOutletStubComponent
providers: [{ provide: Router, useClass: FakeRouter }], ]
schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents() .compileComponents()
.then(() => { .then(() => {
fixture = TestBed.createComponent(AppComponent); fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
}); });
})); }));
// #enddocregion setup-stubs, setup-stubs-w-imports
tests(); 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', () => { .compileComponents()
expect(comp).not.toBeNull(); .then(() => {
}); fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
it('can get RouterLinks from template', () => { });
fixture.detectChanges(); }));
// #enddocregion setup-schemas
const links = fixture.debugElement tests();
// 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');
});
}
//////// Testing w/ real root module ////// //////// Testing w/ real root module //////
// Best to avoid
// Tricky because we are disabling the router and its configuration // Tricky because we are disabling the router and its configuration
// Better to use RouterTestingModule
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { AppRoutingModule } from './app-routing.module';
describe('AppComponent & AppModule', () => { describe('AppComponent & AppModule', () => {
beforeEach( async(() => { beforeEach( async(() => {
TestBed.configureTestingModule({ 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, { .overrideModule(AppModule, {
// Must get rid of `RouterModule.forRoot` to prevent attempt to configure a router remove: {
// Can't remove it because it doesn't have a known type (`forRoot` returns an object) imports: [ AppRoutingModule ]
// 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, {
add: { add: {
declarations: [ FakeRouterLinkDirective, FakeRouterOutletComponent ], declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ]
providers: [{ provide: Router, useClass: FakeRouter }]
} }
}) })
@ -117,3 +103,46 @@ describe('AppComponent & AppModule', () => {
tests(); 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 // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
templateUrl: 'app/app.component.html' templateUrl: 'app/app.component.html'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
// #docplaster
import { import {
async, ComponentFixture, fakeAsync, inject, TestBed, tick async, ComponentFixture, fakeAsync, inject, TestBed, tick
} from '@angular/core/testing'; } from '@angular/core/testing';
@ -6,100 +7,197 @@ import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { import {
addMatchers, newEvent, ActivatedRoute, ActivatedRouteStub, click, newEvent, Router, RouterStub
ActivatedRoute, FakeActivatedRoute, Router, FakeRouter
} from '../../testing'; } from '../../testing';
import { HEROES, FakeHeroService } from '../model/testing'; import { Hero } from '../model';
import { HeroModule } from './hero.module';
import { HeroDetailComponent } from './hero-detail.component'; import { HeroDetailComponent } from './hero-detail.component';
import { HeroDetailService } from './hero-detail.service'; import { HeroDetailService } from './hero-detail.service';
import { Hero, HeroService } from '../model'; import { HeroModule } from './hero.module';
////// Testing Vars ////// ////// Testing Vars //////
let activatedRoute: FakeActivatedRoute; let activatedRoute: ActivatedRouteStub;
let comp: HeroDetailComponent; let comp: HeroDetailComponent;
let fixture: ComponentFixture<HeroDetailComponent>; let fixture: ComponentFixture<HeroDetailComponent>;
let page: Page; let page: Page;
////////// Tests //////////////////// ////// Tests //////
describe('HeroDetailComponent', () => { 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(() => { beforeEach( async(() => {
addMatchers(); createComponent();
activatedRoute = new FakeActivatedRoute(); // get the component's injected StubHeroDetailService
hds = fixture.debugElement.injector.get(HeroDetailService);
}));
TestBed.configureTestingModule({ it('should display stub hero\'s name', () => {
imports: [ HeroModule ], expect(page.nameDisplay.textContent).toBe(hds.testHero.name);
});
// DON'T RE-DECLARE because already declared in HeroModule it('should save stub hero change', fakeAsync(() => {
// declarations: [HeroDetailComponent, TitleCasePipe], // No! 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: [ providers: [
{ provide: ActivatedRoute, useValue: activatedRoute }, { provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HeroService, useClass: FakeHeroService }, { provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: FakeRouter}, { provide: Router, useClass: RouterStub},
] ]
}) })
.compileComponents(); .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; let expectedHero: Hero;
beforeEach( async(() => { beforeEach( async(() => {
expectedHero = HEROES[0]; expectedHero = firstHero;
activatedRoute.testParams = { id: expectedHero.id }; activatedRoute.testParams = { id: expectedHero.id };
createComponent(); createComponent();
})); }));
// #docregion selected-tests
it('should display that hero\'s name', () => { it('should display that hero\'s name', () => {
expect(page.nameDisplay.textContent).toBe(expectedHero.name); expect(page.nameDisplay.textContent).toBe(expectedHero.name);
}); });
// #enddocregion route-good-id
it('should navigate when click cancel', () => { it('should navigate when click cancel', () => {
page.cancelBtn.triggerEventHandler('click', null); click(page.cancelBtn);
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
}); });
it('should save when click save', () => { it('should save when click save but not navigate immediately', () => {
page.saveBtn.triggerEventHandler('click', null); click(page.saveBtn);
expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called'); 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(() => { it('should navigate when click save and save resolves', fakeAsync(() => {
page.saveBtn.triggerEventHandler('click', null); click(page.saveBtn);
tick(); // waits for async save to "complete" before navigating tick(); // wait for async save to complete
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
})); }));
// #docregion title-case-pipe // #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(() => { it('should convert hero name to Title Case', fakeAsync(() => {
const inputName = 'quick BROWN fox'; 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; page.nameInput.value = inputName;
// dispatch a DOM event so that Angular learns of input value change. // 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')); page.nameInput.dispatchEvent(newEvent('input'));
// Tell Angular to update the output span through the title pipe
fixture.detectChanges(); 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', () => { describe('when navigate with no hero id', () => {
beforeEach( async( createComponent )); beforeEach( async( createComponent ));
@ -111,7 +209,9 @@ describe('HeroDetailComponent', () => {
expect(page.nameDisplay.textContent).toBe(''); expect(page.nameDisplay.textContent).toBe('');
}); });
}); });
// #enddocregion route-no-id
// #docregion route-bad-id
describe('when navigate to non-existant hero id', () => { describe('when navigate to non-existant hero id', () => {
beforeEach( async(() => { beforeEach( async(() => {
activatedRoute.testParams = { id: 99999 }; activatedRoute.testParams = { id: 99999 };
@ -123,11 +223,10 @@ describe('HeroDetailComponent', () => {
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
}); });
}); });
// #enddocregion route-bad-id
///////////////////////////
// Why we must use `fixture.debugElement.injector` in `Page()` // 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; let service: HeroDetailService;
fixture = TestBed.createComponent(HeroDetailComponent); fixture = TestBed.createComponent(HeroDetailComponent);
expect( expect(
@ -141,26 +240,85 @@ describe('HeroDetailComponent', () => {
service = fixture.debugElement.injector.get(HeroDetailService); service = fixture.debugElement.injector.get(HeroDetailService);
expect(service).toBeDefined('debugElement.injector'); 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 ///// /////////// Helpers /////
// #docregion create-component
/** Create the HeroDetailComponent, initialize it, set test variables */ /** Create the HeroDetailComponent, initialize it, set test variables */
function createComponent() { function createComponent() {
fixture = TestBed.createComponent(HeroDetailComponent); fixture = TestBed.createComponent(HeroDetailComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
page = new Page(); page = new Page();
// change detection triggers ngOnInit which gets a hero // 1st change detection triggers ngOnInit which gets a hero
fixture.detectChanges(); fixture.detectChanges();
return fixture.whenStable().then(() => { return fixture.whenStable().then(() => {
// got the hero and updated component // 2nd change detection displays the async-fetched hero
// change detection updates the view
fixture.detectChanges(); fixture.detectChanges();
page.addPageElements(); page.addPageElements();
}); });
} }
// #enddocregion create-component
// #docregion page
class Page { class Page {
gotoSpy: jasmine.Spy; gotoSpy: jasmine.Spy;
navSpy: jasmine.Spy; navSpy: jasmine.Spy;
@ -173,19 +331,20 @@ class Page {
constructor() { constructor() {
// Use component's injector to see the services it injected. // Use component's injector to see the services it injected.
let compInjector = fixture.debugElement.injector; const compInjector = fixture.debugElement.injector;
let hds = compInjector.get(HeroDetailService); const hds = compInjector.get(HeroDetailService);
let router = compInjector.get(Router); const router = compInjector.get(Router);
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
this.saveSpy = spyOn(hds, 'saveHero').and.callThrough(); this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
this.navSpy = spyOn(router, 'navigate').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() { addPageElements() {
if (comp.hero) { if (comp.hero) {
// have a hero so these DOM elements can be reached // have a hero so these elements are now in the DOM
let buttons = fixture.debugElement.queryAll(By.css('button')); const buttons = fixture.debugElement.queryAll(By.css('button'));
this.saveBtn = buttons[0]; this.saveBtn = buttons[0];
this.cancelBtn = buttons[1]; this.cancelBtn = buttons[1];
this.nameDisplay = fixture.debugElement.query(By.css('span')).nativeElement; 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 { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import 'rxjs/add/operator/pluck';
import { Hero } from '../model'; import { Hero } from '../model';
import { HeroDetailService } from './hero-detail.service'; import { HeroDetailService } from './hero-detail.service';
// #docregion prototype
@Component({ @Component({
selector: 'app-hero-detail', selector: 'app-hero-detail',
templateUrl: 'app/hero/hero-detail.component.html', templateUrl: 'app/hero/hero-detail.component.html',
styleUrls: [ styleUrls: ['app/hero/hero-detail.component.css'],
'app/shared/styles.css',
'app/hero/hero-detail.component.css'
],
providers: [ HeroDetailService ] providers: [ HeroDetailService ]
}) })
export class HeroDetailComponent implements OnInit { export class HeroDetailComponent implements OnInit {
@Input() hero: Hero; // #docregion ctor
constructor( constructor(
private heroDetailService: HeroDetailService, private heroDetailService: HeroDetailService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router) { private router: Router) {
} }
// #enddocregion ctor
// #enddocregion prototype
ngOnInit() { @Input() hero: Hero;
let id = this.route.snapshot.params['id'];
// tslint:disable-next-line:triple-equals // #docregion ng-on-init
if (id == undefined) { ngOnInit(): void {
// no id; act as if is new // get hero when `id` param changes
this.hero = new Hero(); this.route.params.pluck<string>('id')
} else { .forEach(id => this.getHero(id))
this.heroDetailService.getHero(id).then(hero => { .catch(() => this.hero = new Hero()); // no id; should edit new hero
if (hero) { }
this.hero = hero; // #enddocregion ng-on-init
} else {
this.gotoList(); // id not found; navigate to list 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()); this.heroDetailService.saveHero(this.hero).then(() => this.gotoList());
} }
@ -49,4 +54,6 @@ export class HeroDetailComponent implements OnInit {
gotoList() { gotoList() {
this.router.navigate(['../'], {relativeTo: this.route}); 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'; import { Hero, HeroService } from '../model';
// #docregion prototype
@Injectable() @Injectable()
export class HeroDetailService { export class HeroDetailService {
constructor(private heroService: HeroService) { } constructor(private heroService: HeroService) { }
// #enddocregion prototype
// Returns a clone which caller may modify safely
getHero(id: number | string): Promise<Hero> { getHero(id: number | string): Promise<Hero> {
if (typeof id === 'string') { if (typeof id === 'string') {
id = parseInt(id as string, 10); id = parseInt(id as string, 10);
@ -18,4 +21,6 @@ export class HeroDetailService {
saveHero(hero: Hero) { saveHero(hero: Hero) {
return this.heroService.updateHero(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 { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { addMatchers, newEvent, Router, FakeRouter import { addMatchers, newEvent, Router, RouterStub
} from '../../testing'; } from '../../testing';
import { HEROES, FakeHeroService } from '../model/testing'; import { HEROES, FakeHeroService } from '../model/testing';
@ -28,7 +28,7 @@ describe('HeroListComponent', () => {
imports: [HeroModule], imports: [HeroModule],
providers: [ providers: [
{ provide: HeroService, useClass: FakeHeroService }, { provide: HeroService, useClass: FakeHeroService },
{ provide: Router, useClass: FakeRouter} { provide: Router, useClass: RouterStub}
] ]
}) })
.compileComponents() .compileComponents()
@ -132,7 +132,7 @@ class Page {
// Get the component's injected router and spy on it // Get the component's injected router and spy on it
const router = fixture.debugElement.injector.get(Router); 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({ @Component({
selector: 'app-heroes', selector: 'app-heroes',
templateUrl: 'app/hero/hero-list.component.html', templateUrl: 'app/hero/hero-list.component.html',
styleUrls: [ styleUrls: ['app/hero/hero-list.component.css']
'app/shared/styles.css',
'app/hero/hero-list.component.css'
]
}) })
export class HeroListComponent implements OnInit { export class HeroListComponent implements OnInit {
heroes: Promise<Hero[]>; heroes: Promise<Hero[]>;

View File

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

View File

@ -1,58 +1,100 @@
import { Component, DebugElement } from '@angular/core'; import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; 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({ @Component({
template: ` template: `
<h2 highlight="yellow">Something Yellow</h2> <h2 highlight="yellow">Something Yellow</h2>
<h2 highlight>Something Gray</h2> <h2 highlight>The Default (Gray)</h2>
<h2>Something White</h2> <h2>No Highlight</h2>
` <input #box [highlight]="box.value" value="cyan"/>`
}) })
class TestComponent { } class TestComponent { }
// #enddocregion test-component
////// Tests //////////
describe('HighlightDirective', () => { describe('HighlightDirective', () => {
let fixture: ComponentFixture<TestComponent>; 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(() => { beforeEach(() => {
fixture = TestBed.configureTestingModule({ fixture = TestBed.configureTestingModule({
declarations: [ HighlightDirective, TestComponent ] declarations: [ HighlightDirective, TestComponent ]
}) })
.createComponent(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`', () => { // color tests
// The HighlightDirective listed in <h2> tokens means it is attached it('should have three highlighted elements', () => {
expect(h2Des[0].providerTokens).toContain(HighlightDirective, 'HighlightDirective'); expect(des.length).toBe(3);
}); });
it('should color first <h2> background "yellow"', () => { it('should color 1st <h2> background "yellow"', () => {
fixture.detectChanges(); expect(des[0].styles['backgroundColor']).toBe('yellow');
const h2 = h2Des[0].nativeElement as HTMLElement;
expect(h2.style.backgroundColor).toBe('yellow');
}); });
it('should color second <h2> background w/ default color', () => { it('should color 2nd <h2> background w/ default color', () => {
fixture.detectChanges(); const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;
const h2 = h2Des[1].nativeElement as HTMLElement; expect(des[1].styles['backgroundColor']).toBe(dir.defaultColor);
expect(h2.style.backgroundColor).toBe(HighlightDirective.defaultColor);
}); });
it('should NOT color third <h2> (no directive)', () => { it('should bind <input> background to value color', () => {
// no directive // easier to work with nativeElement
expect(h2Des[2].providerTokens).not.toContain(HighlightDirective, 'HighlightDirective'); 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(); fixture.detectChanges();
const h2 = h2Des[2].nativeElement as HTMLElement; expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');
expect(h2.style.backgroundColor).toBe('', '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'; import { Directive, ElementRef, Input, OnChanges, Renderer } from '@angular/core';
@Directive({ selector: '[highlight]' }) @Directive({ selector: '[highlight]' })
/** /** Set backgroundColor for the attached element to highlight color
* Set backgroundColor for the attached element ton highlight color and * and set the element's customProperty to true */
* set element `customProperty` = true
*/
export class HighlightDirective implements OnChanges { export class HighlightDirective implements OnChanges {
static defaultColor = 'rgb(211, 211, 211)'; // lightgray defaultColor = 'rgb(211, 211, 211)'; // lightgray
@Input('highlight') bgColor: string; @Input('highlight') bgColor: string;
@ -18,7 +17,6 @@ export class HighlightDirective implements OnChanges {
ngOnChanges() { ngOnChanges() {
this.renderer.setElementStyle( this.renderer.setElementStyle(
this.el.nativeElement, 'backgroundColor', 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 userService: UserService; // the actually injected service
let welcomeEl: DebugElement; // the element with the welcome message let welcomeEl: DebugElement; // the element with the welcome message
let userServiceStub: {
isLoggedIn: boolean;
user: { name: string}
};
// #docregion setup // #docregion setup
beforeEach(() => { beforeEach(() => {
// fake UserService for test purposes // stub UserService for test purposes
// #docregion fake-userservice // #docregion user-service-stub
const fakeUserService = { userServiceStub = {
isLoggedIn: true, isLoggedIn: true,
user: { name: 'Test User'} user: { name: 'Test User'}
}; };
// #enddocregion fake-userservice // #enddocregion user-service-stub
// #docregion config-test-module // #docregion config-test-module
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -29,7 +34,7 @@ describe('WelcomeComponent', () => {
// #enddocregion setup // #enddocregion setup
// providers: [ UserService ] // a real service would be a problem! // providers: [ UserService ] // a real service would be a problem!
// #docregion setup // #docregion setup
providers: [ {provide: UserService, useValue: fakeUserService } ] providers: [ {provide: UserService, useValue: userServiceStub } ]
}); });
// #enddocregion config-test-module // #enddocregion config-test-module
@ -80,4 +85,8 @@ describe('WelcomeComponent', () => {
expect(content).toMatch(/log in/i, '"log in"'); expect(content).toMatch(/log in/i, '"log in"');
}); });
// #enddocregion tests // #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. // Uncomment to get full stacktrace output. Sometimes helpful, usually not.
// Error.stackTraceLimit = Infinity; // // Error.stackTraceLimit = Infinity; //
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
var baseURL = document.baseURI; var baseURL = document.baseURI;
baseURL = baseURL + baseURL[baseURL.length-1] ? '' : '/'; 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'; import { tick, ComponentFixture } from '@angular/core/testing';
export * from './jasmine-matchers'; 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 * Create custom DOM event the old fashioned way
* *
@ -16,8 +24,20 @@ export function newEvent(eventName: string, bubbles = false, cancelable = false)
return evt; return evt;
} }
/** Wait a tick, then detect changes */ // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
export function advance(f: ComponentFixture<any>): void { // #docregion click-event
tick(); /** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
f.detectChanges(); 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>; type WPromise<T> = webdriver.promise.Promise<T>;
const expectedH1 = 'Tour of Heroes'; const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular 2 ${expectedH1}`; const expectedTitle = `Angular ${expectedH1}`;
class Hero { class Hero {
id: number; id: number;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,9 +23,6 @@ void main() {
bootstrap(AppComponent, [ bootstrap(AppComponent, [
provide(BrowserClient, useFactory: () => new BrowserClient(), deps: []) 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 // #enddocregion v1
*/ */

View File

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

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<base href="/"> <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"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">

View File

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

View File

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

View File

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

View File

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

View File

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

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