diff --git a/public/docs/_examples/structural-directives/ts/index.html b/public/docs/_examples/structural-directives/ts/index.html index 3716c56686..e9e6431990 100644 --- a/public/docs/_examples/structural-directives/ts/index.html +++ b/public/docs/_examples/structural-directives/ts/index.html @@ -17,7 +17,7 @@ + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/test-helpers/dom-setup.ts.not-yet b/public/docs/_examples/testing/ts/test-helpers/dom-setup.ts.not-yet new file mode 100644 index 0000000000..5e8f6d03a7 --- /dev/null +++ b/public/docs/_examples/testing/ts/test-helpers/dom-setup.ts.not-yet @@ -0,0 +1,18 @@ +/////// MUST IMPORT AND EXECUTE BEFORE TestComponentBuilder TESTS //////////// + +// CRAZY BUG WORKAROUND: +// Must FIRST import and mention something (anything?) from angular +// else this file hangs systemjs for almost a minute +import { bind } from 'angular2/angular2'; +function noop() { return bind; } + +/////// THIS SECTION REALLY SHOULD BE EXECUTED FOR US BY ANGULAR //////////// +// should be in `angular2/test` or `angular2/angular2` but it isn't yet +import {BrowserDomAdapter} from 'angular2/src/core/dom/browser_adapter'; + +if (BrowserDomAdapter) { + // MUST be called before any specs involving the TestComponentBuilder + BrowserDomAdapter.makeCurrent(); +} else { + console.log("BrowserDomAdapter not found; TestComponentBuilder tests will fail"); +} diff --git a/public/docs/_examples/testing/ts/test-helpers/test-helpers.ts.not-yet b/public/docs/_examples/testing/ts/test-helpers/test-helpers.ts.not-yet new file mode 100644 index 0000000000..e39f4ae8ee --- /dev/null +++ b/public/docs/_examples/testing/ts/test-helpers/test-helpers.ts.not-yet @@ -0,0 +1,103 @@ +import {FunctionWithParamTokens, injectAsync,RootTestComponent, TestComponentBuilder} from 'angular2/testing'; +import {By} from 'angular2/angular2' + +///////// Should be in testing ///////// + +export type DoneFn = { + fail: (err?:any) => void, + (done?:any): () => void +} + +///////// injectAsync extensions /// + +type PromiseLikeTestFn = (...args:any[]) => PromiseLike; +type PromiseLikeTcbTestFn = (tcb: TestComponentBuilder, ...args:any[]) => PromiseLike; + +/** Run an async component test within Angular test bed using TestComponentBuilder +// Example +// it('async Component test', tcb => { +// // your test here +// // your test here +// // your test here +// return aPromise; +// }); +// +// May precede the test fn with some injectables which will be passed as args AFTER the TestComponentBuilder +// Example: +// it('async Component test w/ injectables', [HeroService], (tcb, service:HeroService) => { +// // your test here +// return aPromise; +// }); +*/ +export function injectTcb(testFn: (tcb: TestComponentBuilder) => PromiseLike): FunctionWithParamTokens; +export function injectTcb(dependencies: any[], testFn: PromiseLikeTcbTestFn): FunctionWithParamTokens; +export function injectTcb(dependencies: any[] | PromiseLikeTcbTestFn, testFn?: PromiseLikeTcbTestFn) { + + if (typeof dependencies === 'function' ){ + testFn = dependencies; + dependencies = []; + } + + return injectAsync([TestComponentBuilder, ...(dependencies)], testFn); +} +///////// inspectors and expectations ///////// + +export function getSelectedHtml(rootTC: RootTestComponent, selector: string) { + var debugElement = rootTC.debugElement.query(By.css(selector)); + return debugElement && debugElement.nativeElement && debugElement.nativeElement.innerHTML; +} + +export function expectSelectedHtml(rootTC: RootTestComponent, selector: string) { + return expect(getSelectedHtml(rootTC, selector)); +} + +export function getSelectedClassName(rootTC: RootTestComponent, selector: string) { + var debugElement = rootTC.debugElement.query(By.css(selector)); + return debugElement && debugElement.nativeElement && debugElement.nativeElement.className; +} + +export function expectSelectedClassName(rootTC: RootTestComponent, selector: string) { + return expect(getSelectedClassName(rootTC, selector)); +} + +export function getViewChildHtml(rootTC: RootTestComponent, elIndex: number = 0) { + let child = rootTC.debugElement.componentViewChildren[elIndex]; + return child && child.nativeElement && child.nativeElement.innerHTML +} + +export function expectViewChildHtml(rootTC: RootTestComponent, elIndex: number = 0) { + return expect(getViewChildHtml(rootTC, elIndex)); +} + +export function expectViewChildClass(rootTC: RootTestComponent, elIndex: number = 0) { + let child = rootTC.debugElement.componentViewChildren[elIndex]; + return expect(child && child.nativeElement && child.nativeElement.className); +} + +export function dispatchEvent(element: Element, eventType: string) { + element.dispatchEvent(new Event(eventType)); +} + +/** Let time pass so that DOM or Ng can react +// returns a promise that returns ("passes through") +// the value resolved in the previous `then` (if any) +// after delaying for [millis] which is zero by default. +// Example (passing along the rootTC w/ no delay): +// ... +// return rootTC; // optional +// }) +// .then(tick) +// .then(rootTC:RTC => { .. do something ..}); +// +// Example (passing along nothing in particular w/ 10ms delay): +// ... +// // don't care if it returns something or not +// }) +// .then(_ => tick(_, 10)) // ten milliseconds pass +// .then(() => { .. do something ..}); +*/ +export function tick(passThru?: any, millis: number = 0){ + return new Promise((resolve, reject) =>{ + setTimeout(() => resolve(passThru), millis); + }); +} \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/tsconfig.1.json b/public/docs/_examples/testing/ts/tsconfig.1.json new file mode 100644 index 0000000000..6ee6719cb3 --- /dev/null +++ b/public/docs/_examples/testing/ts/tsconfig.1.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + }, + "exclude": [ + "node_modules", + "typings/main", + "typings/main.d.ts" + ] +} diff --git a/public/docs/_examples/testing/ts/unit-tests-0.html b/public/docs/_examples/testing/ts/unit-tests-0.html new file mode 100644 index 0000000000..fd73c1d2e0 --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-0.html @@ -0,0 +1,28 @@ + + + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-1.html b/public/docs/_examples/testing/ts/unit-tests-1.html new file mode 100644 index 0000000000..90b8335bd2 --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-1.html @@ -0,0 +1,21 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-2.html b/public/docs/_examples/testing/ts/unit-tests-2.html new file mode 100644 index 0000000000..c7e22db334 --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-2.html @@ -0,0 +1,27 @@ + + + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-3.html b/public/docs/_examples/testing/ts/unit-tests-3.html new file mode 100644 index 0000000000..708f8d0e94 --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-3.html @@ -0,0 +1,42 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-4.html b/public/docs/_examples/testing/ts/unit-tests-4.html new file mode 100644 index 0000000000..fffc849ea0 --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-4.html @@ -0,0 +1,52 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/unit-tests-5.html.not-yet b/public/docs/_examples/testing/ts/unit-tests-5.html.not-yet new file mode 100644 index 0000000000..df8e3704ba --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-5.html.not-yet @@ -0,0 +1,44 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet b/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet new file mode 100644 index 0000000000..d5449711ee --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests-6.html.not-yet @@ -0,0 +1,46 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/unit-tests.html.not-yet b/public/docs/_examples/testing/ts/unit-tests.html.not-yet new file mode 100644 index 0000000000..f1b8ab444d --- /dev/null +++ b/public/docs/_examples/testing/ts/unit-tests.html.not-yet @@ -0,0 +1,57 @@ + + + + + + Ng App Unit Tests + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/docs/ts/latest/guide/testing.jade b/public/docs/ts/latest/guide/testing.jade index cc5405a388..848eaf1b8f 100644 --- a/public/docs/ts/latest/guide/testing.jade +++ b/public/docs/ts/latest/guide/testing.jade @@ -4,14 +4,6 @@ 1. They **guard** against breaking existing code (“regressions”) when we make changes. 1. They **clarify** what the code does both when used as intended and when faced with deviant conditions. 1. They **reveal** mistakes in design and implementation. Tests force us to look at our code from many angles. When a part of our application seems hard to test, we may have discovered a design flaw, something we can cure now rather than later when it becomes expensive to fix. - -.alert.is-important - :marked - These testing chapters were written before the Angular 2 Beta release - and are scheduled for significant updates. - Much of the material remains accurate and relevant but references to - specific features of Angular 2 and the Angular 2 testing library - may not be correct. Please bear with us. a(id="top") :marked @@ -101,4 +93,4 @@ a(href="#top").to-top Back to top .alert.is-important :marked The testing chapter is still under development. - Please bear with us as we both update and complete it. \ No newline at end of file + Please bear with us as we both update and complete it. diff --git a/public/docs/ts/latest/testing/first-app-tests.jade b/public/docs/ts/latest/testing/first-app-tests.jade index f9461efa18..faa57bbf73 100644 --- a/public/docs/ts/latest/testing/first-app-tests.jade +++ b/public/docs/ts/latest/testing/first-app-tests.jade @@ -26,24 +26,10 @@ include ../_util-fns Locate the `src` folder that contains the application `index.html` Create a new, sibling HTML file, ** `unit-tests.html` ** and copy over the same basic material from the `unit-tests.html` in the [Jasmine 101](./jasmine-testing-101.html) chapter. + ++makeExample('testing/ts/unit-tests-2.html', 'test-runner-base', 'unit-tests.html') - ``` - - - 1st Jasmine Tests - - - - - - - - - - - - ``` - +:marked We're picking up right where we left off. All we've done is change the title. .l-main-section @@ -74,58 +60,16 @@ pre.prettyprint.lang-bash :marked ## First app tests - Believe it or not … we could start testing *some* of our app right away. For example, we can test the `Hero` class: - ``` - let nextId = 30; - - export class Hero { - constructor( - public id?: number, - public name?: string, - public power?: string, - public alterEgo?: string - ) { - this.id = id || nextId++; - } - - clone() { return Hero.clone(this); } - - static clone = (h:any) => new Hero(h.id, h.name, h.alterEgo, h.power); - - static setNextId = (next:number) => nextId = next; - } - ``` - - Let's add a couple of simple tests in the `` element. - - First, we'll load the JavaScript file that defines the `Hero` class. - -code-example(format="" language="html"). - <!-- load the application's Hero definition --> - <script src="app/hero.js"></script> + We can start testing *some* of our app right away. For example, we can test the `Hero` class: ++makeExample('testing/ts/app/hero.ts') + :marked - Next, we'll add an inline script element with the `Hero`tests themselves + Let's add a couple of simple tests in a new file. - ``` - - ``` ++makeExample('testing/ts/app/hero.spec.ts', 'base-hero-spec') +:marked That's the basic Jasmine we learned back in "Jasmine 101". Notice that we surrounded our tests with ** `describe('Hero')` **. @@ -133,32 +77,8 @@ code-example(format="" language="html"). **By convention, our test always begin with a `describe` that identifies the application part under test.** The description should be sufficient to identify the tested application part and its source file. Almost any convention will do as long as you and your team follow it consistently and are never confused. - -.l-main-section -:marked - ## Run the tests - - Open one terminal window and run the watching compiler command: `npm run tsc` - - Open another terminal window and run live-server: `npm test` - - The browser should launch and display the two passing tests: - -figure.image-display - img(src='/resources/images/devguide/first-app-tests/passed-2-specs-0-failures.png' style="width:400px;" alt="Two passing tests") - - -.l-main-section -:marked - ## Critique - - Is this `Hero` class even worth testing? It's essentially a property bag with almost no logic. Maybe we should have tested the cloning feature. Maybe we should have tested id generation. We didn't bother because there wasn't much to learn by doing that. - - It's more important to take note of the `//Demo only` comment in the `unit-tests.html`. - - ** We'll never write real tests in the HTML this way**. It's nice that we can write *some* of our application tests directly in the HTML. But dumping all of our tests into HTML is not sustainable and even if we didn't mind that approach, we could only test a tiny fraction of our app this way. - - We need to relocate these tests to a separate file. Let's do that next. + + But we haven't saved this test yet. .l-main-section :marked @@ -191,40 +111,23 @@ figure.image-display .alert.is-important All of our unit test files follow this .spec naming pattern. :marked - Move the tests we just wrote in`unit-tests.html` to `hero.spec.ts` and convert them from JavaScript into TypeScript: + Save the tests we just made in `hero.spec.ts`: - ``` - import {Hero} from './hero'; - - describe('Hero', () => { - - it('has name given in the constructor', () => { - let hero = new Hero(1, 'Super Cat'); - expect(hero.name).toEqual('Super Cat'); - }); - - it('has id given in the constructor', () => { - let hero = new Hero(1, 'Super Cat'); - expect(hero.id).toEqual(1); - }); - }) - - ``` ++makeExample('testing/ts/app/hero.spec.ts', 'base-hero-spec') +:marked ### Import the part we're testing - During our conversion to TypeScript, we added an `import {Hero} from './hero' ` statement. + We have an `import {Hero} from './hero' ` statement. If we forgot this import, a TypeScript-aware editor would warn us, with a squiggly red underline, that it can't find the definition of the `Hero` class. - TypeScript doesn't know what a `Hero` is. It doesn't know about the script tag back in the `unit-tests.html` that loads the `hero.js` file. - ### Update unit-tests.html Next we update the `unit-tests.html` with a reference to our new `hero.spec.ts` file. Delete the inline test code. The revised pertinent HTML looks like this: -code-example(format="" language="html"). - <script src="app/hero.js"></script> - <script src="app/hero.spec.js"></script> + ++makeExample('testing/ts/unit-tests-2.html', 'load-hero-and-spec')(format=".") + :marked ### Run and Fail @@ -239,7 +142,7 @@ figure.image-display Open the browser's Developer Tools (F12, Ctrl-Shift-i). There's an error: code-example(format="" language="html"). - Uncaught ReferenceError: exports is not defined + Uncaught ReferenceError: System is not defined .l-main-section :marked @@ -250,7 +153,7 @@ code-example(format="" language="html"). It wasn't a problem until we tried to `import` the `Hero` class in our tests. Our test environment lacks support for module loading. - Apparently we can't simply load our application and test scripts like we do with 3rd party JavaScript libraries. + Apparently we can't simply load our application and test scripts like we do with 3rd party JavaScript libraries. We are committed to module loading in our application. Our app will call `import`. Our tests must do so too. @@ -265,33 +168,9 @@ code-example(format="" language="html"). These steps are all clearly visible, in exactly that order, in the following lines that replace the `` contents in `unit-tests.html`: - ``` - - - - - - - ``` ++makeExample('testing/ts/unit-tests-3.html', 'systemjs')(format=".") +:marked Look in the browser window. Our tests pass once again. figure.image-display diff --git a/public/docs/ts/latest/testing/jasmine-testing-101.jade b/public/docs/ts/latest/testing/jasmine-testing-101.jade index a2941f72ef..f5e8bc04bd 100644 --- a/public/docs/ts/latest/testing/jasmine-testing-101.jade +++ b/public/docs/ts/latest/testing/jasmine-testing-101.jade @@ -38,31 +38,15 @@ pre.prettyprint.lang-bash :marked Create a new file called`unit-tests.html` and enter the following: - ``` - - - 1st Jasmine Tests - - - - - - - - - - - - ``` ++makeExample('testing/ts/unit-tests-0.html', 'no-script', 'unit-tests.html') +:marked In the head we have three Jasmine scripts and one Jasmine css file. That’s the foundation for running any tests. We’ll write our first test with inline JavaScript inside the body tag: -code-example(format="" language="html"). - <script> - it('true is true', function(){ expect(true).toEqual(true); }); - </script> + ++makeExample('testing/ts/unit-tests-0.html', 'body')(format='.') :marked Now open `unit-tests.html` in a browser and see the Jasmine HTML test output: @@ -85,14 +69,16 @@ figure.image-display :marked The test we wrote is valid TypeScript because any JavaScript is valid TypeScript. But let’s make it more modern with an arrow function: -code-example(format="" ). - it('true is true', () => expect(true).toEqual(true)); + ++makeExample('testing/ts/1st.spec.ts', 'it', '1st.spec.ts') + :marked Now modify `unit-tests.html` to load the script: -code-example(format="" language="html"). - <script src="1st.spec.js"></script> + ++makeExample('testing/ts/unit-tests-1.html', 'script') + :marked - Hold on! We wrote a TypeScript file but we’re loading a JavaScript file? + Hold on! We wrote a TypeScript file but we’re loading a JavaScript file? That’s a reminder that we need to compile our TypeScript test files as we do our TypeScript application files. Do that next. @@ -103,23 +89,12 @@ code-example(format="" language="html"). As we’ve seen before, we first have to tell the compiler how to compile our TypeScript files with a ** `tsconfig.json` **. - We can copy one from an application we wrote previously and paste it into our src sub-folder. + We can copy one from the quickstart we wrote previously and paste it into our src sub-folder. It should look something like this: - ``` - { - "compilerOptions": { - "target": "ES5", - "module": "commonjs", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true - }, - "exclude": [ - "node_modules" - ] - } - ``` ++makeExample('testing/ts/tsconfig.1.json', null, 'tsconfig.json') + +:marked ## Compile and Run Compile in the terminal window using the npm script command @@ -157,13 +132,10 @@ pre.prettyprint.lang-bash We should wrap this test into something that identifies the file. In Jasmine that “something” is a `describe` function. Every test file should have at least one `describe` that identifies the file holding the test(s). Here’s what our revised `1st.spec.ts` looks like when wrapped in a `describe`: - ``` - describe('1st tests', () => { - it('true is true', () => expect(true).toEqual(true)); ++makeExample('testing/ts/1st.spec.ts', 'describe') - }); - ``` +:marked And here’s how the test report displays it. figure.image-display @@ -171,10 +143,9 @@ figure.image-display :marked Let’s add another Jasmine test to `1st.spec.ts` -code-example(format="" ). - it('null is not the same thing as undefined', - () => expect(null).not.toEqual(undefined) - ); + ++makeExample('testing/ts/1st.spec.ts', 'another-test')(format=".") + :marked You knew that right? Let’s prove it with this test. The browser should refresh after you paste that test, and show: diff --git a/public/docs/ts/latest/testing/testing-an-angular-pipe.jade b/public/docs/ts/latest/testing/testing-an-angular-pipe.jade index 9db9ea27d0..0702ff8723 100644 --- a/public/docs/ts/latest/testing/testing-an-angular-pipe.jade +++ b/public/docs/ts/latest/testing/testing-an-angular-pipe.jade @@ -9,25 +9,14 @@ include ../_util-fns We use it our `hero-detail.component.html` template to turn a hero name like “eeny weenie” into “Eeny Weenie” -code-example(format="." language="html" escape="html"). -

