docs(testing): more testing samples and infrastructure setup

added wallaby config; revised karma-test-shim
This commit is contained in:
Ward Bell 2016-04-14 10:36:38 -07:00
parent 96137efd79
commit e1862887ae
26 changed files with 1019 additions and 175 deletions

View File

@ -76,7 +76,8 @@ var _exampleBoilerplateFiles = [
'styles.css', 'styles.css',
'tsconfig.json', 'tsconfig.json',
'tslint.json', 'tslint.json',
'typings.json' 'typings.json',
'wallaby.js'
]; ];
var _exampleDartWebBoilerPlateFiles = ['styles.css']; var _exampleDartWebBoilerPlateFiles = ['styles.css'];

View File

@ -1,13 +1,21 @@
.editorconfig .editorconfig
.idea
.vscode
styles.css styles.css
typings typings
typings.json typings.json
node_modules
jspm_packages
*.js.map *.js.map
package.json package.json
karma.conf.js karma.conf.js
karma-test-shim.js karma-test-shim.js
tsconfig.json tsconfig.json
tslint.json tslint.json
wallaby.js
npm-debug*. npm-debug*.
**/protractor.config.js protractor.config.js
_test-output _test-output
_temp
!**/*e2e-spec.js

View File

@ -1,5 +1,4 @@
describe('Displaying Data Tests', function () { describe('Displaying Data Tests', function () {
var _title = "Tour of Heroes"; var _title = "Tour of Heroes";
var _defaultHero = 'Windstorm' var _defaultHero = 'Windstorm'
@ -15,7 +14,12 @@ describe('Displaying Data Tests', function () {
expect(element(by.css('h2')).getText()).toContain(_defaultHero); 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!'); expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!');
}); });
}); });

View File

@ -3,7 +3,7 @@
// #docregion imports // #docregion imports
import {Component} from 'angular2/core'; import {Component} from 'angular2/core';
// #enddocregion imports // #enddocregion imports
import {Hero} from './hero' import {Hero} from './hero';
@Component({ @Component({
selector: 'my-app', selector: 'my-app',

View File

@ -41,30 +41,29 @@ System.config({ packages: packages });
// Configure Angular for the browser and // Configure Angular for the browser and
// with test versions of the platform providers // with test versions of the platform providers
System.import('angular2/testing') Promise.all([
.then(function (testing) { System.import('angular2/testing'),
return System.import('angular2/platform/testing/browser') System.import('angular2/platform/testing/browser')
.then(function (providers) { ])
testing.setBaseTestProviders( .then(function (results) {
providers.TEST_BROWSER_PLATFORM_PROVIDERS, var testing = results[0];
providers.TEST_BROWSER_APPLICATION_PROVIDERS 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 .then(success, fail);
// (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);
////// Helpers ////// ////// Helpers //////

View File

@ -1,7 +1,7 @@
module.exports = function(config) { module.exports = function(config) {
var appBase = 'app/'; // transpiled app JS files 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({ config.set({
basePath: '', basePath: '',

View File

@ -1,3 +1,4 @@
/* tslint:disable:forin */
// #docregion // #docregion
import { import {
Component, Input, ViewChild, Component, Input, ViewChild,
@ -6,7 +7,7 @@ import {
class Hero { class Hero {
constructor(public name:string){} constructor(public name: string) {}
} }
@Component({ @Component({
@ -30,13 +31,13 @@ export class OnChangesComponent implements OnChanges {
@Input() power: string; @Input() power: string;
// #enddocregion inputs // #enddocregion inputs
changeLog:string[] = []; changeLog: string[] = [];
// #docregion ng-on-changes // #docregion ng-on-changes
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
for (let propName in changes) { for (let propName in changes) {
let prop = changes[propName]; let prop = changes[propName];
let cur = JSON.stringify(prop.currentValue) let cur = JSON.stringify(prop.currentValue);
let prev = JSON.stringify(prop.previousValue); let prev = JSON.stringify(prop.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
} }
@ -50,13 +51,13 @@ export class OnChangesComponent implements OnChanges {
@Component({ @Component({
selector: 'on-changes-parent', selector: 'on-changes-parent',
templateUrl:'app/on-changes-parent.component.html', templateUrl: 'app/on-changes-parent.component.html',
styles: ['.parent {background: Lavender;}'], styles: ['.parent {background: Lavender;}'],
directives: [OnChangesComponent] directives: [OnChangesComponent]
}) })
export class OnChangesParentComponent { export class OnChangesParentComponent {
hero:Hero; hero: Hero;
power:string; power: string;
title = 'OnChanges'; title = 'OnChanges';
@ViewChild(OnChangesComponent) childView:OnChangesComponent; @ViewChild(OnChangesComponent) childView:OnChangesComponent;
@ -69,6 +70,6 @@ export class OnChangesParentComponent {
this.hero = new Hero('Windstorm'); this.hero = new Hero('Windstorm');
// setting power only triggers onChanges if this value is different // setting power only triggers onChanges if this value is different
this.power = 'sing'; this.power = 'sing';
this.childView && this.childView.reset(); if (this.childView) { this.childView.reset(); }
} }
} }

View File

@ -2,19 +2,18 @@
"name": "angular2-examples-master", "name": "angular2-examples-master",
"version": "1.0.0", "version": "1.0.0",
"description": "Master package.json, the superset of all dependencies for all of the _example package.json files.", "description": "Master package.json, the superset of all dependencies for all of the _example package.json files.",
"main": "index.js",
"scripts": { "scripts": {
"start": "tsc && concurrently \"tsc -w\" \"lite-server\" ", "start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
"tsc": "tsc", "e2e": "tsc && concurrently \"http-server\" \"protractor protractor.config.js\"",
"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",
"http-server": "tsc && http-server", "http-server": "tsc && http-server",
"http-server:e2e": "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", "typings": "typings",
"postinstall": "typings install" "webdriver:update": "webdriver-manager update"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -37,6 +36,7 @@
"typescript": "^1.8.10", "typescript": "^1.8.10",
"typings":"^0.7.12", "typings":"^0.7.12",
"canonical-path": "0.0.2",
"http-server": "^0.9.0", "http-server": "^0.9.0",
"jasmine-core": "~2.4.1", "jasmine-core": "~2.4.1",
"karma": "^0.13.22", "karma": "^0.13.22",
@ -44,7 +44,6 @@
"karma-cli": "^0.1.2", "karma-cli": "^0.1.2",
"karma-htmlfile-reporter": "^0.2.2", "karma-htmlfile-reporter": "^0.2.2",
"karma-jasmine": "^0.3.8", "karma-jasmine": "^0.3.8",
"live-server": "^0.9.2",
"protractor": "^3.2.2", "protractor": "^3.2.2",
"rimraf": "^2.5.2" "rimraf": "^2.5.2"
}, },

View File

@ -1,9 +1,14 @@
// TO RUN THE TESTS // FIRST TIME ONLY- run:
//
// The first time, run:
// ./node_modules/.bin/webdriver-manager update // ./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 fs = require('fs');
var path = require('canonical-path'); 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) { function sendKeys(element, str) {
return str.split('').reduce(function (promise, char) { return str.split('').reduce(function (promise, char) {
return promise.then(function () { return promise.resolve(element.sendKeys(char));
return element.sendKeys(char);
});
}, element.getAttribute('value')); }, element.getAttribute('value'));
// better to create a resolved promise here but ... don't know how with protractor;
} }
function Reporter(options) { function Reporter(options) {
var _defaultOutputFile = path.resolve(process.cwd(), "../../", 'protractor-results.txt'); var _defaultOutputFile = path.resolve(process.cwd(), "../../", 'protractor-results.txt');
options.outputFile = options.outputFile || _defaultOutputFile; options.outputFile = options.outputFile || _defaultOutputFile;

View File

@ -3,11 +3,11 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ", "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"lite": "lite-server",
"postinstall": "typings install",
"tsc": "tsc", "tsc": "tsc",
"tsc:w": "tsc -w", "tsc:w": "tsc -w",
"lite": "lite-server", "typings": "typings"
"typings": "typings",
"postinstall": "typings install"
}, },
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {

View File

@ -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');
});
});

View File

@ -1,18 +1,20 @@
// #docplaster // #docplaster
// #docregion // #docregion
import { Component } from 'angular2/core'; 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 { DashboardComponent } from './dashboard.component'; // import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { HeroesComponent } from './heroes.component';
// #docregion hero-detail-import 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'; import { HeroDetailComponent } from './hero-detail.component';
// #enddocregion hero-detail-import import { HeroService } from './hero.service';
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
// #docregion template
template: ` template: `
<h1>{{title}}</h1> <h1>{{title}}</h1>
<nav> <nav>
@ -21,39 +23,18 @@ import { HeroDetailComponent } from './hero-detail.component';
</nav> </nav>
<router-outlet></router-outlet> <router-outlet></router-outlet>
`, `,
// #enddocregion template
// #docregion style-urls
styleUrls: ['app/app.component.css'], styleUrls: ['app/app.component.css'],
// #enddocregion style-urls directives: [RouterLink, RouterOutlet],
directives: [ROUTER_DIRECTIVES],
providers: [ providers: [
ROUTER_PROVIDERS, ROUTER_PROVIDERS,
HeroService HeroService
] ]
}) })
@RouteConfig([ @RouteConfig([
// #docregion dashboard-route { path: '/dashboard', name: 'Dashboard', component: DashboardComponent, useAsDefault: true },
{ { path: '/detail/:id', name: 'HeroDetail', component: HeroDetailComponent },
path: '/dashboard', { path: '/heroes', name: 'Heroes', component: HeroesComponent }
name: 'Dashboard',
component: DashboardComponent,
useAsDefault: true
},
// #enddocregion dashboard-route
// #docregion hero-detail-route
{
path: '/detail/:id',
name: 'HeroDetail',
component: HeroDetailComponent
},
// #enddocregion hero-detail-route
{
path: '/heroes',
name: 'Heroes',
component: HeroesComponent
}
]) ])
export class AppComponent { export class AppComponent {
title = 'Tour of Heroes'; title = 'Tour of Heroes';
} }
// #enddocregion

View File

@ -5,28 +5,22 @@ import {
ChildChildComp, ChildComp, ChildWithChildComp, ChildChildComp, ChildComp, ChildWithChildComp,
ExternalTemplateComp, ExternalTemplateComp,
FancyService, MockFancyService, FancyService, MockFancyService,
MyIfComp, InputComp,
MyIfComp, MyIfChildComp, MyIfParentComp,
MockChildComp, MockChildChildComp, MockChildComp, MockChildChildComp,
ParentComp, ParentComp,
TestProvidersComp, TestViewProvidersComp TestProvidersComp, TestViewProvidersComp
} from './public'; } from './bag';
import { DebugElement } from 'angular2/core';
import { By } from 'angular2/platform/browser';
import { import {
it, beforeEach, beforeEachProviders, withProviders,
iit, describe, ddescribe, xdescribe,
xit, expect, it, iit, xit,
describe, inject, injectAsync, fakeAsync, tick,
ddescribe, ComponentFixture, TestComponentBuilder
xdescribe,
expect,
fakeAsync,
tick,
beforeEach,
inject,
injectAsync,
withProviders,
beforeEachProviders,
TestComponentBuilder
} from 'angular2/testing'; } from 'angular2/testing';
import { provide } from 'angular2/core'; import { provide } from 'angular2/core';
@ -49,13 +43,13 @@ describe('angular2 jasmine matchers', () => {
describe('toHaveCssClass', () => { describe('toHaveCssClass', () => {
it('should assert that the CSS class is present', () => { it('should assert that the CSS class is present', () => {
let el = document.createElement('div'); let el = document.createElement('div');
el.classList.add('matias'); el.classList.add('bombasto');
expect(el).toHaveCssClass('matias'); expect(el).toHaveCssClass('bombasto');
}); });
it('should assert that the CSS class is not present', () => { it('should assert that the CSS class is not present', () => {
let el = document.createElement('div'); let el = document.createElement('div');
el.classList.add('matias'); el.classList.add('bombasto');
expect(el).not.toHaveCssClass('fatias'); expect(el).not.toHaveCssClass('fatias');
}); });
}); });
@ -187,13 +181,39 @@ describe('test component builder', function() {
let comp = <ButtonComp> fixture.componentInstance; let comp = <ButtonComp> fixture.componentInstance;
expect(comp.wasClicked).toEqual(false, 'wasClicked should be false at start'); 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.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'); 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', it('should override a template',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { 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) => { injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideDirective(ParentComp, ChildComp, MockChildComp) 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) => { injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp) return tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
@ -262,7 +282,6 @@ describe('test component builder', function() {
}); });
})); }));
it('should override a viewProvider', it('should override a viewProvider',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
@ -288,16 +307,158 @@ describe('test component builder', function() {
.toHaveText('from external template\n'); .toHaveText('from external template\n');
}); });
}), 10000); // Long timeout because this test makes an actual XHR. }), 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 originalJasmineIt: any;
let originalJasmineBeforeEach: any; let originalJasmineBeforeEach: any;
let patchJasmineIt = () => { let patchJasmineIt = () => {
let deferred = PromiseWrapper.completer(); let deferred = PromiseWrapper.completer();
originalJasmineIt = jasmine.getEnv().it; originalJasmineIt = jasmine.getEnv().it;
jasmine.getEnv().it = (description: string, fn: Function) => { jasmine.getEnv().it = (description: string, fn: Function): jasmine.Spec => {
let done = () => { deferred.resolve(); }; let done = () => { deferred.resolve(); };
(<any>done).fail = (err: any) => { deferred.reject(err); }; (<any>done).fail = (err: any) => { deferred.reject(err); };
fn(done); fn(done);
@ -311,7 +472,7 @@ describe('errors', () => {
let patchJasmineBeforeEach = () => { let patchJasmineBeforeEach = () => {
let deferred = PromiseWrapper.completer(); let deferred = PromiseWrapper.completer();
originalJasmineBeforeEach = jasmine.getEnv().beforeEach; originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
jasmine.getEnv().beforeEach = (fn: any) => { jasmine.getEnv().beforeEach = (fn: any): void => {
let done = () => { deferred.resolve(); }; let done = () => { deferred.resolve(); };
(<any>done).fail = (err: any) => { deferred.reject(err); }; (<any>done).fail = (err: any) => { deferred.reject(err); };
fn(done); 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);
}));
});

View File

@ -1,6 +1,7 @@
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts // Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
import { Component, Injectable } from 'angular2/core'; /* tslint:disable:forin */
import { NgIf } from 'angular2/common'; import { Component, EventEmitter, Injectable, Input, Output,
OnInit, OnChanges, OnDestroy, SimpleChange } from 'angular2/core';
// Let TypeScript know about the special SystemJS __moduleName variable // Let TypeScript know about the special SystemJS __moduleName variable
declare var __moduleName: string; declare var __moduleName: string;
@ -39,6 +40,13 @@ export class ButtonComp {
clicked() { this.wasClicked = true; } clicked() { this.wasClicked = true; }
} }
@Component({
selector: 'input-comp',
template: `<input [(ngModel)]="name">`
})
export class InputComp {
name = 'John';
}
@Component({ @Component({
selector: 'child-comp', selector: 'child-comp',
@ -66,14 +74,12 @@ export class ParentComp { }
@Component({ @Component({
selector: 'my-if-comp', selector: 'my-if-comp',
template: `MyIf(<span *ngIf="showMore">More</span>)`, template: `MyIf(<span *ngIf="showMore">More</span>)`
directives: [NgIf]
}) })
export class MyIfComp { export class MyIfComp {
showMore: boolean = false; showMore = false;
} }
@Component({ @Component({
selector: 'child-child-comp', selector: 'child-child-comp',
template: '<span>ChildChild</span>' template: '<span>ChildChild</span>'
@ -121,7 +127,7 @@ export class TestViewProvidersComp {
@Component({ @Component({
moduleId: __moduleName, moduleId: __moduleName,
selector: 'external-template-comp', selector: 'external-template-comp',
templateUrl: 'public-external-template.html' templateUrl: 'bag-external-template.html'
}) })
export class ExternalTemplateComp { } export class ExternalTemplateComp { }
@ -131,3 +137,88 @@ export class ExternalTemplateComp { }
templateUrl: 'non-existant.html' templateUrl: 'non-existant.html'
}) })
export class BadTemplateUrl { } 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';
}
}

View File

@ -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');
});
}));
});
});

View File

@ -31,7 +31,7 @@ export class DashboardComponent implements OnInit {
ngOnInit() { ngOnInit() {
this._heroService.getHeroes() this._heroService.getHeroes()
.then(heroes => this.heroes = heroes.slice(1,5)); .then(heroes => this.heroes = heroes.slice(1, 5));
} }
// #docregion goto-detail // #docregion goto-detail
@ -41,4 +41,4 @@ export class DashboardComponent implements OnInit {
} }
// #enddocregion goto-detail // #enddocregion goto-detail
} }
// #enddocregion // #enddocregion

View File

@ -1,19 +1,9 @@
/* tslint:disable:no-unused-variable */ /* tslint:disable:no-unused-variable */
import { import {
it, beforeEach, beforeEachProviders, withProviders,
iit, describe, ddescribe, xdescribe,
xit, expect, it, iit, xit,
describe, inject, injectAsync, fakeAsync, TestComponentBuilder, tick
ddescribe,
xdescribe,
expect,
fakeAsync,
tick,
beforeEach,
inject,
injectAsync,
withProviders,
beforeEachProviders
} from 'angular2/testing'; } from 'angular2/testing';
import { provide } from 'angular2/core'; import { provide } from 'angular2/core';
@ -23,17 +13,12 @@ import {
MockConnection } from 'angular2/src/http/backends/mock_backend'; MockConnection } from 'angular2/src/http/backends/mock_backend';
import { import {
BaseRequestOptions, Http, HTTP_PROVIDERS,
ConnectionBackend, ConnectionBackend, XHRBackend,
Request, Request, RequestMethod, BaseRequestOptions, RequestOptions,
RequestMethod, Response, ResponseOptions,
RequestOptions, URLSearchParams
Response, } from 'angular2/http';
ResponseOptions,
URLSearchParams,
HTTP_PROVIDERS,
XHRBackend,
Http} from 'angular2/http';
// Add all operators to Observable // Add all operators to Observable
import 'rxjs/Rx'; import 'rxjs/Rx';
@ -45,10 +30,10 @@ import { HeroService } from './http-hero.service';
type HeroData = {id: string, name: string} type HeroData = {id: string, name: string}
const makeHeroData = () => [ const makeHeroData = () => [
{ "id": "1", "name": "Windstorm" }, { id: '1', name: 'Windstorm' },
{ "id": "2", "name": "Bombasto" }, { id: '2', name: 'Bombasto' },
{ "id": "3", "name": "Magneta" }, { id: '3', name: 'Magneta' },
{ "id": "4", "name": "Tornado" } { id: '4', name: 'Tornado' }
]; ];
// HeroService expects response data like {data: {the-data}} // HeroService expects response data like {data: {the-data}}

