Merge remote-tracking branch 'origin/master'
# Conflicts: # public/_includes/_main-nav.jade # public/_includes/_version-dropdown.jade # public/docs/_includes/_side-nav.jade
This commit is contained in:
commit
b86aa26da9
|
@ -76,7 +76,8 @@ var _exampleBoilerplateFiles = [
|
|||
'styles.css',
|
||||
'tsconfig.json',
|
||||
'tslint.json',
|
||||
'typings.json'
|
||||
'typings.json',
|
||||
'wallaby.js'
|
||||
];
|
||||
|
||||
var _exampleDartWebBoilerPlateFiles = ['styles.css'];
|
||||
|
|
|
@ -3,23 +3,22 @@
|
|||
- var headerTitle = title + (typeof varType !== 'undefined' ? (': ' + varType) : '');
|
||||
- var capitalize = function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
|
||||
- var useBadges = docType || stability;
|
||||
|
||||
|
||||
if current.path[4] && current.path[3] == 'api'
|
||||
- var textFormat = 'is-standard-case'
|
||||
|
||||
header(class="hero background-sky")
|
||||
span(class="hero-title-with-badges" layout="row" layout-xs="column" layout-align="start center" layout-align-xs="start start")
|
||||
h1(class="hero-title text-display-1 #{textFormat}") #{headerTitle}
|
||||
if useBadges
|
||||
span(class="badges")
|
||||
if docType
|
||||
span(class="status-badge").
|
||||
#{capitalize(docType)}
|
||||
if stability
|
||||
span(layout="row" class="status-badge")
|
||||
// badge circle is filled based on stability by matching a css selector in _hero.scss
|
||||
span(class="status-circle status-#{stability}")
|
||||
span Stability: #{capitalize(stability)}
|
||||
h1(class="hero-title text-display-1 #{textFormat}") #{headerTitle}
|
||||
if useBadges
|
||||
span(class="badges")
|
||||
if docType
|
||||
span(class="status-badge").
|
||||
#{capitalize(docType)}
|
||||
if stability
|
||||
span(layout="row" class="status-badge")
|
||||
// badge circle is filled based on stability by matching a css selector in _hero.scss
|
||||
span(class="status-circle status-#{stability}")
|
||||
span Stability: #{capitalize(stability)}
|
||||
|
||||
if subtitle
|
||||
h2.hero-subtitle.text-subhead #{subtitle}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
- var language = current.path[1] || 'ts'
|
||||
- if (language !== 'ts' || language !== 'js' || language !== 'dart') { language = 'ts'; }
|
||||
|
||||
md-toolbar(class="main-nav background-regal l-pinned-top l-layer-5",scroll-y-offset-element)
|
||||
nav
|
||||
h1 <a href="/" md-button>Angular <sup>by Google</sup></a>
|
||||
|
@ -6,10 +9,10 @@ md-toolbar(class="main-nav background-regal l-pinned-top l-layer-5",scroll-y-off
|
|||
|
||||
ul(ng-class="appCtrl.showMainNav ? 'is-visible' : ''")
|
||||
li.l-left <a class="main-nav-button" href="/features.html" md-button>特性</a>
|
||||
li.l-left <a class="main-nav-button" href="/docs/ts/latest/" md-button>文档</a>
|
||||
li.l-left <a class="main-nav-button" href="/docs/#{language}/latest/" md-button>文档</a>
|
||||
li.l-left <a class="main-nav-button" href="/about/" md-button>关于</a>
|
||||
li.l-left <a class="main-nav-button" href="/contribute.html" md-button>贡献</a>
|
||||
li.l-left <a class="main-nav-button" href="/support.html" md-button>支持</a>
|
||||
li.l-left <a class="main-nav-button" href="/news.html" md-button>新闻</a>
|
||||
li.l-left <a class="main-nav-button" href="/events.html" md-button>事件</a>
|
||||
li.l-right.feedback-button <md-button ng-click="appCtrl.openFeedback()" class="md-icon-button top-nav-icon" aria-label="提交关于此页面的反馈"><span class="material-icons">feedback</span></md-button>
|
||||
li.l-right.feedback-button <md-button ng-click="appCtrl.openFeedback()" class="md-icon-button top-nav-icon" aria-label="提交关于本页面的反馈"><span class="material-icons">feedback</span></md-button>
|
||||
|
|
|
@ -64,7 +64,7 @@ if language == 'dart'
|
|||
|
||||
|
||||
<!-- DROPDOWN BUTTON -->
|
||||
nav.hero-subtitle.text-subhead.dropdown
|
||||
nav.dropdown
|
||||
button(aria-label="选择Angular版本" md-button class="dropdown-button" ng-click="appCtrl.toggleVersionMenu($event)") #{title} <span class="icon icon-arrow-drop-down"></span>
|
||||
div(class="overlay ng-hide" ng-click="appCtrl.toggleVersionMenu($event)" ng-show="appCtrl.showMenu")
|
||||
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
.editorconfig
|
||||
.idea
|
||||
.vscode
|
||||
styles.css
|
||||
typings
|
||||
typings.json
|
||||
node_modules
|
||||
jspm_packages
|
||||
*.js.map
|
||||
package.json
|
||||
karma.conf.js
|
||||
karma-test-shim.js
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
wallaby.js
|
||||
npm-debug*.
|
||||
**/protractor.config.js
|
||||
protractor.config.js
|
||||
_test-output
|
||||
_temp
|
||||
|
||||
!**/*e2e-spec.js
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
describe('Displaying Data Tests', function () {
|
||||
|
||||
var _title = "Tour of Heroes";
|
||||
var _defaultHero = 'Windstorm'
|
||||
|
||||
|
@ -15,7 +14,12 @@ describe('Displaying Data Tests', function () {
|
|||
expect(element(by.css('h2')).getText()).toContain(_defaultHero);
|
||||
});
|
||||
|
||||
it('should have many heroes', function () {
|
||||
it('should have heroes', function () {
|
||||
var heroEls = element.all(by.css('li'));
|
||||
expect(heroEls.count()).not.toBe(0, 'should have heroes');
|
||||
});
|
||||
|
||||
it('should display "there are many heroes!"', function () {
|
||||
expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// #docregion imports
|
||||
import {Component} from 'angular2/core';
|
||||
// #enddocregion imports
|
||||
import {Hero} from './hero'
|
||||
import {Hero} from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
class Hero {
|
||||
String name;
|
||||
String power;
|
||||
|
|
|
@ -7,7 +7,6 @@ class RestoreService<T> {
|
|||
T _currentItem;
|
||||
|
||||
setItem(T item) {
|
||||
print(item.runtimeType);
|
||||
_originalItem = item;
|
||||
_currentItem = clone(item);
|
||||
}
|
||||
|
|
|
@ -41,30 +41,29 @@ System.config({ packages: packages });
|
|||
|
||||
// Configure Angular for the browser and
|
||||
// with test versions of the platform providers
|
||||
System.import('angular2/testing')
|
||||
.then(function (testing) {
|
||||
return System.import('angular2/platform/testing/browser')
|
||||
.then(function (providers) {
|
||||
testing.setBaseTestProviders(
|
||||
providers.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
providers.TEST_BROWSER_APPLICATION_PROVIDERS
|
||||
);
|
||||
});
|
||||
Promise.all([
|
||||
System.import('angular2/testing'),
|
||||
System.import('angular2/platform/testing/browser')
|
||||
])
|
||||
.then(function (results) {
|
||||
var testing = results[0];
|
||||
var browser = results[1];
|
||||
testing.setBaseTestProviders(
|
||||
browser.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
browser.TEST_BROWSER_APPLICATION_PROVIDERS);
|
||||
|
||||
// Load all spec files
|
||||
// (e.g. 'base/app/hero.service.spec.js')
|
||||
return Promise.all(
|
||||
Object.keys(window.__karma__.files)
|
||||
.filter(onlySpecFiles)
|
||||
.map(function (moduleName) {
|
||||
moduleNames.push(moduleName);
|
||||
return System.import(moduleName);
|
||||
}));
|
||||
})
|
||||
|
||||
// Load all spec files
|
||||
// (e.g. 'base/app/hero.service.spec.js')
|
||||
.then(function () {
|
||||
return Promise.all(
|
||||
Object.keys(window.__karma__.files)
|
||||
.filter(onlySpecFiles)
|
||||
.map(function (moduleName) {
|
||||
moduleNames.push(moduleName);
|
||||
return System.import(moduleName);
|
||||
}));
|
||||
})
|
||||
|
||||
.then(success, fail);
|
||||
.then(success, fail);
|
||||
|
||||
////// Helpers //////
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module.exports = function(config) {
|
||||
|
||||
var appBase = 'app/'; // transpiled app JS files
|
||||
var appAssets ='base/app/'; // component assets fetched by Angular's compiler
|
||||
var appBase = 'app/'; // transpiled app JS files
|
||||
var appAssets ='/base/app/'; // component assets fetched by Angular's compiler
|
||||
|
||||
config.set({
|
||||
basePath: '',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* tslint:disable:forin */
|
||||
// #docregion
|
||||
import {
|
||||
Component, Input, ViewChild,
|
||||
|
@ -6,7 +7,7 @@ import {
|
|||
|
||||
|
||||
class Hero {
|
||||
constructor(public name:string){}
|
||||
constructor(public name: string) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
@ -30,13 +31,13 @@ export class OnChangesComponent implements OnChanges {
|
|||
@Input() power: string;
|
||||
// #enddocregion inputs
|
||||
|
||||
changeLog:string[] = [];
|
||||
changeLog: string[] = [];
|
||||
|
||||
// #docregion ng-on-changes
|
||||
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
||||
for (let propName in changes) {
|
||||
let prop = changes[propName];
|
||||
let cur = JSON.stringify(prop.currentValue)
|
||||
let cur = JSON.stringify(prop.currentValue);
|
||||
let prev = JSON.stringify(prop.previousValue);
|
||||
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
||||
}
|
||||
|
@ -50,13 +51,13 @@ export class OnChangesComponent implements OnChanges {
|
|||
|
||||
@Component({
|
||||
selector: 'on-changes-parent',
|
||||
templateUrl:'app/on-changes-parent.component.html',
|
||||
templateUrl: 'app/on-changes-parent.component.html',
|
||||
styles: ['.parent {background: Lavender;}'],
|
||||
directives: [OnChangesComponent]
|
||||
})
|
||||
export class OnChangesParentComponent {
|
||||
hero:Hero;
|
||||
power:string;
|
||||
hero: Hero;
|
||||
power: string;
|
||||
title = 'OnChanges';
|
||||
@ViewChild(OnChangesComponent) childView:OnChangesComponent;
|
||||
|
||||
|
@ -69,6 +70,6 @@ export class OnChangesParentComponent {
|
|||
this.hero = new Hero('Windstorm');
|
||||
// setting power only triggers onChanges if this value is different
|
||||
this.power = 'sing';
|
||||
this.childView && this.childView.reset();
|
||||
if (this.childView) { this.childView.reset(); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,18 @@
|
|||
"name": "angular2-examples-master",
|
||||
"version": "1.0.0",
|
||||
"description": "Master package.json, the superset of all dependencies for all of the _example package.json files.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
|
||||
"tsc": "tsc",
|
||||
"tsc:w": "tsc -w",
|
||||
"lite": "lite-server",
|
||||
"live": "live-server",
|
||||
"test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
|
||||
"build-and-test": "npm run tsc && npm run test",
|
||||
"e2e": "tsc && concurrently \"http-server\" \"protractor protractor.config.js\"",
|
||||
"http-server": "tsc && http-server",
|
||||
"http-server:e2e": "http-server",
|
||||
"lite": "lite-server",
|
||||
"postinstall": "typings install",
|
||||
"test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
|
||||
"tsc": "tsc",
|
||||
"tsc:w": "tsc -w",
|
||||
"typings": "typings",
|
||||
"postinstall": "typings install"
|
||||
"webdriver:update": "webdriver-manager update"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
@ -37,6 +36,7 @@
|
|||
"typescript": "^1.8.10",
|
||||
"typings":"^0.7.12",
|
||||
|
||||
"canonical-path": "0.0.2",
|
||||
"http-server": "^0.9.0",
|
||||
"jasmine-core": "~2.4.1",
|
||||
"karma": "^0.13.22",
|
||||
|
@ -44,7 +44,6 @@
|
|||
"karma-cli": "^0.1.2",
|
||||
"karma-htmlfile-reporter": "^0.2.2",
|
||||
"karma-jasmine": "^0.3.8",
|
||||
"live-server": "^0.9.2",
|
||||
"protractor": "^3.2.2",
|
||||
"rimraf": "^2.5.2"
|
||||
},
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
// TO RUN THE TESTS
|
||||
//
|
||||
// The first time, run:
|
||||
// FIRST TIME ONLY- run:
|
||||
// ./node_modules/.bin/webdriver-manager update
|
||||
// Make sure the test server is running. Then do.
|
||||
// ./node_modules/.bin/protractor protractor.config.js
|
||||
//
|
||||
// Try: `npm run webdriver:update`
|
||||
//
|
||||
// AND THEN EVERYTIME ...
|
||||
// 1. Compile with `tsc`
|
||||
// 2. Make sure the test server (e.g., http-server: localhost:8080) is running.
|
||||
// 3. ./node_modules/.bin/protractor protractor.config.js
|
||||
//
|
||||
// To do all steps, try: `npm run e2e`
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('canonical-path');
|
||||
|
@ -92,17 +97,13 @@ function itIf(cond, name, func) {
|
|||
}
|
||||
}
|
||||
|
||||
// Hack - because of bug with send keys
|
||||
// Hack - because of bug with protractor send keys
|
||||
function sendKeys(element, str) {
|
||||
return str.split('').reduce(function (promise, char) {
|
||||
return promise.then(function () {
|
||||
return element.sendKeys(char);
|
||||
});
|
||||
return promise.resolve(element.sendKeys(char));
|
||||
}, element.getAttribute('value'));
|
||||
// better to create a resolved promise here but ... don't know how with protractor;
|
||||
}
|
||||
|
||||
|
||||
function Reporter(options) {
|
||||
var _defaultOutputFile = path.resolve(process.cwd(), "../../", 'protractor-results.txt');
|
||||
options.outputFile = options.outputFile || _defaultOutputFile;
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
|
||||
"lite": "lite-server",
|
||||
"postinstall": "typings install",
|
||||
"tsc": "tsc",
|
||||
"tsc:w": "tsc -w",
|
||||
"lite": "lite-server",
|
||||
"typings": "typings",
|
||||
"postinstall": "typings install"
|
||||
"typings": "typings"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
import { By } from 'angular2/platform/browser';
|
||||
import { provide } from 'angular2/core';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
inject, injectAsync,
|
||||
ComponentFixture, TestComponentBuilder
|
||||
} from 'angular2/testing';
|
||||
|
||||
import { Hero, HeroService, MockHeroService } from './mock-hero.service';
|
||||
|
||||
import { Router, MockRouter,
|
||||
RouterLink, MockRouterLink,
|
||||
RouterOutlet, MockRouterOutlet} from './mock-router';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let fixture: ComponentFixture;
|
||||
let comp: AppComponent;
|
||||
|
||||
beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
return tcb
|
||||
.overrideDirective(AppComponent, RouterLink, MockRouterLink)
|
||||
.overrideDirective(AppComponent, RouterOutlet, MockRouterOutlet)
|
||||
.overrideProviders(AppComponent, [
|
||||
provide(HeroService, {useClass: MockHeroService}),
|
||||
provide(Router, {useClass: MockRouter}),
|
||||
])
|
||||
.createAsync(AppComponent)
|
||||
.then(fix => {
|
||||
fixture = fix;
|
||||
comp = fixture.debugElement.componentInstance;
|
||||
});
|
||||
}));
|
||||
|
||||
it('can instantiate it', () => {
|
||||
expect(comp).not.toBeNull();
|
||||
});
|
||||
|
||||
it('can get title from template', () => {
|
||||
fixture.detectChanges();
|
||||
let titleEl = fixture.debugElement.query(By.css('h1')).nativeElement;
|
||||
expect(titleEl).toHaveText(comp.title);
|
||||
});
|
||||
|
||||
it('can get RouterLinks from template', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let links = fixture.debugElement
|
||||
.queryAll(function (de) { return de.componentInstance instanceof MockRouterLink; })
|
||||
.map(de => <MockRouterLink> de.componentInstance);
|
||||
|
||||
expect(links.length).toEqual(2, 'should have 2 links');
|
||||
expect(links[0].routeParams[0]).toEqual('Dashboard', '1st link should go to Dashboard');
|
||||
expect(links[1].routeParams[0]).toEqual('Heroes', '1st link should go to Heroes');
|
||||
|
||||
let result = links[1].onClick();
|
||||
expect(result).toEqual(false, 'click should prevent default browser behavior');
|
||||
});
|
||||
|
||||
it('can click Heroes link in template', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
// Heroes RouterLink DebugElement
|
||||
let heroesDe = fixture.debugElement
|
||||
.queryAll(function (de) { return de.componentInstance instanceof MockRouterLink; })[1];
|
||||
|
||||
expect(heroesDe).not.toBeNull('should 2nd link');
|
||||
|
||||
let link = <MockRouterLink> heroesDe.componentInstance;
|
||||
expect(link.navigatedTo).toBeNull('link should not have navigate yet');
|
||||
|
||||
heroesDe.triggerEventHandler('click', null);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(link.navigatedTo[0]).toEqual('Heroes');
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from 'angular2/core';
|
||||
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
|
||||
|
||||
import { HeroService } from './hero.service';
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroesComponent } from './heroes.component';
|
||||
// #docregion hero-detail-import
|
||||
// Can't test with ROUTER_DIRECTIVES yet
|
||||
// import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
|
||||
|
||||
import { RouteConfig, RouterLink,
|
||||
RouterOutlet, ROUTER_PROVIDERS } from 'angular2/router';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroesComponent } from './heroes.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
// #enddocregion hero-detail-import
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
// #docregion template
|
||||
template: `
|
||||
<h1>{{title}}</h1>
|
||||
<nav>
|
||||
|
@ -21,39 +23,18 @@ import { HeroDetailComponent } from './hero-detail.component';
|
|||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
// #enddocregion template
|
||||
// #docregion style-urls
|
||||
styleUrls: ['app/app.component.css'],
|
||||
// #enddocregion style-urls
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
directives: [RouterLink, RouterOutlet],
|
||||
providers: [
|
||||
ROUTER_PROVIDERS,
|
||||
HeroService
|
||||
]
|
||||
})
|
||||
@RouteConfig([
|
||||
// #docregion dashboard-route
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: DashboardComponent,
|
||||
useAsDefault: true
|
||||
},
|
||||
// #enddocregion dashboard-route
|
||||
// #docregion hero-detail-route
|
||||
{
|
||||
path: '/detail/:id',
|
||||
name: 'HeroDetail',
|
||||
component: HeroDetailComponent
|
||||
},
|
||||
// #enddocregion hero-detail-route
|
||||
{
|
||||
path: '/heroes',
|
||||
name: 'Heroes',
|
||||
component: HeroesComponent
|
||||
}
|
||||
{ path: '/dashboard', name: 'Dashboard', component: DashboardComponent, useAsDefault: true },
|
||||
{ path: '/detail/:id', name: 'HeroDetail', component: HeroDetailComponent },
|
||||
{ path: '/heroes', name: 'Heroes', component: HeroesComponent }
|
||||
])
|
||||
export class AppComponent {
|
||||
title = 'Tour of Heroes';
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -5,28 +5,22 @@ import {
|
|||
ChildChildComp, ChildComp, ChildWithChildComp,
|
||||
ExternalTemplateComp,
|
||||
FancyService, MockFancyService,
|
||||
MyIfComp,
|
||||
InputComp,
|
||||
MyIfComp, MyIfChildComp, MyIfParentComp,
|
||||
MockChildComp, MockChildChildComp,
|
||||
ParentComp,
|
||||
TestProvidersComp, TestViewProvidersComp
|
||||
} from './public';
|
||||
} from './bag';
|
||||
|
||||
import { DebugElement } from 'angular2/core';
|
||||
import { By } from 'angular2/platform/browser';
|
||||
|
||||
import {
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
describe,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
expect,
|
||||
fakeAsync,
|
||||
tick,
|
||||
beforeEach,
|
||||
inject,
|
||||
injectAsync,
|
||||
withProviders,
|
||||
beforeEachProviders,
|
||||
TestComponentBuilder
|
||||
beforeEach, beforeEachProviders, withProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
inject, injectAsync, fakeAsync, tick,
|
||||
ComponentFixture, TestComponentBuilder
|
||||
} from 'angular2/testing';
|
||||
|
||||
import { provide } from 'angular2/core';
|
||||
|
@ -49,13 +43,13 @@ describe('angular2 jasmine matchers', () => {
|
|||
describe('toHaveCssClass', () => {
|
||||
it('should assert that the CSS class is present', () => {
|
||||
let el = document.createElement('div');
|
||||
el.classList.add('matias');
|
||||
expect(el).toHaveCssClass('matias');
|
||||
el.classList.add('bombasto');
|
||||
expect(el).toHaveCssClass('bombasto');
|
||||
});
|
||||
|
||||
it('should assert that the CSS class is not present', () => {
|
||||
let el = document.createElement('div');
|
||||
el.classList.add('matias');
|
||||
el.classList.add('bombasto');
|
||||
expect(el).not.toHaveCssClass('fatias');
|
||||
});
|
||||
});
|
||||
|
@ -187,13 +181,39 @@ describe('test component builder', function() {
|
|||
let comp = <ButtonComp> fixture.componentInstance;
|
||||
expect(comp.wasClicked).toEqual(false, 'wasClicked should be false at start');
|
||||
|
||||
let btn = fixture.debugElement.query(el => el.name === 'button');
|
||||
let btn = fixture.debugElement.query(By.css('button'));
|
||||
// let btn = fixture.debugElement.query(el => el.name === 'button'); // the hard way
|
||||
|
||||
btn.triggerEventHandler('click', null);
|
||||
// btn.nativeElement.click(); // this works too; which is "better"?
|
||||
// btn.nativeElement.click(); // this often works too ... but not all the time!
|
||||
expect(comp.wasClicked).toEqual(true, 'wasClicked should be true after click');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support entering text in input box (ngModel)',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
let origName = 'John';
|
||||
let newName = 'Sally';
|
||||
|
||||
return tcb.createAsync(InputComp).then(fixture => {
|
||||
|
||||
let comp = <InputComp> fixture.componentInstance;
|
||||
expect(comp.name).toEqual(origName, `At start name should be ${origName} `);
|
||||
|
||||
let inputBox = <HTMLInputElement> fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(inputBox.value).toEqual(origName, `At start input box value should be ${origName} `);
|
||||
|
||||
inputBox.value = newName;
|
||||
expect(comp.name).toEqual(origName,
|
||||
`Name should still be ${origName} after value change, before detectChanges`);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(inputBox.value).toEqual(newName,
|
||||
`After value change and detectChanges, name should now be ${newName} `);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should override a template',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
|
@ -220,7 +240,7 @@ describe('test component builder', function() {
|
|||
});
|
||||
}));
|
||||
|
||||
it('should override component dependencies',
|
||||
it('should override component directives',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
return tcb.overrideDirective(ParentComp, ChildComp, MockChildComp)
|
||||
|
@ -233,7 +253,7 @@ describe('test component builder', function() {
|
|||
}));
|
||||
|
||||
|
||||
it('should override child component\'s dependencies',
|
||||
it('should override child component\'s directives',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
return tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
|
||||
|
@ -262,7 +282,6 @@ describe('test component builder', function() {
|
|||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should override a viewProvider',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
|
@ -288,16 +307,158 @@ describe('test component builder', function() {
|
|||
.toHaveText('from external template\n');
|
||||
});
|
||||
}), 10000); // Long timeout because this test makes an actual XHR.
|
||||
|
||||
describe('(lifecycle hooks w/ MyIfParentComp)', () => {
|
||||
let fixture: ComponentFixture;
|
||||
let parent: MyIfParentComp;
|
||||
let child: MyIfChildComp;
|
||||
|
||||
/**
|
||||
* Get the MyIfChildComp from parent; fail w/ good message if cannot.
|
||||
*/
|
||||
function getChild() {
|
||||
|
||||
let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp
|
||||
|
||||
// The Hard Way: requires detailed knowledge of the parent template
|
||||
try {
|
||||
childDe = fixture.debugElement.children[4].children[0];
|
||||
} catch (err) { /* we'll report the error */ }
|
||||
|
||||
// DebugElement.queryAll: if we wanted all of many instances:
|
||||
childDe = fixture.debugElement
|
||||
.queryAll(function (de) { return de.componentInstance instanceof MyIfChildComp; })[0];
|
||||
|
||||
// WE'LL USE THIS APPROACH !
|
||||
// DebugElement.query: find first instance (if any)
|
||||
childDe = fixture.debugElement
|
||||
.query(function (de) { return de.componentInstance instanceof MyIfChildComp; });
|
||||
|
||||
if (childDe && childDe.componentInstance) {
|
||||
child = childDe.componentInstance;
|
||||
} else {
|
||||
fail('Unable to find MyIfChildComp within MyIfParentComp');
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
// Create MyIfParentComp TCB and component instance before each test (async beforeEach)
|
||||
beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
return tcb.createAsync(MyIfParentComp)
|
||||
.then(fix => {
|
||||
fixture = fix;
|
||||
parent = fixture.debugElement.componentInstance;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should instantiate parent component', () => {
|
||||
expect(parent).not.toBeNull('parent component should exist');
|
||||
});
|
||||
|
||||
it('parent component OnInit should NOT be called before first detectChanges()', () => {
|
||||
expect(parent.ngOnInitCalled).toEqual(false);
|
||||
});
|
||||
|
||||
it('parent component OnInit should be called after first detectChanges()', () => {
|
||||
fixture.detectChanges();
|
||||
expect(parent.ngOnInitCalled).toEqual(true);
|
||||
});
|
||||
|
||||
it('child component should exist after OnInit', () => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
expect(child instanceof MyIfChildComp).toEqual(true, 'should create child');
|
||||
});
|
||||
|
||||
it('should have called child component\'s OnInit ', () => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
expect(child.ngOnInitCalled).toEqual(true);
|
||||
});
|
||||
|
||||
it('child component called OnChanges once', () => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
expect(child.ngOnChangesCounter).toEqual(1);
|
||||
});
|
||||
|
||||
it('changed parent value flows to child', () => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
|
||||
parent.parentValue = 'foo';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(child.ngOnChangesCounter).toEqual(2,
|
||||
'expected 2 changes: initial value and changed value');
|
||||
expect(child.childValue).toEqual('foo',
|
||||
'childValue should eq changed parent value');
|
||||
});
|
||||
|
||||
it('changed child value flows to parent', injectAsync([], () => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
|
||||
child.childValue = 'bar';
|
||||
|
||||
let deferred = PromiseWrapper.completer();
|
||||
let p = deferred.promise.then(() => {
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(child.ngOnChangesCounter).toEqual(2,
|
||||
'expected 2 changes: initial value and changed value');
|
||||
expect(parent.parentValue).toEqual('bar',
|
||||
'parentValue should eq changed parent value');
|
||||
});
|
||||
|
||||
// Wait one JS engine turn!
|
||||
setTimeout(() => deferred.resolve(), 0);
|
||||
|
||||
return p;
|
||||
}));
|
||||
|
||||
/* Will soon be able to write it like this:
|
||||
it('changed child value flows to parent', async(() => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
|
||||
child.childValue = 'bar';
|
||||
|
||||
// Wait one JS engine turn!
|
||||
setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
expect(child.ngOnChangesCounter).toEqual(2,
|
||||
'expected 2 changes: initial value and changed value');
|
||||
expect(parent.parentValue).toEqual('bar',
|
||||
'parentValue should eq changed parent value');
|
||||
}, 0);
|
||||
}));
|
||||
*/
|
||||
|
||||
it('clicking "Close Child" triggers child OnDestroy', () => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
|
||||
let btn = fixture.debugElement.query(By.css('button'));
|
||||
btn.triggerEventHandler('click', null);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(child.ngOnDestroyCalled).toEqual(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
describe('inject/async testing errors', () => {
|
||||
let originalJasmineIt: any;
|
||||
let originalJasmineBeforeEach: any;
|
||||
|
||||
let patchJasmineIt = () => {
|
||||
let deferred = PromiseWrapper.completer();
|
||||
originalJasmineIt = jasmine.getEnv().it;
|
||||
jasmine.getEnv().it = (description: string, fn: Function) => {
|
||||
jasmine.getEnv().it = (description: string, fn: Function): jasmine.Spec => {
|
||||
let done = () => { deferred.resolve(); };
|
||||
(<any>done).fail = (err: any) => { deferred.reject(err); };
|
||||
fn(done);
|
||||
|
@ -311,7 +472,7 @@ describe('errors', () => {
|
|||
let patchJasmineBeforeEach = () => {
|
||||
let deferred = PromiseWrapper.completer();
|
||||
originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
|
||||
jasmine.getEnv().beforeEach = (fn: any) => {
|
||||
jasmine.getEnv().beforeEach = (fn: any): void => {
|
||||
let done = () => { deferred.resolve(); };
|
||||
(<any>done).fail = (err: any) => { deferred.reject(err); };
|
||||
fn(done);
|
||||
|
@ -456,3 +617,32 @@ describe('errors', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//////// Testing Framework Bugs? /////
|
||||
import { HeroService } from './hero.service';
|
||||
import { Component } from 'angular2/core';
|
||||
|
||||
@Component({
|
||||
selector: 'another-comp',
|
||||
template: `AnotherProvidersComp()`,
|
||||
providers: [FancyService] // <======= BOOM! if we comment out
|
||||
// Failed: 'undefined' is not an object (evaluating 'dm.providers.concat')
|
||||
})
|
||||
export class AnotherProvidersComp {
|
||||
constructor(
|
||||
private _heroService: HeroService
|
||||
) { }
|
||||
}
|
||||
|
||||
describe('tcb.overrideProviders', () => {
|
||||
it('Component must have at least one provider else crash',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
return tcb.overrideProviders(
|
||||
AnotherProvidersComp,
|
||||
[provide(HeroService, {useValue: {}})]
|
||||
)
|
||||
.createAsync(AnotherProvidersComp);
|
||||
}));
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
|
||||
import { Component, Injectable } from 'angular2/core';
|
||||
import { NgIf } from 'angular2/common';
|
||||
/* tslint:disable:forin */
|
||||
import { Component, EventEmitter, Injectable, Input, Output,
|
||||
OnInit, OnChanges, OnDestroy, SimpleChange } from 'angular2/core';
|
||||
|
||||
// Let TypeScript know about the special SystemJS __moduleName variable
|
||||
declare var __moduleName: string;
|
||||
|
@ -39,6 +40,13 @@ export class ButtonComp {
|
|||
clicked() { this.wasClicked = true; }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'input-comp',
|
||||
template: `<input [(ngModel)]="name">`
|
||||
})
|
||||
export class InputComp {
|
||||
name = 'John';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-comp',
|
||||
|
@ -66,14 +74,12 @@ export class ParentComp { }
|
|||
|
||||
@Component({
|
||||
selector: 'my-if-comp',
|
||||
template: `MyIf(<span *ngIf="showMore">More</span>)`,
|
||||
directives: [NgIf]
|
||||
template: `MyIf(<span *ngIf="showMore">More</span>)`
|
||||
})
|
||||
export class MyIfComp {
|
||||
showMore: boolean = false;
|
||||
showMore = false;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'child-child-comp',
|
||||
template: '<span>ChildChild</span>'
|
||||
|
@ -121,7 +127,7 @@ export class TestViewProvidersComp {
|
|||
@Component({
|
||||
moduleId: __moduleName,
|
||||
selector: 'external-template-comp',
|
||||
templateUrl: 'public-external-template.html'
|
||||
templateUrl: 'bag-external-template.html'
|
||||
})
|
||||
export class ExternalTemplateComp { }
|
||||
|
||||
|
@ -131,3 +137,88 @@ export class ExternalTemplateComp { }
|
|||
templateUrl: 'non-existant.html'
|
||||
})
|
||||
export class BadTemplateUrl { }
|
||||
|
||||
|
||||
///////// MyIfChildComp ////////
|
||||
@Component({
|
||||
selector: 'my-if-child-comp',
|
||||
|
||||
template: `
|
||||
<h4>MyIfChildComp</h4>
|
||||
<div>
|
||||
<label>Child value: <input [(ngModel)]="childValue"> </label>
|
||||
</div>
|
||||
<p><i>Change log:</i></p>
|
||||
<div *ngFor="#log of changeLog; #i=index">{{i + 1}} - {{log}}</div>`
|
||||
})
|
||||
export class MyIfChildComp implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() value = '';
|
||||
@Output() valueChange = new EventEmitter<string>();
|
||||
|
||||
get childValue() { return this.value; }
|
||||
set childValue(v: string) {
|
||||
if (this.value === v) { return; }
|
||||
this.value = v;
|
||||
this.valueChange.emit(v);
|
||||
}
|
||||
|
||||
changeLog: string[] = [];
|
||||
|
||||
ngOnInitCalled = false;
|
||||
ngOnChangesCounter = 0;
|
||||
ngOnDestroyCalled = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.ngOnInitCalled = true;
|
||||
this.changeLog.push('ngOnInit called');
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.ngOnDestroyCalled = true;
|
||||
this.changeLog.push('ngOnDestroy called');
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
|
||||
for (let propName in changes) {
|
||||
this.ngOnChangesCounter += 1;
|
||||
let prop = changes[propName];
|
||||
let cur = JSON.stringify(prop.currentValue);
|
||||
let prev = JSON.stringify(prop.previousValue);
|
||||
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////// MyIfParentComp ////////
|
||||
|
||||
@Component({
|
||||
selector: 'my-if-parent-comp',
|
||||
template: `
|
||||
<h3>MyIfParentComp</h3>
|
||||
<label>Parent value:
|
||||
<input [(ngModel)]="parentValue">
|
||||
</label>
|
||||
<button (click)='clicked()'>{{toggleLabel}} Child</button><br>
|
||||
<div *ngIf="showChild"
|
||||
style="margin: 4px; padding: 4px; background-color: aliceblue;">
|
||||
<my-if-child-comp [(value)]="parentValue"></my-if-child-comp>
|
||||
</div>
|
||||
`,
|
||||
directives: [MyIfChildComp]
|
||||
})
|
||||
export class MyIfParentComp implements OnInit {
|
||||
ngOnInitCalled = false;
|
||||
parentValue = 'Hello, World';
|
||||
showChild = false;
|
||||
toggleLabel = 'Unknown';
|
||||
|
||||
ngOnInit() {
|
||||
this.ngOnInitCalled = true;
|
||||
this.clicked();
|
||||
}
|
||||
|
||||
clicked() {
|
||||
this.showChild = !this.showChild;
|
||||
this.toggleLabel = this.showChild ? 'Close' : 'Show';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
|
||||
import { By } from 'angular2/platform/browser';
|
||||
import { provide } from 'angular2/core';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
inject, injectAsync,
|
||||
TestComponentBuilder
|
||||
} from 'angular2/testing';
|
||||
|
||||
import { Hero, HeroService, MockHeroService } from './mock-hero.service';
|
||||
import { Router, MockRouter } from './mock-router';
|
||||
|
||||
interface Done {
|
||||
(): void;
|
||||
fail: (err: any) => void;
|
||||
}
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
|
||||
//////// WITHOUT ANGULAR INVOLVED ///////
|
||||
describe('w/o Angular', () => {
|
||||
let comp: DashboardComponent;
|
||||
let mockHeroService: MockHeroService;
|
||||
let router: MockRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
router = new MockRouter();
|
||||
mockHeroService = new MockHeroService();
|
||||
comp = new DashboardComponent(router, mockHeroService);
|
||||
});
|
||||
|
||||
it('should NOT have heroes before calling OnInit', () => {
|
||||
expect(comp.heroes.length).toEqual(0,
|
||||
'should not have heroes before OnInit');
|
||||
});
|
||||
|
||||
it('should NOT have heroes immediately after OnInit', () => {
|
||||
comp.ngOnInit(); // ngOnInit -> getHeroes
|
||||
expect(comp.heroes.length).toEqual(0,
|
||||
'should not have heroes until service promise resolves');
|
||||
});
|
||||
|
||||
it('should HAVE heroes after HeroService gets them', (done: Done) => {
|
||||
comp.ngOnInit(); // ngOnInit -> getHeroes
|
||||
mockHeroService.lastPromise // the one from getHeroes
|
||||
.then(() => {
|
||||
// throw new Error('deliberate error'); // see it fail gracefully
|
||||
expect(comp.heroes.length).toBeGreaterThan(0,
|
||||
'should have heroes after service promise resolves');
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should tell ROUTER to navigate by hero id', () => {
|
||||
let hero: Hero = {id: 42, name: 'Abbracadabra' };
|
||||
let spy = spyOn(router, 'navigate').and.callThrough();
|
||||
|
||||
comp.gotoDetail(hero);
|
||||
|
||||
let linkParams = spy.calls.mostRecent().args[0];
|
||||
expect(linkParams[0]).toEqual('HeroDetail', 'should nav to "HeroDetail"');
|
||||
expect(linkParams[1].id).toEqual(hero.id, 'should nav to fake hero\'s id');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
////// WITH ANGULAR TEST INFRASTRUCTURE ///////
|
||||
describe('using TCB', () => {
|
||||
let comp: DashboardComponent;
|
||||
let mockHeroService: MockHeroService;
|
||||
|
||||
beforeEachProviders(() => {
|
||||
mockHeroService = new MockHeroService();
|
||||
return [
|
||||
provide(Router, {useClass: MockRouter}),
|
||||
provide(MockRouter, {useExisting: Router}),
|
||||
provide(HeroService, {useValue: mockHeroService})
|
||||
];
|
||||
});
|
||||
|
||||
it('can instantiate it',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
return tcb.createAsync(DashboardComponent);
|
||||
}));
|
||||
|
||||
it('should NOT have heroes before OnInit',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
return tcb.createAsync(DashboardComponent).then(fixture => {
|
||||
comp = fixture.debugElement.componentInstance;
|
||||
|
||||
expect(comp.heroes.length).toEqual(0,
|
||||
'should not have heroes before OnInit');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should NOT have heroes immediately after OnInit',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
return tcb.createAsync(DashboardComponent).then(fixture => {
|
||||
comp = fixture.debugElement.componentInstance;
|
||||
fixture.detectChanges(); // runs initial lifecycle hooks
|
||||
|
||||
expect(comp.heroes.length).toEqual(0,
|
||||
'should not have heroes until service promise resolves');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should HAVE heroes after HeroService gets them',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
return tcb.createAsync(DashboardComponent).then(fixture => {
|
||||
comp = fixture.debugElement.componentInstance;
|
||||
fixture.detectChanges(); // runs ngOnInit -> getHeroes
|
||||
|
||||
return mockHeroService.lastPromise // the one from getHeroes
|
||||
.then(() => {
|
||||
expect(comp.heroes.length).toBeGreaterThan(0,
|
||||
'should have heroes after service promise resolves');
|
||||
});
|
||||
|
||||
});
|
||||
}));
|
||||
|
||||
it('should DISPLAY heroes after HeroService gets them',
|
||||
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
return tcb.createAsync(DashboardComponent).then(fixture => {
|
||||
comp = fixture.debugElement.componentInstance;
|
||||
fixture.detectChanges(); // runs ngOnInit -> getHeroes
|
||||
|
||||
return mockHeroService.lastPromise // the one from getHeroes
|
||||
.then(() => {
|
||||
|
||||
// Find and examine the displayed heroes
|
||||
fixture.detectChanges(); // update bindings
|
||||
let heroNames = fixture.debugElement.queryAll(By.css('h4'));
|
||||
|
||||
expect(heroNames.length).toEqual(4, 'should display 4 heroes');
|
||||
|
||||
// the 4th displayed hero should be the 5th mock hero
|
||||
expect(heroNames[3].nativeElement)
|
||||
.toHaveText(mockHeroService.mockHeroes[4].name);
|
||||
});
|
||||
|
||||
});
|
||||
}));
|
||||
|
||||
it('should tell ROUTER to navigate by hero id',
|
||||
injectAsync([TestComponentBuilder, Router],
|
||||
(tcb: TestComponentBuilder, router: MockRouter) => {
|
||||
|
||||
let spy = spyOn(router, 'navigate').and.callThrough();
|
||||
|
||||
return tcb.createAsync(DashboardComponent).then(fixture => {
|
||||
let hero: Hero = {id: 42, name: 'Abbracadabra' };
|
||||
comp = fixture.debugElement.componentInstance;
|
||||
comp.gotoDetail(hero);
|
||||
|
||||
let linkParams = spy.calls.mostRecent().args[0];
|
||||
expect(linkParams[0]).toEqual('HeroDetail', 'should nav to "HeroDetail"');
|
||||
expect(linkParams[1].id).toEqual(hero.id, 'should nav to fake hero\'s id');
|
||||
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -31,7 +31,7 @@ export class DashboardComponent implements OnInit {
|
|||
|
||||
ngOnInit() {
|
||||
this._heroService.getHeroes()
|
||||
.then(heroes => this.heroes = heroes.slice(1,5));
|
||||
.then(heroes => this.heroes = heroes.slice(1, 5));
|
||||
}
|
||||
|
||||
// #docregion goto-detail
|
||||
|
@ -41,4 +41,4 @@ export class DashboardComponent implements OnInit {
|
|||
}
|
||||
// #enddocregion goto-detail
|
||||
}
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import {
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
describe,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
expect,
|
||||
fakeAsync,
|
||||
tick,
|
||||
beforeEach,
|
||||
inject,
|
||||
injectAsync,
|
||||
withProviders,
|
||||
beforeEachProviders
|
||||
beforeEach, beforeEachProviders, withProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
inject, injectAsync, fakeAsync, TestComponentBuilder, tick
|
||||
} from 'angular2/testing';
|
||||
|
||||
import { provide } from 'angular2/core';
|
||||
|
@ -23,17 +13,12 @@ import {
|
|||
MockConnection } from 'angular2/src/http/backends/mock_backend';
|
||||
|
||||
import {
|
||||
BaseRequestOptions,
|
||||
ConnectionBackend,
|
||||
Request,
|
||||
RequestMethod,
|
||||
RequestOptions,
|
||||
Response,
|
||||
ResponseOptions,
|
||||
URLSearchParams,
|
||||
HTTP_PROVIDERS,
|
||||
XHRBackend,
|
||||
Http} from 'angular2/http';
|
||||
Http, HTTP_PROVIDERS,
|
||||
ConnectionBackend, XHRBackend,
|
||||
Request, RequestMethod, BaseRequestOptions, RequestOptions,
|
||||
Response, ResponseOptions,
|
||||
URLSearchParams
|
||||
} from 'angular2/http';
|
||||
|
||||
// Add all operators to Observable
|
||||
import 'rxjs/Rx';
|
||||
|
@ -45,10 +30,10 @@ import { HeroService } from './http-hero.service';
|
|||
type HeroData = {id: string, name: string}
|
||||
|
||||
const makeHeroData = () => [
|
||||
{ "id": "1", "name": "Windstorm" },
|
||||
{ "id": "2", "name": "Bombasto" },
|
||||
{ "id": "3", "name": "Magneta" },
|
||||
{ "id": "4", "name": "Tornado" }
|
||||
{ id: '1', name: 'Windstorm' },
|
||||
{ id: '2', name: 'Bombasto' },
|
||||
{ id: '3', name: 'Magneta' },
|
||||
{ id: '4', name: 'Tornado' }
|
||||
];
|
||||
|
||||
// HeroService expects response data like {data: {the-data}}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { bootstrap } from 'angular2/platform/browser';
|
||||
import { AppComponent } from './app.component';
|
||||
import { bootstrap } from 'angular2/platform/browser';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MyIfParentComp } from './bag';
|
||||
|
||||
bootstrap(AppComponent);
|
||||
bootstrap(AppComponent);
|
||||
bootstrap(MyIfParentComp);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
export { Hero } from './hero';
|
||||
export { HeroService } from './hero.service';
|
||||
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { Hero } from './hero';
|
||||
import { HeroService } from './hero.service';
|
||||
|
||||
import { PromiseWrapper } from 'angular2/src/facade/promise';
|
||||
|
||||
export class MockHeroService implements HeroService {
|
||||
|
||||
mockHeroes = HEROES.slice();
|
||||
lastPromise: Promise<any>; // so we can spy on promise calls
|
||||
|
||||
getHero(id: number) {
|
||||
return this.lastPromise = PromiseWrapper.resolve(this.mockHeroes[0]);
|
||||
}
|
||||
|
||||
getHeroes() {
|
||||
return this.lastPromise = PromiseWrapper.resolve<Hero[]>(this.mockHeroes);
|
||||
}
|
||||
|
||||
getHeroesSlowly() { return this.getHeroes(); }
|
||||
}
|
|
@ -2,15 +2,15 @@
|
|||
import { Hero } from './hero';
|
||||
|
||||
export var HEROES: Hero[] = [
|
||||
{"id": 11, "name": "Mr. Nice"},
|
||||
{"id": 12, "name": "Narco"},
|
||||
{"id": 13, "name": "Bombasto"},
|
||||
{"id": 14, "name": "Celeritas"},
|
||||
{"id": 15, "name": "Magneta"},
|
||||
{"id": 16, "name": "RubberMan"},
|
||||
{"id": 17, "name": "Dynama"},
|
||||
{"id": 18, "name": "Dr IQ"},
|
||||
{"id": 19, "name": "Magma"},
|
||||
{"id": 20, "name": "Tornado"}
|
||||
{id: 11, name: 'Mr. Nice'},
|
||||
{id: 12, name: 'Narco'},
|
||||
{id: 13, name: 'Bombasto'},
|
||||
{id: 14, name: 'Celeritas'},
|
||||
{id: 15, name: 'Magneta'},
|
||||
{id: 16, name: 'RubberMan'},
|
||||
{id: 17, name: 'Dynama'},
|
||||
{id: 18, name: 'Dr IQ'},
|
||||
{id: 19, name: 'Magma'},
|
||||
{id: 20, name: 'Tornado'}
|
||||
];
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
export * from 'angular2/router';
|
||||
|
||||
import { Directive, DynamicComponentLoader, ElementRef,
|
||||
Injectable, Optional, Input } from 'angular2/core';
|
||||
|
||||
import { PromiseWrapper } from 'angular2/src/facade/promise';
|
||||
import { isString} from 'angular2/src/facade/lang';
|
||||
|
||||
import { ComponentInstruction, Instruction,
|
||||
Router, RouterOutlet} from 'angular2/router';
|
||||
|
||||
let _resolveToTrue = PromiseWrapper.resolve(true);
|
||||
|
||||
const NOT_IMPLEMENTED = (what: string) => {
|
||||
throw new Error (`"${what}" is not implemented`);
|
||||
};
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[routerLink]',
|
||||
host: {
|
||||
'(click)': 'onClick()',
|
||||
'[attr.href]': 'visibleHref',
|
||||
'[class.router-link-active]': 'isRouteActive'
|
||||
}
|
||||
})
|
||||
export class MockRouterLink {
|
||||
|
||||
isRouteActive = false;
|
||||
visibleHref: string; // the url displayed on the anchor element.
|
||||
|
||||
@Input('routerLink') routeParams: any[];
|
||||
@Input() target: string;
|
||||
navigatedTo: any[] = null;
|
||||
|
||||
constructor(public router: Router) { }
|
||||
|
||||
onClick() {
|
||||
this.navigatedTo = null;
|
||||
|
||||
// If no target, or if target is _self, prevent default browser behavior
|
||||
if (!isString(this.target) || this.target === '_self') {
|
||||
this.navigatedTo = this.routeParams;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'router-outlet'})
|
||||
export class MockRouterOutlet extends RouterOutlet {
|
||||
name: string = null;
|
||||
|
||||
constructor(
|
||||
_elementRef: ElementRef,
|
||||
@Optional() _loader: DynamicComponentLoader,
|
||||
_parentRouter: Router,
|
||||
nameAttr: string) {
|
||||
super(_elementRef, _loader, _parentRouter, nameAttr);
|
||||
if (nameAttr) {
|
||||
this.name = nameAttr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the Router to instantiate a new component during the commit phase of a navigation.
|
||||
* This method in turn is responsible for calling the `routerOnActivate` hook of its child.
|
||||
*/
|
||||
activate(nextInstruction: ComponentInstruction): Promise<any> { NOT_IMPLEMENTED('activate'); return _resolveToTrue; }
|
||||
|
||||
/**
|
||||
* Called by the {@link Router} during the commit phase of a navigation when an outlet
|
||||
* reuses a component between different routes.
|
||||
* This method in turn is responsible for calling the `routerOnReuse` hook of its child.
|
||||
*/
|
||||
reuse(nextInstruction: ComponentInstruction): Promise<any> { NOT_IMPLEMENTED('reuse'); return _resolveToTrue; }
|
||||
|
||||
/**
|
||||
* Called by the {@link Router} when an outlet disposes of a component's contents.
|
||||
* This method in turn is responsible for calling the `routerOnDeactivate` hook of its child.
|
||||
*/
|
||||
deactivate(nextInstruction: ComponentInstruction): Promise<any> { NOT_IMPLEMENTED('deactivate'); return _resolveToTrue; }
|
||||
|
||||
/**
|
||||
* Called by the {@link Router} during recognition phase of a navigation.
|
||||
*
|
||||
* If this resolves to `false`, the given navigation is cancelled.
|
||||
*
|
||||
* This method delegates to the child component's `routerCanDeactivate` hook if it exists,
|
||||
* and otherwise resolves to true.
|
||||
*/
|
||||
routerCanDeactivate(nextInstruction: ComponentInstruction): Promise<any> {
|
||||
NOT_IMPLEMENTED('routerCanDeactivate'); return _resolveToTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link Router} during recognition phase of a navigation.
|
||||
*
|
||||
* If the new child component has a different Type than the existing child component,
|
||||
* this will resolve to `false`. You can't reuse an old component when the new component
|
||||
* is of a different Type.
|
||||
*
|
||||
* Otherwise, this method delegates to the child component's `routerCanReuse` hook if it exists,
|
||||
* or resolves to true if the hook is not present.
|
||||
*/
|
||||
routerCanReuse(nextInstruction: ComponentInstruction): Promise<any> { NOT_IMPLEMENTED('routerCanReuse'); return _resolveToTrue; }
|
||||
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MockRouter extends Router {
|
||||
|
||||
mockIsRouteActive = false;
|
||||
mockRecognizedInstruction: Instruction;
|
||||
outlet: RouterOutlet = null;
|
||||
|
||||
constructor() {
|
||||
super(null, null, null, null);
|
||||
}
|
||||
|
||||
auxRouter(hostComponent: any): Router { return new MockChildRouter(this, hostComponent); }
|
||||
childRouter(hostComponent: any): Router { return new MockChildRouter(this, hostComponent); }
|
||||
|
||||
commit(instruction: Instruction, _skipLocationChange = false): Promise<any> {
|
||||
NOT_IMPLEMENTED('commit'); return _resolveToTrue;
|
||||
}
|
||||
|
||||
deactivate(instruction: Instruction, _skipLocationChange = false): Promise<any> {
|
||||
NOT_IMPLEMENTED('deactivate'); return _resolveToTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an `Instruction` based on the provided Route Link DSL.
|
||||
*/
|
||||
generate(linkParams: any[]): Instruction {
|
||||
NOT_IMPLEMENTED('generate'); return null;
|
||||
}
|
||||
|
||||
isRouteActive(instruction: Instruction): boolean { return this.mockIsRouteActive; }
|
||||
|
||||
/**
|
||||
* Navigate based on the provided Route Link DSL. It's preferred to navigate with this method
|
||||
* over `navigateByUrl`.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* This method takes an array representing the Route Link DSL:
|
||||
* ```
|
||||
* ['./MyCmp', {param: 3}]
|
||||
* ```
|
||||
* See the {@link RouterLink} directive for more.
|
||||
*/
|
||||
navigate(linkParams: any[]): Promise<any> {
|
||||
return PromiseWrapper.resolve(linkParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a URL. Returns a promise that resolves when navigation is complete.
|
||||
* It's preferred to navigate with `navigate` instead of this method, since URLs are more brittle.
|
||||
*
|
||||
* If the given URL begins with a `/`, router will navigate absolutely.
|
||||
* If the given URL does not begin with `/`, the router will navigate relative to this component.
|
||||
*/
|
||||
navigateByUrl(url: string, _skipLocationChange = false): Promise<any> {
|
||||
return PromiseWrapper.resolve(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Navigate via the provided instruction. Returns a promise that resolves when navigation is
|
||||
* complete.
|
||||
*/
|
||||
navigateByInstruction(instruction: Instruction, _skipLocationChange = false): Promise<any> {
|
||||
return PromiseWrapper.resolve(instruction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to URL updates from the router
|
||||
*/
|
||||
subscribe(onNext: (v: any) => void, onError?: (v: any) => void) {
|
||||
return {onNext, onError};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL, returns an instruction representing the component graph
|
||||
*/
|
||||
recognize(url: string): Promise<Instruction> {
|
||||
return PromiseWrapper.resolve(this.mockRecognizedInstruction);
|
||||
}
|
||||
|
||||
registerPrimaryOutlet(outlet: RouterOutlet): Promise<any> {
|
||||
this.outlet = outlet;
|
||||
return super.registerPrimaryOutlet(outlet);
|
||||
}
|
||||
|
||||
unregisterPrimaryOutlet(outlet: RouterOutlet) {
|
||||
super.unregisterPrimaryOutlet(outlet);
|
||||
this.outlet = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockChildRouter extends MockRouter {
|
||||
constructor(parent: MockRouter, hostComponent: any) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
||||
navigateByUrl(url: string, _skipLocationChange = false): Promise<any> {
|
||||
// Delegate navigation to the root router
|
||||
return this.parent.navigateByUrl(url, _skipLocationChange);
|
||||
}
|
||||
|
||||
navigateByInstruction(instruction: Instruction, _skipLocationChange = false):
|
||||
Promise<any> {
|
||||
// Delegate navigation to the root router
|
||||
return this.parent.navigateByInstruction(instruction, _skipLocationChange);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
import { MyUppercasePipe } from './my-uppercase.pipe';
|
||||
|
||||
describe('MyUppercasePipe', () => {
|
||||
let pipe : MyUppercasePipe;
|
||||
let pipe: MyUppercasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
pipe = new MyUppercasePipe();
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
<!-- #docregion head -->
|
||||
<!-- IE required polyfills, in this exact order -->
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
|
||||
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
|
@ -35,5 +34,7 @@
|
|||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
<hr>
|
||||
<my-if-parent-comp>Loading MyIfParentComp ...</my-if-parent-comp>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<title>Angular Public Unit Tests</title>
|
||||
<title>Bag of Unit Tests</title>
|
||||
<link rel="stylesheet" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||
|
||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<script src="node_modules/angular2/bundles/testing.dev.js"></script>
|
||||
|
||||
<script>
|
||||
var __spec_files__ = ['app/public.spec'];
|
||||
var __spec_files__ = ['app/bag.spec'];
|
||||
</script>
|
||||
<script src="test-shim.js"></script>
|
||||
</body>
|
|
@ -0,0 +1,83 @@
|
|||
// Configuration for the Wallaby Visual Studio Code testing extension
|
||||
// https://marketplace.visualstudio.com/items?itemName=WallabyJs.wallaby-vscode
|
||||
// Note: Wallaby is not open source and costs money
|
||||
module.exports = function () {
|
||||
|
||||
return {
|
||||
files: [
|
||||
{pattern: 'node_modules/es6-shim/es6-shim.js', instrument: false},
|
||||
{pattern: 'node_modules/systemjs/dist/system-polyfills.js', instrument: false},
|
||||
{pattern: 'node_modules/systemjs/dist/system.js', instrument: false},
|
||||
{pattern: 'node_modules/reflect-metadata/Reflect.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/zone.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/long-stack-trace-zone.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/jasmine-patch.js', instrument: false},
|
||||
|
||||
{pattern: 'app/**/*+(ts|html|css)', load: false},
|
||||
{pattern: 'app/**/*.spec.ts', ignore: true}
|
||||
],
|
||||
|
||||
tests: [
|
||||
{pattern: 'app/**/*.spec.ts', load: false}
|
||||
],
|
||||
|
||||
middleware: function (app, express) {
|
||||
app.use('/node_modules', express.static(require('path').join(__dirname, 'node_modules')));
|
||||
},
|
||||
|
||||
testFramework: 'jasmine',
|
||||
|
||||
bootstrap: function (wallaby) {
|
||||
wallaby.delayStart();
|
||||
|
||||
System.config({
|
||||
defaultJSExtensions: true,
|
||||
packages: {
|
||||
app: {
|
||||
meta: {
|
||||
'*': {
|
||||
scriptLoad: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
paths: {
|
||||
'npm:*': 'node_modules/*'
|
||||
},
|
||||
map: {
|
||||
'angular2': 'npm:angular2',
|
||||
'rxjs': 'npm:rxjs'
|
||||
}
|
||||
});
|
||||
|
||||
// Configure Angular for the browser and
|
||||
// with test versions of the platform providers
|
||||
Promise.all([
|
||||
System.import('angular2/testing'),
|
||||
System.import('angular2/platform/testing/browser')
|
||||
])
|
||||
.then(function (results) {
|
||||
var testing = results[0];
|
||||
var browser = results[1];
|
||||
testing.setBaseTestProviders(
|
||||
browser.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
browser.TEST_BROWSER_APPLICATION_PROVIDERS);
|
||||
|
||||
// Load all spec files
|
||||
return Promise.all(wallaby.tests.map(function (specFile) {
|
||||
return System.import(specFile);
|
||||
}));
|
||||
})
|
||||
.then(function () {
|
||||
wallaby.start();
|
||||
})
|
||||
.catch(function (e) {
|
||||
setTimeout(function () {
|
||||
throw e;
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
debug: true
|
||||
};
|
||||
};
|
|
@ -38,6 +38,18 @@
|
|||
- var guide = sections('guide', function(item) { return !item.basics; });
|
||||
- var qs = sections('', function(item) { return item.slug === 'quickstart'; })[0] || {};
|
||||
- var reference = sections('', function(item) { return item.reference; });
|
||||
- var anyItemSelected = function(items) {
|
||||
- var selectedCount = items.filter(function(item) { return !!item.class; }).length;
|
||||
- return selectedCount > 0 ? 'is-nav-title-selected' : '';
|
||||
- }
|
||||
- var isQuickstartSelected = function(_cur) {
|
||||
- var splitted = cur.split('/');
|
||||
- var bit = splitted[splitted.length - 1].replace('.html', '');
|
||||
- return bit === 'quickstart' ? 'is-nav-title-selected' : '';
|
||||
-}
|
||||
- var isCollapsed = function(titleStyle) {
|
||||
- return titleStyle === 'is-nav-title-selected' ? '' : 'is-hidden';
|
||||
- }
|
||||
|
||||
nav.side-nav.l-pinned-left.l-layer-4.l-offset-nav
|
||||
// SEARCH BAR
|
||||
|
@ -49,42 +61,51 @@ nav.side-nav.l-pinned-left.l-layer-4.l-offset-nav
|
|||
|
||||
div(class="side-nav-secondary" ng-class="appCtrl.showDocsNav ? 'is-visible' : ''")
|
||||
.nav-blocks
|
||||
.nav-title 指南
|
||||
.nav-primary-link(class="#{qs.class}")
|
||||
a(href="#{qs.href}" title="#{qs.tooltip}") #{qs.navTitle}
|
||||
.nav-sub-title 案例: 英雄指南
|
||||
.nav-ordered-lists
|
||||
ol
|
||||
each item in tutorial
|
||||
li(class="#{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
a(class="nav-title #{isQuickstartSelected(cur)}" href="#{qs.href}" title="#{qs.tooltip}") Quickstart
|
||||
|
||||
.nav-blocks
|
||||
.nav-title 基础知识
|
||||
.nav-ordered-lists
|
||||
ol
|
||||
each item in basics
|
||||
li(class="#{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
a(class="nav-title #{anyItemSelected(tutorial)}" href="#{tutorial[0].href}" title="#{tutorial[0].tooltip}") 教程
|
||||
img(class="inline-arrow-down-svg" src="/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg")
|
||||
.nav-ordered-lists(class="#{isCollapsed(anyItemSelected(tutorial))}")
|
||||
ul
|
||||
each item, index in tutorial
|
||||
li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{index + 1}. #{item.navTitle}
|
||||
|
||||
.nav-blocks
|
||||
.nav-title 开发人员指南
|
||||
.nav-unordered-lists
|
||||
a(class="nav-title #{anyItemSelected(basics)}" href="#{basics[0].href}" title="#{basics[0].tooltip}") 基础知识
|
||||
img(class="inline-arrow-down-svg" src="/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg")
|
||||
|
||||
.nav-ordered-lists(class="#{isCollapsed(anyItemSelected(basics))}")
|
||||
ul
|
||||
each item, index in basics
|
||||
li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{index + 1}. #{item.navTitle}
|
||||
|
||||
.nav-blocks
|
||||
a(class="nav-title #{anyItemSelected(guide)}" href="#{guide[0].href}" title="#{guide[0].tooltip}") 开发人员指南
|
||||
img(class="inline-arrow-down-svg" src="/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg")
|
||||
|
||||
.nav-unordered-lists(class="#{isCollapsed(anyItemSelected(guide))}")
|
||||
ul
|
||||
each item in guide
|
||||
li(class="#{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
|
||||
.nav-blocks
|
||||
.nav-title Cookbook
|
||||
.nav-unordered-lists
|
||||
a(class="nav-title #{anyItemSelected(cookbook)}" href="#{cookbook[0].href}" title="#{cookbook[0].tooltip}") Cookbook
|
||||
img(class="inline-arrow-down-svg" src="/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg")
|
||||
|
||||
.nav-unordered-lists(class="#{isCollapsed(anyItemSelected(cookbook))}")
|
||||
ul
|
||||
each item in cookbook
|
||||
li(class="#{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
|
||||
.nav-blocks
|
||||
.nav-title 参考
|
||||
.nav-unordered-lists
|
||||
a(class="nav-title #{anyItemSelected(reference)}" href="#{reference[0].href}" title="#{reference[0].tooltip}") 参考
|
||||
img(class="inline-arrow-down-svg" src="/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg")
|
||||
|
||||
.nav-unordered-lists(class="#{isCollapsed(anyItemSelected(reference))}")
|
||||
ul
|
||||
each item in reference
|
||||
li(class="#{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle}
|
||||
|
||||
script.
|
||||
// Could put in appCtrl but only needed here and clear here
|
||||
|
|
|
@ -1,12 +1,178 @@
|
|||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
We're working on the Dart version of this chapter.
|
||||
In the meantime, please see these resources:
|
||||
We learned the basics of Angular Dependency injection in the
|
||||
[Dependency Injection](./dependency-injection.html) chapter.
|
||||
|
||||
* [Hierarchical Injectors](/docs/ts/latest/guide/hierarchical-dependency-injection.html):
|
||||
The TypeScript version of this chapter
|
||||
Angular has a Hierarchical Dependency Injection system.
|
||||
There is actually a tree of injectors
|
||||
that parallel an application's component tree.
|
||||
We can re-configure the injectors at any level of that component tree with
|
||||
interesting and useful results.
|
||||
|
||||
* [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/hierarchical-dependency-injection/dart):
|
||||
A preliminary version of the example code that will appear in this chapter
|
||||
In this chapter we explore these points and write some code.
|
||||
|
||||
The source code for the example app in this chapter is
|
||||
[in GitHub](https://github.com/angular/angular.io/tree/master/public/docs/_examples/hierarchical-dependency-injection/dart).
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Injector Tree
|
||||
|
||||
In the [Dependency Injection](./dependency-injection.html) chapter
|
||||
we learned how to configure a dependency injector and how to retrieve dependencies where we need them.
|
||||
|
||||
We oversimplified. In fact, there is no such thing as ***the*** injector!
|
||||
An application may have multiple injectors!
|
||||
|
||||
An Angular application is a tree of components. Each component instance has its own injector!
|
||||
The tree of components parallels the tree of injectors.
|
||||
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Angular doesn't *literally* create a separate injector for each component.
|
||||
Every component doesn't need its own injector and it would be horribly inefficient to create
|
||||
masses of injectors for no good purpose.
|
||||
|
||||
But it is true that every component ***has an injector*** (even if it shares that injector with another component)
|
||||
and there may be many different injector instances operating at different levels of the component tree.
|
||||
|
||||
It is useful to pretend that every component has its own injector.
|
||||
:marked
|
||||
Consider a simple variation on the Tour of Heroes application consisting of three different components:
|
||||
`HeroesApp`, `HeroesListComponent` and `HeroesCardComponent`.
|
||||
The `HeroesApp` holds a single instance of `HeroesListComponent`.
|
||||
The new twist is that the `HeroesListComponent` may hold and manage multiple instances of the `HeroesCardComponent`.
|
||||
|
||||
The following diagram represents the state of the component tree when there are three instances of `HeroesCardComponent`
|
||||
open simultaneously.
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="500")
|
||||
|
||||
:marked
|
||||
Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.
|
||||
|
||||
When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
||||
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
|
||||
If that injector can't satisfy the request, it passes it along to *its* parent component's injector.
|
||||
The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors.
|
||||
If we run out of ancestors, Angular throws an error.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
There's a third possibililty. An intermediate component can declare that it is the "host" component.
|
||||
The hunt for providers will climb no higher than the injector for this host component.
|
||||
We'll reserve discussion of this option for another day.
|
||||
:marked
|
||||
Such a proliferation of injectors makes little sense until we consider the possiblity that injectors at different levels can be
|
||||
configured with different providers. We don't *have* to re-configure providers at every level. But we *can*.
|
||||
|
||||
If we don't re-configure, the tree of injectors appears to be flat. All requests bubble up to the root injector that we
|
||||
configured with the `bootstrap` method.
|
||||
|
||||
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
||||
|
||||
Let’s return to our Car example.
|
||||
Suppose we configured the root injector (marked as A) with providers for `Car`, `Engine` and `Tires`.
|
||||
We create a child component (B) that defines its own providers for `Car` and `Engine`
|
||||
This child is the parent of another component (C) that defines its own provider for `Car`.
|
||||
|
||||
Behind the scenes each component sets up its own injector with one or more providers defined for that component itself.
|
||||
|
||||
When we resolve an instance of `Car` at the deepest component (C),
|
||||
it's injector produces an instance of `Car` resolved by injector (C) with an `Engine` resolved by injector (B) and
|
||||
`Tires` resolved by the root injector (A).
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="injector tree" width="600")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Component Injectors
|
||||
|
||||
In the previous section, we talked about injectors and how they are organized like a tree. Lookups follow the injector tree upwards until they find the requested thing to inject. But when do we actually want to provide providers on the root injector and when do we want to provide them on a child injector?
|
||||
|
||||
Consider you are building a component to show a list of super heroes that displays each super hero in a card with it’s name and superpower. There should also be an edit button that opens up an editor to change the name and superpower of our hero.
|
||||
|
||||
One important aspect of the editing functionality is that we want to allow multiple heroes to be in edit mode at the same time and that one can always either commit or cancel the proposed changes.
|
||||
|
||||
Let’s take a look at the `HeroesListComponent` which is the root component for this example.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/heroes_list_component.dart', null, 'lib/heroes_list_component.dart')
|
||||
|
||||
:marked
|
||||
Notice that it imports the `HeroService` that we’ve used before so we can skip its declaration. The only difference is that we’ve used a more formal approach for our `Hero`model and defined it upfront as such.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/hero.dart', null, 'lib/hero.dart')(format=".")
|
||||
|
||||
:marked
|
||||
Our `HeroesListComponent` defines a template that creates a list of `HeroCardComponent`s and `HeroEditorComponent`s, each bound to an instance of hero that is returned from the `HeroService`. Ok, that’s not entirely true. It actually binds to an `EditItem<Hero>` which is a simple generic datatype that can wrap any type and indicate if the item being wrapped is currently being edited or not.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/edit_item.dart', null, 'lib/edit_item.dart')(format=".")
|
||||
|
||||
:marked
|
||||
But how is `HeroCardComponent` implemented? Let’s take a look.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/hero_card_component.dart', null, 'lib/hero_card_component.dart')
|
||||
|
||||
:marked
|
||||
The `HeroCardComponent` is basically a component that defines a template to render a hero. Nothing more.
|
||||
|
||||
Let’s get to the interesting part and take a look at the `HeroEditorComponent`
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/hero_editor_component.dart', null, 'lib/hero_editor_component.dart')
|
||||
|
||||
:marked
|
||||
Now here it’s getting interesting. The `HeroEditorComponent`defines a template with an input to change the name of the hero and a `cancel` and a `save` button. Remember that we said we want to have the flexibility to cancel our editing and restore the old value? This means we need to maintain two copies of our `Hero` that we want to edit. Thinking ahead this is a perfect use case to abstract it into it’s own generic service since we have probably more cases like this in our app.
|
||||
|
||||
And this is where the `RestoreService` enters the stage.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/restore_service.dart', null, 'lib/restore_service.dart')
|
||||
|
||||
:marked
|
||||
All this tiny service does is define an API to set a value of any type which can be altered, retrieved or set back to it’s initial value. That’s exactly what we need to implement the desired functionality.
|
||||
|
||||
Our `HeroEditComponent` uses this services under the hood for it’s `hero` property. It intercepts the `get` and `set` method to delegate the actual work to our `RestoreService` which in turn makes sure that we won’t work on the original item but on a copy instead.
|
||||
|
||||
At this point we may be scratching our heads asking what this has to do with component injectors?
|
||||
Look closely at the metadata for our `HeroEditComponent`. Notice the `providers` property.
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/lib/hero_editor_component.dart', 'providers')
|
||||
:marked
|
||||
This adds a `RestoreService` provider to the injector of the `HeroEditComponent`.
|
||||
Couldn’t we simply alter our bootstrap call to this?
|
||||
|
||||
+makeExample('hierarchical-dependency-injection/dart/web/main.dart', 'bad-alternative')
|
||||
:marked
|
||||
Technically we could, but our component wouldn’t quite behave the way it is supposed to. Remember that each injector treats the services that it provides as singletons. However, in order to be able to have multiple instances of `HeroEditComponent` edit multiple heroes at the same time we need to have multiple instances of the `RestoreService`. More specifically each instance of `HeroEditComponent` needs to be bound to it’s own instance of the `RestoreService`.
|
||||
|
||||
By configuring a provider for the `RestoreService` on the `HeroEditComponent`, we get exactly one new instance of the `RestoreService`per `HeroEditComponent`.
|
||||
|
||||
Does that mean that services aren’t singletons anymore in Angular 2? Yes and no.
|
||||
There can be only one instance of a service type in a particular injector.
|
||||
But we've learned that we can have multiple injectors operating at different levels of the application's component tree.
|
||||
Any of those injectors could have its own instance of the service.
|
||||
|
||||
If we defined a `RestoreService` provider only on the root component,
|
||||
we would have exactly one instance of that service and it would be shared across the entire application.
|
||||
|
||||
That’s clearly not what we want in this scenario. We want each component to have its own instance of the `RestoreService`.
|
||||
Defining (or re-defining) a provider at the component level creates a new instance of the service for each new instance
|
||||
of that component. We've made the `RestoreService` a kind of "private" singleton for each `HeroEditComponent`,
|
||||
scoped to that component instance and its child components.
|
||||
|
||||
<!--
|
||||
## Advanced Dependency Injection in Angular 2
|
||||
|
||||
Restrict Dependency Lookups
|
||||
[TODO] (@Host) This has been postponed for now until we come up with a decent use case
|
||||
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Dependency Visibility
|
||||
|
||||
[TODO] (providers vs viewProviders) This has been postponed for now until come up with a decent use case
|
||||
-->
|
||||
|
|
|
@ -13,24 +13,24 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: (ericjim) the new api docs use material design blue 600 links in this fashion.
|
||||
a {
|
||||
color: $blueberry;
|
||||
color: #1E88E5;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration-color: #1E88E5;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: #eef1f2;
|
||||
background: $lightgrey;
|
||||
border-radius: 2px;
|
||||
font-family: $mono-font;
|
||||
color: $regal;
|
||||
color: $darkgrey;
|
||||
padding: 0px 4px;
|
||||
}
|
||||
|
||||
p code {
|
||||
border-bottom: 1px dotted $regal;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 1px solid $regal;
|
||||
}
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
|
@ -118,11 +118,6 @@ p code {
|
|||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
p {
|
||||
font-size: 15px;
|
||||
color: #5c7078;
|
||||
line-height: 28pt;
|
||||
}
|
||||
}
|
||||
|
||||
table th,
|
||||
|
@ -234,4 +229,4 @@ table td {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -129,14 +129,16 @@ input.api-filter {
|
|||
}
|
||||
}
|
||||
|
||||
a {
|
||||
code {
|
||||
color: $ocean;
|
||||
.code-links {
|
||||
a {
|
||||
code, .api-doc-code {
|
||||
color: #1E88E5 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
color: #1a2326;
|
||||
|
||||
&.selector {
|
||||
margin: 0;
|
||||
|
@ -146,7 +148,7 @@ input.api-filter {
|
|||
margin: 0 0 16px 16px !important;
|
||||
}
|
||||
|
||||
code {
|
||||
.api-doc-code {
|
||||
border-bottom: 0px;
|
||||
|
||||
:hover {
|
||||
|
@ -202,12 +204,17 @@ input.api-filter {
|
|||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
// Override highlight.js
|
||||
.kwd {
|
||||
color: #1E88E5 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
.api-doc-code {
|
||||
font-size: 14px;
|
||||
color: #1a2326;
|
||||
|
||||
|
@ -230,7 +237,7 @@ input.api-filter {
|
|||
}
|
||||
}
|
||||
|
||||
code {
|
||||
.api-doc-code {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.dropdown {
|
||||
position: relative;
|
||||
z-index: $layer-1;
|
||||
margin: ($unit * 2) 0 0 0;
|
||||
|
||||
.dropdown-button {
|
||||
background: rgba($snow, .1);
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
width: 135px;
|
||||
overflow: hidden;
|
||||
text-indent: -3000px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin:0;
|
||||
padding: 0;
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
|
||||
background: url('/resources/images/logos/standard/logo-nav@2x.png') 0px 8px no-repeat;
|
||||
|
@ -111,7 +111,9 @@
|
|||
background: none;
|
||||
border-radius: 0;
|
||||
position: relative;
|
||||
letter-spacing: 0.16px;
|
||||
margin: 0;
|
||||
min-width: auto;
|
||||
|
||||
&:hover {
|
||||
background: $ocean;
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
bottom: auto;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// SEARCH BOX
|
||||
.side-nav-search {
|
||||
|
@ -77,6 +80,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.is-nav-title-selected {
|
||||
color: $regal !important;
|
||||
background-color: #DCE2E5 !important;
|
||||
}
|
||||
|
||||
// PRIMARY SIDENAV BUTTON
|
||||
.side-nav-primary {
|
||||
|
@ -135,8 +142,7 @@
|
|||
// SECONDARY SIDENAV
|
||||
.side-nav-secondary {
|
||||
@extend .side-nav-primary;
|
||||
background: $mist;
|
||||
box-shadow: inset 0px 2px 2px rgba($coal, .24);
|
||||
background-color: #DCE2E5;
|
||||
|
||||
@media handheld and (max-width: $phone-breakpoint),
|
||||
screen and (max-device-width: $phone-breakpoint),
|
||||
|
@ -144,40 +150,35 @@
|
|||
display: none;
|
||||
max-height: ($phone-breakpoint * 0.60);
|
||||
}
|
||||
|
||||
|
||||
// FLAT NAV (.nav)
|
||||
.nav-blocks {
|
||||
margin: 0 ($unit * 1.5);
|
||||
padding: ($unit * 2) 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid $fog;
|
||||
color: $heather;
|
||||
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $metal;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: ($unit * 2);
|
||||
margin: 0
|
||||
}
|
||||
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
|
||||
div, li {
|
||||
&.is-selected {
|
||||
> a {
|
||||
background: white;
|
||||
color: $regal;
|
||||
background: #CDD5DA;
|
||||
color: #1E88E5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
li {
|
||||
min-height: ($unit * 2);
|
||||
position: relative;
|
||||
|
@ -191,17 +192,35 @@
|
|||
.nav-title,
|
||||
.nav-sub-title {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
font-weight: 500; //medium
|
||||
color: #78909C;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: ($unit * 2);
|
||||
padding: ($unit * 0.5) 0 ($unit * 1) 0;
|
||||
font-size: 14px;
|
||||
|
||||
padding: ($unit * 2) ($unit * 2);
|
||||
margin: 0;
|
||||
background-color: #ECEFF1;
|
||||
|
||||
.inline-arrow-down-svg {
|
||||
float: right;
|
||||
opacity: 0.56;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-primary-link {
|
||||
margin-bottom: ($unit * 1);
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
padding-left: 16px;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sub-title {
|
||||
|
@ -212,42 +231,51 @@
|
|||
.nav-active a {
|
||||
color: $regal;
|
||||
background: $lightgrey;
|
||||
font-weight: bold;
|
||||
font-weight: 500; //medium
|
||||
}
|
||||
|
||||
.nav-ordered-lists li a,
|
||||
.nav-unordered-lists li a,
|
||||
.nav-primary-link a {
|
||||
margin-left: ($unit * -3);
|
||||
padding-left: ($unit * 3);
|
||||
padding-top: ($unit * 0.5);
|
||||
padding-bottom: ($unit * 0.5);
|
||||
.nav-unordered-lists, .nav-ordered-lists {
|
||||
// Apply inset shadows to the list under the title
|
||||
box-shadow: inset 0 2px 2px rgba(0, 0, 0, .24), inset 0 -2px 2px rgba(0, 0, 0, 0.08);
|
||||
|
||||
// -- First Child
|
||||
// Apply inset shadows on the first child when hovering, since the background color will cover the shadow
|
||||
.nav-list-item:first-child {
|
||||
&:hover {
|
||||
box-shadow: inset 0 2px 2px rgba(0, 0, 0, .24);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply inset shadows on the first child when selected, since the background color will cover the shadow
|
||||
.is-selected:first-child a {
|
||||
box-shadow: inset 0 2px 2px rgba(0, 0, 0, .24);
|
||||
}
|
||||
|
||||
// -- Last Child
|
||||
// Apply inset shadows on the last child when hovering, since the background color will cover the shadow
|
||||
.nav-list-item:last-child {
|
||||
&:hover {
|
||||
box-shadow: inset 0 -2px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply inset shadows on the last child when selected, since the background color will cover the shadow
|
||||
.is-selected:last-child a {
|
||||
box-shadow: inset 0 -2px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-blocks li a:hover,
|
||||
.nav-primary-link a:hover {
|
||||
background: $fog;
|
||||
}
|
||||
.nav-list-item {
|
||||
a {
|
||||
font-size: 13px;
|
||||
font-weight: 400; //medium
|
||||
padding-left: $unit * 2;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.nav-primary-link a:hover {
|
||||
padding-top: ($unit * 0.5);
|
||||
padding-bottom: ($unit * 0.5);
|
||||
&:hover {
|
||||
background-color: #CFD8DC;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-blocks li a:hover:before,
|
||||
.nav-primary-link a:hover:before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 0.4em;
|
||||
height: 0.4em;
|
||||
border-right: 0.2em solid $tin;
|
||||
border-top: 0.2em solid $tin;
|
||||
transform: rotate(45deg);
|
||||
right: -5px;
|
||||
margin-top: -3px;
|
||||
margin-right: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<svg fill="#8FA4AE" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z"/>
|
||||
<path d="M0-.75h24v24H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 213 B |
|
@ -31,35 +31,35 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
div(flex="20" flex-xs="100")
|
||||
h2(class="h2-api-docs") Class Overview
|
||||
div(flex="80" flex-xs="100")
|
||||
code.no-bg class {$ doc.name $} {
|
||||
code(class="no-bg api-doc-code") class {$ doc.name $} {
|
||||
|
||||
{% if doc.statics.length %}
|
||||
.div(layout="column")
|
||||
{% for member in doc.statics %}{% if not member.internal %}
|
||||
pre(class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ member.name $}') }")
|
||||
pre(class="prettyprint no-bg-with-indent")
|
||||
a(class="code-anchor" href="#{$ member.name $}-anchor")
|
||||
code(class="code-background") {$ member.name | indent(6, false) | trim $}
|
||||
code {$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
code(class="code-background api-doc-code") {$ member.name | indent(6, false) | trim $}
|
||||
code(class="api-doc-code") {$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
{% if doc.constructorDoc.name %}
|
||||
.div(layout="column")
|
||||
pre(class="prettyprint no-bg-with-indent")
|
||||
a(class="code-anchor" href="#constructor")
|
||||
code(class="code-background") {$ doc.constructorDoc.name $}
|
||||
code {$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
|
||||
code(class="code-background api-doc-code") {$ doc.constructorDoc.name $}
|
||||
code(class="api-doc-code") {$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
|
||||
{% endif %}
|
||||
{% if doc.members.length %}
|
||||
.div(layout="column")
|
||||
{% for member in doc.members %}{% if not member.internal %}
|
||||
pre(class="prettyprint no-bg-with-indent")
|
||||
a(class="code-anchor" href="#{$ member.name $}-anchor")
|
||||
code(class="code-background") {$ member.name | indent(6, false) | trim $}
|
||||
code {$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
code(class="code-background api-doc-code") {$ member.name | indent(6, false) | trim $}
|
||||
code(class="api-doc-code") {$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
p.selector
|
||||
code.no-bg }
|
||||
code(class="api-doc-code no-bg") }
|
||||
|
||||
{% block additional %}
|
||||
{% endblock %}
|
||||
|
@ -67,7 +67,7 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
.div(class="row-margin" layout="row" layout-xs="column")
|
||||
div(flex="20" flex-xs="100")
|
||||
h2(class="h2-api-docs") Class Description
|
||||
div(class="class-description-content" flex="80" flex-xs="100")
|
||||
div(class="code-links" flex="80" flex-xs="100")
|
||||
:marked
|
||||
{%- if doc.description.length > 2 %}
|
||||
{$ doc.description | indentForMarkdown(6) | trimBlankLines $}
|
||||
|
@ -80,7 +80,7 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
h2(class="h2-api-docs") Class Export
|
||||
div(flex="80" flex-xs="100")
|
||||
pre.prettyprint.no-bg
|
||||
code.no-pln.
|
||||
code(class="api-doc-code no-pln").
|
||||
export {$ doc.docType $} {$ doc.name $}
|
||||
|
||||
{%- if doc.decorators.length %}
|
||||
|
@ -91,7 +91,7 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
div(flex="80" flex-xs="100")
|
||||
{%- for decorator in doc.decorators %}
|
||||
pre.prettyprint.no-bg
|
||||
code.
|
||||
code(class="api-doc-code")
|
||||
@{$ decorator.name $}{$ paramList(decorator.arguments) | indent(10, false) $}
|
||||
:marked
|
||||
{%- if decorator.notYetDocumented %}
|
||||
|
@ -110,7 +110,7 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
div(flex="80" flex-xs="100")
|
||||
a(name="constructor" class="anchor-offset")
|
||||
pre(class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ doc.constructorDoc.name $}') }")
|
||||
code.
|
||||
code(class="api-doc-code").
|
||||
{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
|
||||
:marked
|
||||
{%- if doc.constructorDoc.notYetDocumented %}
|
||||
|
@ -124,16 +124,17 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
.div(layout="row" layout-xs="column" class="row-margin")
|
||||
div(flex="20" flex-xs="100")
|
||||
h2(class="h2-api-docs") Static Members
|
||||
div(flex="80" flex-xs="100")
|
||||
div(class="code-links" flex="80" flex-xs="100")
|
||||
{% for member in doc.statics %}{% if not member.internal %}
|
||||
pre.prettyprint.no-bg
|
||||
code.
|
||||
a(name="{$ member.name $}-anchor" class="anchor-offset")
|
||||
pre(class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ member.name $}') }")
|
||||
code(class="api-doc-code").
|
||||
{$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
:marked
|
||||
{%- if member.notYetDocumented %}
|
||||
*Not yet documented*
|
||||
{% else %}
|
||||
{$ member.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
||||
{$ member.description | indentForMarkdown(6) | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
||||
{% endif %}
|
||||
|
||||
{% if not loop.last %}
|
||||
|
@ -151,7 +152,7 @@ include {$ relativePath(doc.path, '_util-fns') $}
|
|||
{% for member in doc.members %}{% if not member.internal %}
|
||||
a(name="{$ member.name $}-anchor" class="anchor-offset")
|
||||
pre(class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ member.name $}') }")
|
||||
code.
|
||||
code(class="api-doc-code").
|
||||
{$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
|
||||
:marked
|
||||
|
|
Loading…
Reference in New Issue