diff --git a/.gitignore b/.gitignore index 123ae94..e526281 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules + +# Compiled output +built/ + +# Typescript typings +typings/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..07fe76f --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +## Intro + +This repository is an example Angular2 application, with a focus on showing how +unit tests can be written and run. + +For a full-service starter application, try [angular-cli](https://github.com/angular/angular-cli). + +## Software Prerequisites + +In order to run this seed, the following software is required + +### Git + +See [Setting Up Git](https://help.github.com/articles/set-up-git/) from the GitHub guides. + +### Node.js and npm + +Node.js and Node's package manager, npm, are used for installing dependencies, +running the build steps, and running tests. + + +## Getting Started + +Begin by cloning the repository. + +Use npm to get dependencies: + +`npm install` + +Take a look at the `src` folder. All application and test code, as well as +some configuration files, are in here. The `app` folder contains the actual +application code, written in TypeScript, as well as associated template and +css files. The `test` folder contains unit tests. + +## The Build/Test Pipeline + +To be as minimal as possible, this repo uses npm scripts for all building +and testing steps. You can see exactly what the scripts do in `package.json`. A +more complex application would probably consider using a tool such as grunt +or gulp to manage development pipelines. + +### Build + +The build step invokes the TypeScript compiler to create ES5 javascript +files and source maps from the `.ts` files. Run with: + +`npm run build` + +You can examine the configuration for the TypeScript compiler in `tsconfig.json`. +The generated files are output in the `built/` folder. + +To remove all generated files, run: + +`npm run clean`. + +### Watch + +The watch step can be run with: + +`npm run watch` + +This runs the TypeScript compiler with the additional `--watch` flag, which +sets up a persistent process that recompiles new `.js` files whenever a `.ts` +file changes. + +Run this process indefinitely in a different tab or in the background, since +the following commands will use it. + +### Serve + +To see the app, run + +`npm run serve` + +and navigate to `localhost:9090/built/index.html`. + +### Test + +We use Karma with the Jasmine test framework to run unit tests. Try them with + +`npm run test` + +This will start a persistent process which will re-run tests whenever the `.js` +compiled files are changed. If you have the watch process running, that will +trigger the tests to run whenever you change the `.ts` source files. + +You can see the Karma configuration at `karma.conf.js`. A few things are notable: + + - It grabs Angular by including the `angular2` and `testing.js` files from + `node_modules/angular2/bundles/`. + + - The compiled JavaScript files at `src/**/*.js` are served and watched but _not_ included. + This means that Karma will not run them automatically. + + - To get file imports to work correctly in Karma, we must include `systemjs` + from the node_modules folder, as well as the helper file `karma-test-shim.js`. + This shim file uses System.js to load the JavaScript files which Karma served + but did not automatically run. + \ No newline at end of file diff --git a/karma-test-shim.js b/karma-test-shim.js new file mode 100644 index 0000000..ac4c94c --- /dev/null +++ b/karma-test-shim.js @@ -0,0 +1,67 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +// // Cancel Karma's synchronous start, +// // we will call `__karma__.start()` later, once all the specs are loaded. +__karma__.loaded = function() {}; + + +System.config({ + packages: { + 'base/built/app': { + defaultExtension: false, + format: 'register', + map: Object.keys(window.__karma__.files). + filter(onlyAppFiles). + reduce(function createPathRecords(pathsMapping, appPath) { + // creates local module name mapping to global path with karma's fingerprint in path, e.g.: + // './hero.service': '/base/src/app/hero.service.js?f4523daf879cfb7310ef6242682ccf10b2041b3e' + var moduleName = appPath.replace(/^\/base\/built\/app\//, './').replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath]; + return pathsMapping; + }, {}) + + } + } +}); + +System.import('angular2/testing').then(function(testing) { + return System.import('angular2/platform/testing/browser').then(function(providers) { + testing.setBaseTestProviders(providers.TEST_BROWSER_PLATFORM_PROVIDERS, + providers.TEST_BROWSER_APPLICATION_PROVIDERS); + }); +}).then(function() { + return Promise.all( + Object.keys(window.__karma__.files) // All files served by Karma. + .filter(onlySpecFiles) + // .map(filePath2moduleName) // Normalize paths to module names. + .map(function(moduleName) { + // loads all spec files via their global module names (e.g. 'base/src/app/hero.service.spec') + return System.import(moduleName); + })); +}) +.then(function() { + __karma__.start(); +}, function(error) { + __karma__.error(error.stack || error); +}); + + +function filePath2moduleName(filePath) { + return filePath. + replace(/^\//, ''). // remove / prefix + replace(/\.\w+$/, ''); // remove suffix +} + + +function onlyAppFiles(filePath) { + return /^\/base\/built\/app\/.*\.js$/.test(filePath) +} + + +function onlySpecFiles(path) { + return /_test\.js$/.test(path); +} diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..53b8e02 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,62 @@ +module.exports = function(config) { + config.set({ + + basePath: '', + + frameworks: ['jasmine'], + + files: [ + // System.js for module loading + 'node_modules/systemjs/dist/system-polyfills.js', + 'node_modules/systemjs/dist/system.src.js', + + // Polyfills. + 'node_modules/es6-shim/es6-shim.js', + 'node_modules/angular2/bundles/angular2-polyfills.js', + + // Zone.js dependencies + // Note - do not include zone.js itself here, it is already + // included 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', + + + {pattern: 'karma-test-shim.js', included: true, watched: true}, + {pattern: 'built/test/matchers.js', included: true, watched: true}, + + // paths loaded via module imports + {pattern: 'built/**/*.js', included: false, watched: true}, + + // paths loaded via Angular's component compiler + // (these paths need to be rewritten, see proxies section) + {pattern: 'built/**/*.html', included: false, watched: true}, + {pattern: 'built/**/*.css', included: false, watched: true}, + + // paths to support debugging with source maps in dev tools + {pattern: 'src/**/*.ts', included: false, watched: false}, + {pattern: 'built/**/*.js.map', included: false, watched: false} + ], + + // proxied base paths + proxies: { + // required for component assests fetched by Angular's compiler + "/app/": "/base/built/app/" + }, + + reporters: ['progress'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1c042ba --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "ng2-test-seed", + "version": "0.0.2", + "description": "Setup seed for Angular 2 application", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/angular/ng-test-seed.git" + }, + "scripts": { + "postinstall": "typings install --ambient", + "clean": "rimraf built/", + "copy": "cp src/{index.html,styles.css} built/", + "copytemplates": "cp src/app/{*.html,*.css} built/app/", + "build": "tsc && npm run copy && npm run copytemplates", + "watch": "tsc --watch", + "serve": "http-server -p 9090 -c-1", + "test": "karma start karma.conf.js" + }, + "dependencies": { + "angular2": "2.0.0-beta.16", + "clang-format": "^1.0.35", + "es6-promise": "^3.0.2", + "es6-shim": "^0.35.0", + "reflect-metadata": "0.1.2", + "rxjs": "5.0.0-beta.2", + "systemjs": "0.19.20", + "zone.js": "0.6.12" + }, + "devDependencies": { + "http-server": "^0.8.5", + "jasmine": "2.3.2", + "karma": "^0.13.22", + "karma-chrome-launcher": "^0.2.1", + "karma-cli": "^0.0.4", + "karma-jasmine": "^0.3.6", + "rimraf": "^2.4.3", + "systemjs": "^0.19.4", + "typescript": "1.8.10", + "typings": "^0.6.6" + } +} diff --git a/src/app/app-component.ts b/src/app/app-component.ts new file mode 100644 index 0000000..fecf2d9 --- /dev/null +++ b/src/app/app-component.ts @@ -0,0 +1,17 @@ +import {Component} from 'angular2/core'; +import {GreetingComponent} from './greeting-component'; +import {BorderComponent} from './border-component'; +import {FormComponent} from './form-component'; + +@Component({ + selector: 'my-app', + template: ` + + + + + `, + directives: [GreetingComponent, BorderComponent, FormComponent] +}) +export class AppComponent { +} diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts new file mode 100644 index 0000000..35ab866 --- /dev/null +++ b/src/app/bootstrap.ts @@ -0,0 +1,7 @@ +import {bootstrap} from 'angular2/platform/browser'; +import {UserService} from './user-service'; +import {AppComponent} from './app-component'; +import {LoginService} from './login-service'; + + +bootstrap(AppComponent, [LoginService, UserService]); diff --git a/src/app/border-component.css b/src/app/border-component.css new file mode 100644 index 0000000..5d8aa41 --- /dev/null +++ b/src/app/border-component.css @@ -0,0 +1,4 @@ +div {border: 5px solid #EDF3F3; border-radius: 1em; text-align: center} +span {font-size: 10px;display: inline-block} +.inner {padding: 5px; margin: 5px} +.outer {width: 400px} diff --git a/src/app/border-component.html b/src/app/border-component.html new file mode 100644 index 0000000..0dce192 --- /dev/null +++ b/src/app/border-component.html @@ -0,0 +1,7 @@ +
+ -- {{title}} -- +
+ +
+ -- {{title}} -- +
diff --git a/src/app/border-component.ts b/src/app/border-component.ts new file mode 100644 index 0000000..840e527 --- /dev/null +++ b/src/app/border-component.ts @@ -0,0 +1,11 @@ +import {Component} from 'angular2/core'; + +@Component({ + selector: 'my-fancy-border', + templateUrl: 'app/border-component.html', + styleUrls: ['app/border-component.css'], + inputs: ['title: title'] +}) +export class BorderComponent { + title: string; +} diff --git a/src/app/form-component.html b/src/app/form-component.html new file mode 100644 index 0000000..f2e89b8 --- /dev/null +++ b/src/app/form-component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/form-component.ts b/src/app/form-component.ts new file mode 100644 index 0000000..816ea20 --- /dev/null +++ b/src/app/form-component.ts @@ -0,0 +1,39 @@ +import {Component, ViewChild, EventEmitter, Output} from 'angular2/core'; +import {FORM_DIRECTIVES, NgForm, FormBuilder, Control, ControlGroup, Validators} from 'angular2/common'; + +@Component({ + selector: `my-form`, + template: ` +
+ +
+ + `, + directives: [FORM_DIRECTIVES] +}) +export class FormComponent { + login: Control; + @ViewChild(NgForm) userForm: ControlGroup; +} + +@Component({ + selector: `my-form2`, + template: ` +
+ +
+ + `, + directives: [FORM_DIRECTIVES] +}) +export class Form2Component { + login: Control; + userForm: ControlGroup; + + constructor(fb: FormBuilder) { + this.login = fb.control(''); + this.userForm = fb.group({ + login: this.login, + }); + } +} diff --git a/src/app/greeting-component.ts b/src/app/greeting-component.ts new file mode 100644 index 0000000..5372487 --- /dev/null +++ b/src/app/greeting-component.ts @@ -0,0 +1,29 @@ +import {Component} from 'angular2/core'; +import {UserService} from './user-service'; + +@Component({ + selector: 'my-greeting', + template: ` + + +

Status: {{greeting}}

+ `, + styles :[` + input {font-family: monospace; font-size: 2em; width: 4em} + button {border: 2px solid; height: 2em} + `] +}) +export class GreetingComponent { + greeting: string = 'Enter PIN'; + pending: Promise; + + constructor(public user: UserService) { + } + + enter() { + this.greeting = 'Processing...'; + this.pending = this.user.getGreeting().then((greeting) => { + this.greeting = greeting; + }); + } +} diff --git a/src/app/login-service.ts b/src/app/login-service.ts new file mode 100644 index 0000000..d56cccb --- /dev/null +++ b/src/app/login-service.ts @@ -0,0 +1,16 @@ +import {Injectable} from "angular2/core"; + +@Injectable() +export class LoginService { + login(pin: number) { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (pin === 2015) { + resolve(true); + } else { + resolve(false); + } + }, 1000); + }); + } +} diff --git a/src/app/user-service.ts b/src/app/user-service.ts new file mode 100644 index 0000000..7d73e26 --- /dev/null +++ b/src/app/user-service.ts @@ -0,0 +1,20 @@ +import {LoginService} from './login-service'; +import {Injectable} from 'angular2/core'; + +@Injectable() +export class UserService { + pin: number = 1234; + + constructor(private _loginService: LoginService) { + } + + isValidPin() { + return (this.pin >= 0 && this.pin < 10000); + } + + getGreeting() { + return this._loginService.login(this.pin).then((success) => { + return success ? 'Welcome!': 'Login failure!'; + }); + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..dcaa16e --- /dev/null +++ b/src/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..8fa646e --- /dev/null +++ b/src/styles.css @@ -0,0 +1,4 @@ +h2 { color: #444; font-weight: lighter; } +body { margin: 2em; } +body, input[text], button { color: #888; font-family: Cambria, Georgia; } +button {padding: 0.2em; font-size: 14px} diff --git a/src/test/border-component_test.ts b/src/test/border-component_test.ts new file mode 100644 index 0000000..7377bf5 --- /dev/null +++ b/src/test/border-component_test.ts @@ -0,0 +1,42 @@ +import { + iit, + it, + ddescribe, + describe, + expect, + async, + inject, + TestComponentBuilder, + beforeEachProviders +} from 'angular2/testing'; +import { Component } from 'angular2/core'; +import { BorderComponent } from '../app/border-component'; + +@Component({ + template: '', + directives: [BorderComponent] +}) +class TestComponent { +} + +describe('greeting component', () => { + it('should wrap content', async(inject([TestComponentBuilder], (tcb) => { + tcb.overrideTemplate(TestComponent, 'Content') + .createAsync(TestComponent).then((fixture) => { + fixture.detectChanges(); + var compiled = fixture.debugElement.nativeElement; + + expect(compiled).toContainText('Content'); + }); + }))); + + it('should include a title', async(inject([TestComponentBuilder], (tcb) => { + tcb.overrideTemplate(TestComponent, '') + .createAsync(TestComponent).then((fixture) => { + fixture.detectChanges(); + var compiled = fixture.debugElement.nativeElement; + + expect(compiled).toContainText('ABC'); + }); + }))); +}); diff --git a/src/test/form-component_test.ts b/src/test/form-component_test.ts new file mode 100644 index 0000000..f46b28d --- /dev/null +++ b/src/test/form-component_test.ts @@ -0,0 +1,69 @@ +import { + iit, + it, + xit, + ddescribe, + describe, + expect, + inject, + async, + TestComponentBuilder, + beforeEach, + beforeEachProviders, + fakeAsync, + ComponentFixture, + tick +} from 'angular2/testing'; +import {By} from 'angular2/platform/common_dom'; +import { provide } from 'angular2/core'; +import { FormComponent, Form2Component} from '../app/form-component'; + +ddescribe('form components', () => { + var builder; + + beforeEach(async(inject([TestComponentBuilder], (tcb) => { + builder = tcb; + }))); + + it('should display a form to register', async(() => { + builder.createAsync(FormComponent).then((fixture: ComponentFixture) => { + fixture.detectChanges(); + // given a form + let userForm = fixture.componentInstance.userForm; + expect(userForm.value).toEqual({}); + + setTimeout(() => { + fixture.detectChanges(); + expect(userForm.value).toEqual({ login: null }); + + let login = fixture.debugElement.query(By.css('input')); + login.nativeElement.value = 'Cédric'; + login.nativeElement.dispatchEvent(new Event('input')); + + setTimeout(() => { + fixture.detectChanges(); + expect(userForm.value).toEqual({ login: 'Cédric' }); + }, 100); + }); + }); + })); + + it('should display a form 2', async(() => { + builder.createAsync(Form2Component).then((fixture: ComponentFixture) => { + fixture.detectChanges(); + // given a form + let userForm = fixture.componentInstance.userForm; + + expect(userForm.value).toEqual({ login: '' }); + + // when adding values in the form + let nativeElement = fixture.nativeElement; + nativeElement.querySelector('input').value = 'Cédric'; + nativeElement.querySelector('input').dispatchEvent(new Event('input')); + + fixture.detectChanges(); + + expect(userForm.value).toEqual({ login: 'Cédric' }); + }); + })); +}); diff --git a/src/test/greeting-component_test.ts b/src/test/greeting-component_test.ts new file mode 100644 index 0000000..2b76daa --- /dev/null +++ b/src/test/greeting-component_test.ts @@ -0,0 +1,97 @@ +import { + iit, + it, + ddescribe, + describe, + expect, + inject, + async, + TestComponentBuilder, + beforeEach, + beforeEachProviders, + fakeAsync, + tick +} from 'angular2/testing'; +import { provide } from 'angular2/core'; +import { UserService } from '../app/user-service'; +import { LoginService } from '../app/login-service'; +import { GreetingComponent } from '../app/greeting-component'; + +class MockLoginService extends LoginService { + login(pin: number) { + return Promise.resolve(true); + } +} + +describe('greeting component', () => { + var builder; + + beforeEachProviders(() => [ + provide(LoginService, {useClass: MockLoginService}), + UserService + ]); + + beforeEach(inject([TestComponentBuilder], (tcb) => { + builder = tcb; + })); + + it('should ask for PIN', async(() => { + builder.createAsync(GreetingComponent).then((fixture) => { + fixture.detectChanges(); + var compiled = fixture.debugElement.nativeElement; + + + expect(compiled).toContainText('Enter PIN'); + expect(compiled.querySelector('h3')).toHaveText('Status: Enter PIN'); + }); + })); + + it('should change greeting', async(() => { + builder.createAsync(GreetingComponent).then((fixture) => { + fixture.detectChanges(); + + fixture.debugElement.componentInstance.greeting = 'Foobar'; + + fixture.detectChanges(); + var compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h3')).toHaveText('Status: Foobar'); + }); + })); + + it('should override the template', async(() => { + builder.overrideTemplate(GreetingComponent, `{{greeting}}`) + .createAsync(GreetingComponent).then((fixture) => { + fixture.detectChanges(); + + var compiled = fixture.debugElement.nativeElement; + expect(compiled).toHaveText('Enter PIN'); + }); + })); + + it('should accept pin', async(() => { + builder.createAsync(GreetingComponent).then((fixture) => { + fixture.detectChanges(); + var compiled = fixture.debugElement.nativeElement; + compiled.querySelector('button').click(); + + fixture.debugElement.componentInstance.pending.then(() => { + fixture.detectChanges(); + expect(compiled.querySelector('h3')).toHaveText('Status: Welcome!'); + }); + }); + })); + + it('should accept pin (with fakeAsync)', fakeAsync(() => { + var fixture; + builder.createAsync(GreetingComponent).then((rootFixture) => { + fixture = rootFixture }); + tick(); + + var compiled = fixture.debugElement.nativeElement; + compiled.querySelector('button').click(); + + tick(); + fixture.detectChanges(); + expect(compiled.querySelector('h3')).toHaveText('Status: Welcome!'); + })); +}); diff --git a/src/test/matchers.ts b/src/test/matchers.ts new file mode 100644 index 0000000..4cff412 --- /dev/null +++ b/src/test/matchers.ts @@ -0,0 +1,15 @@ +beforeEach(() => { + jasmine.addMatchers({ + toContainText: function() { + return { + compare: function(actual, expectedText) { + var actualText = actual.textContent; + return { + pass: actualText.indexOf(expectedText) > -1, + get message() { return 'Expected ' + actualText + ' to contain ' + expectedText; } + }; + } + }; + } + }); +}); diff --git a/src/test/sanity_test.ts b/src/test/sanity_test.ts new file mode 100644 index 0000000..56d6d44 --- /dev/null +++ b/src/test/sanity_test.ts @@ -0,0 +1,11 @@ +describe('universal truths', () => { + it('should do math', () => { + expect(1 + 1).toEqual(2); + + expect(5).toBeGreaterThan(4); + }); + + xit('should skip this', () => { + expect(4).toEqual(40); + }); +}); diff --git a/src/test/user-service_test.ts b/src/test/user-service_test.ts new file mode 100644 index 0000000..52ff723 --- /dev/null +++ b/src/test/user-service_test.ts @@ -0,0 +1,68 @@ +import { it, iit, describe, expect, inject, async, beforeEachProviders, fakeAsync, tick } from 'angular2/testing'; +import { provide } from 'angular2/core'; +import { UserService } from '../app/user-service'; +import { LoginService } from '../app/login-service'; + + + +describe('user service', () => { + beforeEachProviders(() => [LoginService, UserService]); + + it('should validate pins', inject([UserService], (service) => { + service.pin = 12345; + expect(service.isValidPin()).toBe(false); + + service.pin = 0; + expect(service.isValidPin()).toBe(true); + + service.pin = 9999; + expect(service.isValidPin()).toBe(true); + + service.pin = -50; + expect(service.isValidPin()).toBe(false); + })); + + it('should greet when pin is wrong', async(inject([UserService], (service) => { + service.pin = 9999; + service.getGreeting().then((greeting) => { + expect(greeting).toEqual('Login failure!'); + }); + })), 3000); + + it('should greet when pin is right', async(inject([UserService], (service) => { + service.pin = 2015; + service.getGreeting().then((greeting) => { + expect(greeting).toEqual('Welcome!'); + }); + })), 3000); +}); + +class MockLoginService extends LoginService { + login(pin: number) { + return Promise.resolve(true); + } +} + +describe('with mocked login', () => { + beforeEachProviders(() => [provide(LoginService, {useClass: MockLoginService}), UserService]); + + it('should greet', async(inject([UserService], (service) => { + service.getGreeting().then((greeting) => { + expect(greeting).toEqual('Welcome!'); + }); + }))); +}); + +describe('with fake async', () => { + beforeEachProviders(() => [LoginService, UserService]); + + it('should greet (with fakeAsync)', inject([UserService], fakeAsync((service) => { + var greeting; + service.getGreeting().then((value) => { + greeting = value; + }); + + tick(2000); + expect(greeting).toEqual('Login failure!'); + }))); +}); diff --git a/src/test/using-injector_test.ts b/src/test/using-injector_test.ts new file mode 100644 index 0000000..9a1a529 --- /dev/null +++ b/src/test/using-injector_test.ts @@ -0,0 +1,16 @@ +import { + it, + describe, + expect, + inject +} from 'angular2/testing'; +import { + APP_ID +} from 'angular2/core'; + + +describe('default test injector', () => { + it('should provide default id', inject([APP_ID], (id) => { + expect(id).toBe('a'); + })); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b9a5192 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "system", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": false, + "outDir": "built/" + }, + "exclude": [ + "node_modules", + "typings/main", + "typings/main.d.ts" + ] +} diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..e0bc4a6 --- /dev/null +++ b/typings.json @@ -0,0 +1,10 @@ +{ + "dependencies": {}, + "devDependencies": {}, + "ambientDevDependencies": { + "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#26c98c8a9530c44f8c801ccc3b2057e2101187ee" + }, + "ambientDependencies": { + "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2" + } +} diff --git a/typings_manual/jasmine.d.ts b/typings_manual/jasmine.d.ts new file mode 100644 index 0000000..d185f75 --- /dev/null +++ b/typings_manual/jasmine.d.ts @@ -0,0 +1,5 @@ +declare module jasmine { + interface Matchers { + toContainText(text: string): boolean; + } + } \ No newline at end of file