View File

@ -1,4 +1,6 @@
import { bootstrap } from 'angular2/platform/browser'; import { bootstrap } from 'angular2/platform/browser';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { MyIfParentComp } from './bag';
bootstrap(AppComponent); bootstrap(AppComponent);
bootstrap(MyIfParentComp);

View File

@ -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(); }
}

View File

@ -2,15 +2,15 @@
import { Hero } from './hero'; import { Hero } from './hero';
export var HEROES: Hero[] = [ export var HEROES: Hero[] = [
{"id": 11, "name": "Mr. Nice"}, {id: 11, name: 'Mr. Nice'},
{"id": 12, "name": "Narco"}, {id: 12, name: 'Narco'},
{"id": 13, "name": "Bombasto"}, {id: 13, name: 'Bombasto'},
{"id": 14, "name": "Celeritas"}, {id: 14, name: 'Celeritas'},
{"id": 15, "name": "Magneta"}, {id: 15, name: 'Magneta'},
{"id": 16, "name": "RubberMan"}, {id: 16, name: 'RubberMan'},
{"id": 17, "name": "Dynama"}, {id: 17, name: 'Dynama'},
{"id": 18, "name": "Dr IQ"}, {id: 18, name: 'Dr IQ'},
{"id": 19, "name": "Magma"}, {id: 19, name: 'Magma'},
{"id": 20, "name": "Tornado"} {id: 20, name: 'Tornado'}
]; ];
// #enddocregion // #enddocregion

View File

@ -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);
}
}

View File

@ -4,7 +4,7 @@
import { MyUppercasePipe } from './my-uppercase.pipe'; import { MyUppercasePipe } from './my-uppercase.pipe';
describe('MyUppercasePipe', () => { describe('MyUppercasePipe', () => {
let pipe : MyUppercasePipe; let pipe: MyUppercasePipe;
beforeEach(() => { beforeEach(() => {
pipe = new MyUppercasePipe(); pipe = new MyUppercasePipe();

View File

@ -15,7 +15,6 @@
<!-- #docregion head --> <!-- #docregion head -->
<!-- IE required polyfills, in this exact order --> <!-- IE required polyfills, in this exact order -->
<script src="node_modules/es6-shim/es6-shim.min.js"></script> <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/es6/dev/src/testing/shims_for_IE.js"></script>
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
@ -35,5 +34,7 @@
<body> <body>
<my-app>Loading...</my-app> <my-app>Loading...</my-app>
<hr>
<my-if-parent-comp>Loading MyIfParentComp ...</my-if-parent-comp>
</body> </body>
</html> </html>

View File

@ -3,7 +3,7 @@
<html> <html>
<head> <head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"> <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"> <link rel="stylesheet" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script> <script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
@ -24,7 +24,7 @@
<script src="node_modules/angular2/bundles/testing.dev.js"></script> <script src="node_modules/angular2/bundles/testing.dev.js"></script>
<script> <script>
var __spec_files__ = ['app/public.spec']; var __spec_files__ = ['app/bag.spec'];
</script> </script>
<script src="test-shim.js"></script> <script src="test-shim.js"></script>
</body> </body>

View File

@ -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
};
};