{{hero.name | initCaps}} is {{userName}}'s current super hero!

++makeExample('testing/ts/app/hero-detail.component.html', 'pipe-usage') :marked The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief: - ``` - import {Pipe, PipeTransform} from 'angular2/core'; - - @Pipe({ name: 'initCaps' }) - export class InitCapsPipe implements PipeTransform{ - transform(value: string) { - return value.toLowerCase().replace(/(?:^|\s)[a-z]/g, function(m) { - return m.toUpperCase(); - }); - } - } - ``` ++makeExample('testing/ts/app/init-caps-pipe.ts') +:marked In this chapter we will: - add the Angular 2 library to our test harness - test this custom Angular pipe class @@ -67,21 +56,18 @@ code-example(format="" language="html" escape="html"). we were going to need Angular sooner or later. That time has come. The `InitCapsPipe` depends on Angular as is clear in the first few lines: -code-example(format=""). - import {Pipe, PipeTransform} from 'angular2/core'; + ++makeExample('testing/ts/app/init-caps-pipe.ts', 'depends-on-angular')(format=".") - @Pipe({ name: 'initCaps' }) - export class InitCapsPipe implements PipeTransform { ... } :marked **Open** `unit-tests.html` **Find** the `src="../node_modules/systemjs/dist/system.src.js">` **Replace** Step #1 with these two scripts: -code-example(format="" language="html"). - <!-- #1. add the system.js and angular libraries --> - <script src="../node_modules/systemjs/dist/system.src.js"></script> - <script src="../node_modules/angular2/bundles/angular2.dev.js"></script> + ++makeExample('testing/ts/unit-tests-4.html', 'import-angular')(format=".") + :marked ## Add another spec file @@ -90,29 +76,10 @@ code-example(format="" language="html"). **Stop and restart the TypeScript compiler** to ensure we compile the new file. **Add** the following lines of rather obvious Jasmine test code - ``` - import {InitCapsPipe} from './init-caps-pipe'; - - describe('InitCapsPipe', () => { - let pipe:InitCapsPipe; - - beforeEach(() => { - pipe = new InitCapsPipe(); - }); - - it('transforms "abc" to "Abc"', () => { - expect(pipe.transform('abc')).toEqual('Abc'); - }); - - it('transforms "abc def" to "Abc Def"', () => { - expect(pipe.transform('abc def')).toEqual('Abc Def'); - }); - - it('leaves "Abc Def" unchanged', () => { - expect(pipe.transform('Abc Def')).toEqual('Abc Def'); - }); - }); - ``` + ++makeExample('testing/ts/app/init-caps-pipe.spec.ts', 'base-pipe-spec') + +:marked Note that each test is short (one line in our case). It has a clear label that accurately describes the test. And it makes exactly one expectation. @@ -135,12 +102,9 @@ code-example(format="" language="html"). Fortunately, we can create a new `Promise` that wraps both import promises and waits for both to finish loading. -code-example(format=""). - // #3. Import the spec files explicitly - Promise.all([ - System.import('app/hero.spec'), - System.import('app/init-caps-pipe.spec') - ]) + ++makeExample('testing/ts/unit-tests-4.html', 'promise-all')(format=".") + :marked Try it. The browser should refresh and show