diff --git a/public/docs/_examples/karma.conf.js b/public/docs/_examples/karma.conf.js index 0b36ed93c5..ddf41a395f 100644 --- a/public/docs/_examples/karma.conf.js +++ b/public/docs/_examples/karma.conf.js @@ -20,24 +20,30 @@ module.exports = function(config) { flags: ['--no-sandbox'] } }, - files: [ - // Angular and shim libraries loaded by Karma - { pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: true, watched: true }, - { pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true }, - { pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true }, - { pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', included: true, watched: true }, - { pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true }, - { pattern: 'node_modules/angular2/bundles/angular2.js', included: true, watched: true }, - { pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: true }, + // System.js for module loading + 'node_modules/systemjs/dist/system-polyfills.js', + 'node_modules/systemjs/dist/system.src.js', - // External libraries loaded by Karma - { pattern: 'node_modules/angular2/bundles/http.dev.js', included: true, watched: true }, - { pattern: 'node_modules/angular2/bundles/router.dev.js', included: true, watched: true }, - { pattern: 'node_modules/a2-in-memory-web-api/web-api.js', included: true, watched: true }, + // Polyfills + 'node_modules/es6-shim/es6-shim.js', + 'node_modules/angular2/bundles/angular2-polyfills.js', - // Configures module loader w/ app and specs, then launch karma - { pattern: 'karma-test-shim.js', included: true, watched: true }, + // Zone.js dependencies + // Note - do not include zone.js itself or long-stack-trace-zone.js` here as + // they are included already in angular2-polyfills + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + 'node_modules/rxjs/bundles/Rx.js', + + // Angular 2 itself and the testing library + 'node_modules/angular2/bundles/angular2.js', + 'node_modules/angular2/bundles/testing.dev.js', + + 'karma-test-shim.js', // transpiled application & spec code paths loaded via module imports {pattern: appBase + '**/*.js', included: false, watched: true}, diff --git a/public/docs/_examples/testing/ts/app/app.component.spec.ts b/public/docs/_examples/testing/ts/app/app.component.spec.ts index caa77ad66e..32f21550c0 100644 --- a/public/docs/_examples/testing/ts/app/app.component.spec.ts +++ b/public/docs/_examples/testing/ts/app/app.component.spec.ts @@ -1,15 +1,14 @@ /* tslint:disable:no-unused-variable */ import { AppComponent } from './app.component'; -import { By } from 'angular2/platform/browser'; -import { provide } from 'angular2/core'; +import { By } from 'angular2/platform/browser'; +import { DebugElement, provide } from 'angular2/core'; import { beforeEach, beforeEachProviders, describe, ddescribe, xdescribe, expect, it, iit, xit, - inject, injectAsync, - ComponentFixture, TestComponentBuilder + async, inject, ComponentFixture, TestComponentBuilder } from 'angular2/testing'; import { Hero, HeroService, MockHeroService } from './mock-hero.service'; @@ -22,8 +21,8 @@ describe('AppComponent', () => { let fixture: ComponentFixture; let comp: AppComponent; - beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb + beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + tcb .overrideDirective(AppComponent, RouterLink, MockRouterLink) .overrideDirective(AppComponent, RouterOutlet, MockRouterOutlet) .overrideProviders(AppComponent, [ @@ -35,7 +34,7 @@ describe('AppComponent', () => { fixture = fix; comp = fixture.debugElement.componentInstance; }); - })); + }))); it('can instantiate it', () => { expect(comp).not.toBeNull(); @@ -51,8 +50,8 @@ describe('AppComponent', () => { fixture.detectChanges(); let links = fixture.debugElement - .queryAll(function (de) { return de.componentInstance instanceof MockRouterLink; }) - .map(de => de.componentInstance); + .queryAll(By.directive(MockRouterLink)) + .map(de => extractDirective(de, MockRouterLink)); expect(links.length).toEqual(2, 'should have 2 links'); expect(links[0].routeParams[0]).toEqual('Dashboard', '1st link should go to Dashboard'); @@ -67,11 +66,12 @@ describe('AppComponent', () => { // Heroes RouterLink DebugElement let heroesDe = fixture.debugElement - .queryAll(function (de) { return de.componentInstance instanceof MockRouterLink; })[1]; + .queryAll(By.directive(MockRouterLink))[1]; - expect(heroesDe).not.toBeNull('should 2nd link'); + expect(heroesDe).toBeDefined('should have a 2nd RouterLink'); + + let link = extractDirective(heroesDe, MockRouterLink); - let link = heroesDe.componentInstance; expect(link.navigatedTo).toBeNull('link should not have navigate yet'); heroesDe.triggerEventHandler('click', null); @@ -82,3 +82,15 @@ describe('AppComponent', () => { }); }); +///////////// Helpers //////////////////// + +import { Type } from 'angular2/src/facade/lang'; + +/** + * Get the directive instance from the DebugElement to which it is attached + */ +function extractDirective(de: DebugElement, directive: Type): any { + return de.injector.get( + de.providerTokens[de.providerTokens.indexOf(directive)] + ); +} diff --git a/public/docs/_examples/testing/ts/app/bad-tests.spec.ts b/public/docs/_examples/testing/ts/app/bad-tests.spec.ts new file mode 100644 index 0000000000..8829c540d6 --- /dev/null +++ b/public/docs/_examples/testing/ts/app/bad-tests.spec.ts @@ -0,0 +1,172 @@ +// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts +/* tslint:disable:no-unused-variable */ +/** + * Tests that show what goes wrong when the tests are incorrectly written or have a problem + */ +import { + BadTemplateUrl, ButtonComp, + ChildChildComp, ChildComp, ChildWithChildComp, + ExternalTemplateComp, + FancyService, MockFancyService, + InputComp, + MyIfComp, MyIfChildComp, MyIfParentComp, + MockChildComp, MockChildChildComp, + ParentComp, + TestProvidersComp, TestViewProvidersComp +} from './bag'; + +import { DebugElement } from 'angular2/core'; +import { By } from 'angular2/platform/browser'; + +import { + beforeEach, beforeEachProviders, withProviders, + describe, ddescribe, xdescribe, + expect, it, iit, xit, + async, inject, fakeAsync, tick, + ComponentFixture, TestComponentBuilder +} from 'angular2/testing'; + +import { provide } from 'angular2/core'; +import { ViewMetadata } from 'angular2/core'; +import { PromiseWrapper } from 'angular2/src/facade/promise'; +import { XHR } from 'angular2/src/compiler/xhr'; +import { XHRImpl } from 'angular2/src/platform/browser/xhr_impl'; +import { Observable } from 'rxjs/Rx'; + +//////// SPECS ///////////// + +xdescribe('async & inject 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.Spec => { + let done = () => { deferred.resolve(); }; + (done).fail = (err: any) => { deferred.reject(err); }; + fn(done); + return null; + }; + return deferred.promise; + }; + + let restoreJasmineIt = () => { jasmine.getEnv().it = originalJasmineIt; }; + + let patchJasmineBeforeEach = () => { + let deferred = PromiseWrapper.completer(); + originalJasmineBeforeEach = jasmine.getEnv().beforeEach; + jasmine.getEnv().beforeEach = (fn: any): void => { + let done = () => { deferred.resolve(); }; + (done).fail = (err: any) => { deferred.reject(err); }; + fn(done); + return null; + }; + return deferred.promise; + }; + + let restoreJasmineBeforeEach = + () => { jasmine.getEnv().beforeEach = originalJasmineBeforeEach; }; + + const shouldNotSucceed = + (done: DoneFn) => () => done.fail( 'Expected an error, but did not get one.'); + + const shouldFail = + (done: DoneFn, emsg: string) => (err: any) => { expect(err).toEqual(emsg); done(); }; + + it('should fail when an asynchronous error is thrown', (done: DoneFn) => { + let itPromise = patchJasmineIt(); + + it('throws an async error', + async(inject([], () => { setTimeout(() => { throw new Error('bar'); }, 0); }))); + + itPromise.then( + shouldNotSucceed(done), + err => { + expect(err).toEqual('bar'); + done(); + }); + restoreJasmineIt(); + }); + + it('should fail when a returned promise is rejected', (done: DoneFn) => { + let itPromise = patchJasmineIt(); + + it('should fail with an error from a promise', async(inject([], () => { + let deferred = PromiseWrapper.completer(); + let p = deferred.promise.then(() => { expect(1).toEqual(2); }); + + deferred.reject('baz'); + return p; + }))); + + itPromise.then( + shouldNotSucceed(done), + err => { + expect(err).toEqual('Uncaught (in promise): baz'); + done(); + }); + restoreJasmineIt(); + }); + + it('should fail when an error occurs inside inject', (done: DoneFn) => { + let itPromise = patchJasmineIt(); + + it('throws an error', inject([], () => { throw new Error('foo'); })); + + itPromise.then( + shouldNotSucceed(done), + shouldFail(done, 'foo') + ); + restoreJasmineIt(); + }); + + // TODO(juliemr): reenable this test when we are using a test zone and can capture this error. + it('should fail when an asynchronous error is thrown', (done: DoneFn) => { + let itPromise = patchJasmineIt(); + + it('throws an async error', + async(inject([], () => { setTimeout(() => { throw new Error('bar'); }, 0); }))); + + itPromise.then( + shouldNotSucceed(done), + shouldFail(done, 'bar') + ); + restoreJasmineIt(); + }); + + it('should fail when XHR loading of a template fails', (done: DoneFn) => { + let itPromise = patchJasmineIt(); + + it('should fail with an error from a promise', + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + tcb.createAsync(BadTemplateUrl); + }))); + + itPromise.then( + shouldNotSucceed(done), + shouldFail(done, 'Uncaught (in promise): Failed to load non-existant.html') + ); + restoreJasmineIt(); + }, 10000); + + describe('using beforeEachProviders', () => { + beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]); + + beforeEach( + inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); })); + + describe('nested beforeEachProviders', () => { + + it('should fail when the injector has already been used', () => { + patchJasmineBeforeEach(); + expect(() => { + beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]); + }) + .toThrowError('beforeEachProviders was called after the injector had been used ' + + 'in a beforeEach or it block. This invalidates the test injector'); + restoreJasmineBeforeEach(); + }); + }); + }); +}); diff --git a/public/docs/_examples/testing/ts/app/bag.spec.ts b/public/docs/_examples/testing/ts/app/bag.spec.ts index f77e2c9703..bc271df4ca 100644 --- a/public/docs/_examples/testing/ts/app/bag.spec.ts +++ b/public/docs/_examples/testing/ts/app/bag.spec.ts @@ -19,7 +19,7 @@ import { beforeEach, beforeEachProviders, withProviders, describe, ddescribe, xdescribe, expect, it, iit, xit, - inject, injectAsync, fakeAsync, tick, + async, inject, fakeAsync, tick, ComponentFixture, TestComponentBuilder } from 'angular2/testing'; @@ -28,12 +28,7 @@ import { ViewMetadata } from 'angular2/core'; import { PromiseWrapper } from 'angular2/src/facade/promise'; import { XHR } from 'angular2/src/compiler/xhr'; import { XHRImpl } from 'angular2/src/platform/browser/xhr_impl'; - -/////////// Module Preparation /////////////////////// -interface Done { - (): void; - fail: (err: any) => void; -} +import { Observable } from 'rxjs/Rx'; //////// SPECS ///////////// @@ -78,16 +73,47 @@ describe('angular2 jasmine matchers', () => { }); }); -describe('using the test injector with the inject helper', () => { - it('should run normal tests', () => { expect(true).toEqual(true); }); +describe('using the async helper', () => { + let actuallyDone = false; - it('should run normal async tests', (done: Done) => { + beforeEach(() => { actuallyDone = false; }); + + afterEach(() => { expect(actuallyDone).toEqual(true); }); + + it('should run normal test', () => { actuallyDone = true; }); + + it('should run normal async test', (done: DoneFn) => { setTimeout(() => { - expect(true).toEqual(true); + actuallyDone = true; done(); }, 0); }); + it('should run async test with task', + async(() => { setTimeout(() => { actuallyDone = true; }, 0); })); + + it('should run async test with successful promise', async(() => { + let p = new Promise(resolve => { setTimeout(resolve, 10); }); + p.then(() => { actuallyDone = true; }); + })); + + it('should run async test with failed promise', async(() => { + let p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); + p.catch(() => { actuallyDone = true; }); + })); + + it('should run async test with successful Observable', async(() => { + let source = Observable.of(true).delay(10); + source.subscribe( + val => {}, + err => fail(err), + () => { actuallyDone = true; } // completed + ); + })); +}); + +describe('using the test injector with the inject helper', () => { + it('provides a real XHR instance', inject([XHR], (xhr: any) => { expect(xhr).toBeAnInstanceOf(XHRImpl); })); @@ -102,14 +128,33 @@ describe('using the test injector with the inject helper', () => { })); it('test should wait for FancyService.getAsyncValue', - injectAsync([FancyService], (service: FancyService) => { - return service.getAsyncValue().then( - (value) => { expect(value).toEqual('async value'); }); - })); + async(inject([FancyService], (service: FancyService) => { + service.getAsyncValue().then( + value => { expect(value).toEqual('async value'); }); + }))); + + it('test should wait for FancyService.getTimeoutValue', + async(inject([FancyService], (service: FancyService) => { + service.getTimeoutValue().then( + value => { expect(value).toEqual('timeout value'); }); + }))); + + it('test should wait for FancyService.getObservableValue', + async(inject([FancyService], (service: FancyService) => { + service.getObservableValue().subscribe( + value => { expect(value).toEqual('observable value'); } + ); + }))); + + it('test should wait for FancyService.getObservableDelayValue', + async(inject([FancyService], (service: FancyService) => { + service.getObservableDelayValue().subscribe( + value => { expect(value).toEqual('observable delay value'); } + ); + }))); - // Experimental: write async tests synchonously by faking async processing it('should allow the use of fakeAsync (Experimental)', - inject([FancyService], fakeAsync((service: FancyService) => { + fakeAsync(inject([FancyService], (service: FancyService) => { let value: any; service.getAsyncValue().then((val: any) => value = val); tick(); // Trigger JS engine cycle until all promises resolve. @@ -128,9 +173,9 @@ describe('using the test injector with the inject helper', () => { }); describe('using async within beforeEach', () => { - beforeEach(injectAsync([FancyService], (service: FancyService) => { - return service.getAsyncValue().then(value => { service.value = value; }); - })); + beforeEach(async(inject([FancyService], (service: FancyService) => { + service.getAsyncValue().then(value => { service.value = value; }); + }))); it('should use asynchronously modified value ... in synchronous test', inject([FancyService], (service: FancyService) => { @@ -152,18 +197,18 @@ describe('using the test injector with the inject helper', () => { describe('test component builder', function() { it('should instantiate a component with valid DOM', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(ChildComp).then(fixture => { + tcb.createAsync(ChildComp).then(fixture => { fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Original Child'); }); - })); + }))); it('should allow changing members of the component', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(MyIfComp).then(fixture => { + tcb.createAsync(MyIfComp).then(fixture => { fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('MyIf()'); @@ -171,12 +216,12 @@ describe('test component builder', function() { fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('MyIf(More)'); }); - })); + }))); it('should support clicking a button', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(ButtonComp).then(fixture => { + tcb.createAsync(ButtonComp).then(fixture => { let comp = fixture.componentInstance; expect(comp.wasClicked).toEqual(false, 'wasClicked should be false at start'); @@ -188,14 +233,14 @@ describe('test component builder', function() { // 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) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { let origName = 'John'; let newName = 'Sally'; - return tcb.createAsync(InputComp).then(fixture => { + tcb.createAsync(InputComp).then(fixture => { let comp = fixture.componentInstance; expect(comp.name).toEqual(origName, `At start name should be ${origName} `); @@ -212,23 +257,23 @@ describe('test component builder', function() { expect(inputBox.value).toEqual(newName, `After value change and detectChanges, name should now be ${newName} `); }); - })); + }))); it('should override a template', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideTemplate(MockChildComp, 'Mock') + tcb.overrideTemplate(MockChildComp, 'Mock') .createAsync(MockChildComp) .then(fixture => { fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Mock'); }); - })); + }))); it('should override a view', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideView( + tcb.overrideView( ChildComp, new ViewMetadata({template: 'Modified {{childBinding}}'}) ) @@ -238,25 +283,25 @@ describe('test component builder', function() { expect(fixture.nativeElement).toHaveText('Modified Child'); }); - })); + }))); it('should override component directives', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideDirective(ParentComp, ChildComp, MockChildComp) + tcb.overrideDirective(ParentComp, ChildComp, MockChildComp) .createAsync(ParentComp) .then(fixture => { fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Parent(Mock)'); }); - })); + }))); it('should override child component\'s directives', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp) + tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp) .overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp) .createAsync(ParentComp) .then(fixture => { @@ -265,12 +310,12 @@ describe('test component builder', function() { .toHaveText('Parent(Original Child(ChildChild Mock))'); }); - })); + }))); it('should override a provider', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideProviders( + tcb.overrideProviders( TestProvidersComp, [provide(FancyService, {useClass: MockFancyService})] ) @@ -280,12 +325,12 @@ describe('test component builder', function() { expect(fixture.nativeElement) .toHaveText('injected value: mocked out value'); }); - })); + }))); it('should override a viewProvider', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideViewProviders( + tcb.overrideViewProviders( TestViewProvidersComp, [provide(FancyService, {useClass: MockFancyService})] ) @@ -295,18 +340,18 @@ describe('test component builder', function() { expect(fixture.nativeElement) .toHaveText('injected value: mocked out value'); }); - })); + }))); it('should allow an external templateUrl', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(ExternalTemplateComp) + tcb.createAsync(ExternalTemplateComp) .then(fixture => { fixture.detectChanges(); expect(fixture.nativeElement) .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; @@ -344,13 +389,13 @@ describe('test component builder', function() { } // Create MyIfParentComp TCB and component instance before each test (async beforeEach) - beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(MyIfParentComp) + beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + tcb.createAsync(MyIfParentComp) .then(fix => { fixture = fix; parent = fixture.debugElement.componentInstance; }); - })); + }))); it('should instantiate parent component', () => { expect(parent).not.toBeNull('parent component should exist'); @@ -396,7 +441,7 @@ describe('test component builder', function() { 'childValue should eq changed parent value'); }); - it('changed child value flows to parent', injectAsync([], () => { + it('changed child value flows to parent', async(() => { fixture.detectChanges(); getChild(); @@ -419,24 +464,6 @@ describe('test component builder', function() { 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(); @@ -451,173 +478,6 @@ describe('test component builder', function() { }); }); -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.Spec => { - let done = () => { deferred.resolve(); }; - (done).fail = (err: any) => { deferred.reject(err); }; - fn(done); - return null; - }; - return deferred.promise; - }; - - let restoreJasmineIt = () => { jasmine.getEnv().it = originalJasmineIt; }; - - let patchJasmineBeforeEach = () => { - let deferred = PromiseWrapper.completer(); - originalJasmineBeforeEach = jasmine.getEnv().beforeEach; - jasmine.getEnv().beforeEach = (fn: any): void => { - let done = () => { deferred.resolve(); }; - (done).fail = (err: any) => { deferred.reject(err); }; - fn(done); - return null; - }; - return deferred.promise; - }; - - let restoreJasmineBeforeEach = - () => { jasmine.getEnv().beforeEach = originalJasmineBeforeEach; }; - - const shouldNotSucceed = - (done: Done) => () => done.fail( 'Expected function to throw, but it did not'); - - const shouldFail = - (done: Done, emsg: string) => (err: any) => { expect(err).toEqual(emsg); done(); }; - - it('injectAsync should fail when return was forgotten in it', (done: Done) => { - let itPromise = patchJasmineIt(); - it('forgets to return a proimse', injectAsync([], () => { return true; })); - - itPromise.then( - shouldNotSucceed(done), - shouldFail(done, - 'Error: injectAsync was expected to return a promise, but the returned value was: true') - ); - restoreJasmineIt(); - }); - - it('inject should fail if a value was returned', (done: Done) => { - let itPromise = patchJasmineIt(); - it('returns a value', inject([], () => { return true; })); - - itPromise.then( - shouldNotSucceed(done), - shouldFail(done, - 'Error: inject returned a value. Did you mean to use injectAsync? Returned value was: true') - ); - restoreJasmineIt(); - }); - - it('injectAsync should fail when return was forgotten in beforeEach', (done: Done) => { - let beforeEachPromise = patchJasmineBeforeEach(); - beforeEach(injectAsync([], () => { return true; })); - - beforeEachPromise.then( - shouldNotSucceed(done), - shouldFail(done, - 'Error: injectAsync was expected to return a promise, but the returned value was: true') - ); - restoreJasmineBeforeEach(); - }); - - it('inject should fail if a value was returned in beforeEach', (done: Done) => { - let beforeEachPromise = patchJasmineBeforeEach(); - beforeEach(inject([], () => { return true; })); - - beforeEachPromise.then( - shouldNotSucceed(done), - shouldFail(done, - 'Error: inject returned a value. Did you mean to use injectAsync? Returned value was: true') - ); - restoreJasmineBeforeEach(); - }); - - it('should fail when an error occurs inside inject', (done: Done) => { - let itPromise = patchJasmineIt(); - - it('throws an error', inject([], () => { throw new Error('foo'); })); - - itPromise.then( - shouldNotSucceed(done), - err => { expect(err.message).toEqual('foo'); done(); } - ); - restoreJasmineIt(); - }); - - // TODO(juliemr): reenable this test when we are using a test zone and can capture this error. - xit('should fail when an asynchronous error is thrown', (done: Done) => { - let itPromise = patchJasmineIt(); - - it('throws an async error', - injectAsync([], () => { setTimeout(() => { throw new Error('bar'); }, 0); })); - - itPromise.then( - shouldNotSucceed(done), - err => { expect(err.message).toEqual('bar'); done(); } - ); - restoreJasmineIt(); - }); - - it('should fail when a returned promise is rejected', (done: Done) => { - let itPromise = patchJasmineIt(); - - it('should fail with an error from a promise', injectAsync([], () => { - let deferred = PromiseWrapper.completer(); - let p = deferred.promise.then(() => { expect(1).toEqual(2); }); - - deferred.reject('baz'); - return p; - })); - - itPromise.then( - shouldNotSucceed(done), - shouldFail(done, 'baz') - ); - restoreJasmineIt(); - }); - - it('should fail when an XHR fails', (done: Done) => { - let itPromise = patchJasmineIt(); - - it('should fail with an error from a promise', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(BadTemplateUrl); - })); - - itPromise.then( - shouldNotSucceed(done), - shouldFail(done, 'Failed to load non-existant.html') - ); - restoreJasmineIt(); - }, 10000); - - describe('using beforeEachProviders', () => { - beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]); - - beforeEach( - inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); })); - - describe('nested beforeEachProviders', () => { - - it('should fail when the injector has already been used', () => { - patchJasmineBeforeEach(); - expect(() => { - beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]); - }) - .toThrowError('beforeEachProviders was called after the injector had been used ' + - 'in a beforeEach or it block. This invalidates the test injector'); - restoreJasmineBeforeEach(); - }); - }); - }); -}); - //////// Testing Framework Bugs? ///// import { HeroService } from './hero.service'; @@ -637,12 +497,12 @@ export class AnotherProvidersComp { describe('tcb.overrideProviders', () => { it('Component must have at least one provider else crash', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.overrideProviders( + tcb.overrideProviders( AnotherProvidersComp, [provide(HeroService, {useValue: {}})] ) .createAsync(AnotherProvidersComp); - })); + }))); }); diff --git a/public/docs/_examples/testing/ts/app/bag.ts b/public/docs/_examples/testing/ts/app/bag.ts index 1b861d1223..f94f33934d 100644 --- a/public/docs/_examples/testing/ts/app/bag.ts +++ b/public/docs/_examples/testing/ts/app/bag.ts @@ -3,6 +3,8 @@ import { Component, EventEmitter, Injectable, Input, Output, OnInit, OnChanges, OnDestroy, SimpleChange } from 'angular2/core'; +import { Observable } from 'rxjs/Rx'; + // Let TypeScript know about the special SystemJS __moduleName variable declare var __moduleName: string; @@ -21,7 +23,16 @@ if (!__moduleName) { @Injectable() export class FancyService { value: string = 'real value'; + getAsyncValue() { return Promise.resolve('async value'); } + + getObservableValue() { return Observable.of('observable value'); } + + getTimeoutValue() { + return new Promise((resolve, reject) => { setTimeout(() => {resolve('timeout value'); }, 10); }); + } + + getObservableDelayValue() { return Observable.of('observable delay value').delay(10); } } @Injectable() diff --git a/public/docs/_examples/testing/ts/app/dashboard.component.spec.ts b/public/docs/_examples/testing/ts/app/dashboard.component.spec.ts index 134c2ae406..a3f45c9f0b 100644 --- a/public/docs/_examples/testing/ts/app/dashboard.component.spec.ts +++ b/public/docs/_examples/testing/ts/app/dashboard.component.spec.ts @@ -8,18 +8,12 @@ import { beforeEach, beforeEachProviders, describe, ddescribe, xdescribe, expect, it, iit, xit, - inject, injectAsync, - TestComponentBuilder + async, inject, 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 /////// @@ -45,7 +39,7 @@ describe('DashboardComponent', () => { 'should not have heroes until service promise resolves'); }); - it('should HAVE heroes after HeroService gets them', (done: Done) => { + it('should HAVE heroes after HeroService gets them', (done: DoneFn) => { comp.ngOnInit(); // ngOnInit -> getHeroes mockHeroService.lastPromise // the one from getHeroes .then(() => { @@ -85,55 +79,55 @@ describe('DashboardComponent', () => { }); it('can instantiate it', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(DashboardComponent); - })); + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + tcb.createAsync(DashboardComponent); + }))); it('should NOT have heroes before OnInit', - injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(DashboardComponent).then(fixture => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + 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 => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + 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) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(DashboardComponent).then(fixture => { + tcb.createAsync(DashboardComponent).then(fixture => { comp = fixture.debugElement.componentInstance; fixture.detectChanges(); // runs ngOnInit -> getHeroes - return mockHeroService.lastPromise // the one from getHeroes + 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) => { + async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { - return tcb.createAsync(DashboardComponent).then(fixture => { + tcb.createAsync(DashboardComponent).then(fixture => { comp = fixture.debugElement.componentInstance; fixture.detectChanges(); // runs ngOnInit -> getHeroes - return mockHeroService.lastPromise // the one from getHeroes + mockHeroService.lastPromise // the one from getHeroes .then(() => { // Find and examine the displayed heroes @@ -148,15 +142,15 @@ describe('DashboardComponent', () => { }); }); - })); + }))); it('should tell ROUTER to navigate by hero id', - injectAsync([TestComponentBuilder, Router], + async(inject([TestComponentBuilder, Router], (tcb: TestComponentBuilder, router: MockRouter) => { let spy = spyOn(router, 'navigate').and.callThrough(); - return tcb.createAsync(DashboardComponent).then(fixture => { + tcb.createAsync(DashboardComponent).then(fixture => { let hero: Hero = {id: 42, name: 'Abbracadabra' }; comp = fixture.debugElement.componentInstance; comp.gotoDetail(hero); @@ -166,6 +160,6 @@ describe('DashboardComponent', () => { expect(linkParams[1].id).toEqual(hero.id, 'should nav to fake hero\'s id'); }); - })); + }))); }); }); diff --git a/public/docs/_examples/testing/ts/app/http-hero.service.spec.ts b/public/docs/_examples/testing/ts/app/http-hero.service.spec.ts index 7a80c5022a..e0a14278a8 100644 --- a/public/docs/_examples/testing/ts/app/http-hero.service.spec.ts +++ b/public/docs/_examples/testing/ts/app/http-hero.service.spec.ts @@ -3,7 +3,7 @@ import { beforeEach, beforeEachProviders, withProviders, describe, ddescribe, xdescribe, expect, it, iit, xit, - inject, injectAsync, fakeAsync, TestComponentBuilder, tick + async, inject, TestComponentBuilder } from 'angular2/testing'; import { provide } from 'angular2/core'; @@ -81,45 +81,45 @@ describe('Http-HeroService (mockBackend)', () => { response = new Response(options); })); - it('should have expected fake heroes (then)', injectAsync([], () => { + it('should have expected fake heroes (then)', async(inject([], () => { backend.connections.subscribe((c: MockConnection) => c.mockRespond(response)); - return service.getHeroes().toPromise() + service.getHeroes().toPromise() // .then(() => Promise.reject('deliberate')) .then(heroes => { expect(heroes.length).toEqual(fakeHeroes.length, 'should have expected no. of heroes'); }); - })); + }))); - it('should have expected fake heroes (Observable.do)', injectAsync([], () => { + it('should have expected fake heroes (Observable.do)', async(inject([], () => { backend.connections.subscribe((c: MockConnection) => c.mockRespond(response)); - return service.getHeroes() + service.getHeroes() .do(heroes => { expect(heroes.length).toEqual(fakeHeroes.length, 'should have expected no. of heroes'); }) .toPromise(); - })); + }))); - it('should be OK returning no heroes', injectAsync([], () => { + it('should be OK returning no heroes', async(inject([], () => { let resp = new Response(new ResponseOptions({status: 200, body: {data: []}})); backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp)); - return service.getHeroes() + service.getHeroes() .do(heroes => { expect(heroes.length).toEqual(0, 'should have no heroes'); }) .toPromise(); - })); + }))); - it('should treat 404 as an Observable error', injectAsync([], () => { + it('should treat 404 as an Observable error', async(inject([], () => { let resp = new Response(new ResponseOptions({status: 404})); backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp)); - return service.getHeroes() + service.getHeroes() .do(heroes => { fail('should not respond with heroes'); }) @@ -128,6 +128,6 @@ describe('Http-HeroService (mockBackend)', () => { return Observable.of(null); // failure is the expected test result }) .toPromise(); - })); + }))); }); }); diff --git a/public/docs/_examples/wallaby.js b/public/docs/_examples/wallaby.js index 6ec91bf5bf..2f8c4371bc 100644 --- a/public/docs/_examples/wallaby.js +++ b/public/docs/_examples/wallaby.js @@ -5,13 +5,22 @@ module.exports = function () { return { files: [ - {pattern: 'node_modules/es6-shim/es6-shim.js', instrument: false}, + // System.js for module loading {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}, + + // Polyfills + {pattern: 'node_modules/es6-shim/es6-shim.js', instrument: false}, + {pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', instrument: false}, + + // Zone.js dependencies + // Note - do not include zone.js itself or long-stack-trace-zone.js` here as + // they are included already in angular2-polyfills {pattern: 'node_modules/zone.js/dist/jasmine-patch.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/async-test.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/fake-async-test.js', instrument: false}, + + // Rx.js, Angular 2 itself, and the testing library not here because loaded by systemjs {pattern: 'app/**/*+(ts|html|css)', load: false}, {pattern: 'app/**/*.spec.ts', ignore: true}