docs(testing): more testing samples and infrastructure setup
added wallaby config; revised karma-test-shim
This commit is contained in:
parent
96137efd79
commit
e1862887ae
|
@ -76,7 +76,8 @@ var _exampleBoilerplateFiles = [
|
|||
'styles.css',
|
||||
'tsconfig.json',
|
||||
'tslint.json',
|
||||
'typings.json'
|
||||
'typings.json',
|
||||
'wallaby.js'
|
||||
];
|
||||
|
||||
var _exampleDartWebBoilerPlateFiles = ['styles.css'];
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -41,20 +41,19 @@ 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) {
|
||||
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(
|
||||
providers.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
providers.TEST_BROWSER_APPLICATION_PROVIDERS
|
||||
);
|
||||
});
|
||||
})
|
||||
browser.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
browser.TEST_BROWSER_APPLICATION_PROVIDERS);
|
||||
|
||||
// Load all spec files
|
||||
// (e.g. 'base/app/hero.service.spec.js')
|
||||
.then(function () {
|
||||
// Load all spec files
|
||||
// (e.g. 'base/app/hero.service.spec.js')
|
||||
return Promise.all(
|
||||
Object.keys(window.__karma__.files)
|
||||
.filter(onlySpecFiles)
|
||||
|
@ -62,9 +61,9 @@ System.import('angular2/testing')
|
|||
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 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');
|
||||
|
@ -93,17 +98,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';
|
||||
// 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';
|
||||
// #docregion hero-detail-import
|
||||
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
|
||||
|
|
|
@ -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 { MyIfParentComp } from './bag';
|
||||
|
||||
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
|
|
@ -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
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue