From 7daf9ccce2f95803980f722afb6f717c9ec92fa6 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 13 Oct 2015 13:45:08 -0700 Subject: [PATCH 01/42] Add app scaffolding --- README.md | 10 +++++++ gulpfile.js | 0 karma.conf.js | 0 package.json | 23 +++++++++++++++ src/index.html | 22 +++++++++++++++ src/karma-test-shim.js | 64 ++++++++++++++++++++++++++++++++++++++++++ src/style.css | 4 +++ src/tsconfig.json | 11 ++++++++ 8 files changed, 134 insertions(+) create mode 100644 README.md create mode 100644 gulpfile.js create mode 100644 karma.conf.js create mode 100644 package.json create mode 100644 src/index.html create mode 100644 src/karma-test-shim.js create mode 100644 src/style.css create mode 100644 src/tsconfig.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0b0261 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +## Software Prerequisites + +### Git + +### Node + +### npm + +## The Build Pipeline + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..e69de29 diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..061bfd3 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "ng2-test-seed", + "version": "0.0.1", + "description": "Setup seed for Angular 2 application", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/angular/ng-test-seed.git" + }, + "scripts": { + }, + "devDependencies": { + "angular2": "2.0.0-alpha.41", + "gulp": "^3.8.8", + "jasmine": "2.3.2", + "karma": "^0.12.23", + "karma-chrome-launcher": "^0.1.4", + "karma-cli": "^0.0.4", + "karma-dart": "^0.2.8", + "karma-jasmine": "^0.3.6", + "karma-sauce-launcher": "^0.2.11" + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..99451d0 --- /dev/null +++ b/src/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + Loading... + + + + diff --git a/src/karma-test-shim.js b/src/karma-test-shim.js new file mode 100644 index 0000000..acb20d8 --- /dev/null +++ b/src/karma-test-shim.js @@ -0,0 +1,64 @@ +// 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/src/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\/src\/app\//, './').replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath] + return pathsMapping; + }, {}) + + } + } +}); + +System.import('angular2/src/core/dom/browser_adapter').then(function(browser_adapter) { + browser_adapter.BrowserDomAdapter.makeCurrent(); +}).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\/src\/app\/.*\.js$/.test(filePath) +} + + +function onlySpecFiles(path) { + return /\.spec\.js$/.test(path); +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..8fa646e --- /dev/null +++ b/src/style.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/tsconfig.json b/src/tsconfig.json new file mode 100644 index 0000000..e5ba919 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "system", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": true + } +} From 91afad567d7d65c1cb0fbae0bb3c6a9969963d0c Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 13 Oct 2015 17:15:48 -0700 Subject: [PATCH 02/42] Add application code (spoiler: it's mostly a todo app) --- src/app/app_component.html | 71 ++++++++++++++++++++++++++++++++++++++ src/app/app_component.ts | 43 +++++++++++++++++++++++ src/app/bootstrap.ts | 5 +++ src/app/data_service.ts | 47 +++++++++++++++++++++++++ src/app/red_directive.ts | 8 +++++ 5 files changed, 174 insertions(+) create mode 100644 src/app/app_component.html create mode 100644 src/app/app_component.ts create mode 100644 src/app/bootstrap.ts create mode 100644 src/app/data_service.ts create mode 100644 src/app/red_directive.ts diff --git a/src/app/app_component.html b/src/app/app_component.html new file mode 100644 index 0000000..b84b8b2 --- /dev/null +++ b/src/app/app_component.html @@ -0,0 +1,71 @@ + + +
+ + + +
+ + + +
    + +
  • + +
    + + + + + + +
    + +
    + + + +
    + +
  • +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/src/app/app_component.ts b/src/app/app_component.ts new file mode 100644 index 0000000..375f4f5 --- /dev/null +++ b/src/app/app_component.ts @@ -0,0 +1,43 @@ +import {NgFor, Component, View} from 'angular2/angular2'; +import {Store, Todo, TodoFactory} from './data_service'; +import {RedDec} from './red_directive'; + +@Component({selector: 'my-app', viewProviders: [Store, TodoFactory]}) +@View({templateUrl: 'app_component.html', directives: [NgFor, RedDec]}) +class AppComponent { + todoEdit: Todo = null; + + constructor(public todoStore: Store, public factory: TodoFactory) {} + + enterTodo(inputElement): void { + this.addTodo(inputElement.value); + inputElement.value = ''; + } + + editTodo(todo: Todo): void { this.todoEdit = todo; } + + doneEditing($event, todo: Todo): void { + var which = $event.which; + var target = $event.target; + if (which === 13) { + todo.title = target.value; + this.todoEdit = null; + } else if (which === 27) { + this.todoEdit = null; + target.value = todo.title; + } + } + + addTodo(newTitle: string): void { this.todoStore.add(this.factory.create(newTitle, false)); } + + completeMe(todo: Todo): void { todo.completed = !todo.completed; } + + deleteMe(todo: Todo): void { this.todoStore.remove(todo); } + + toggleAll($event): void { + var isComplete = $event.target.checked; + this.todoStore.list.forEach((todo: Todo) => { todo.completed = isComplete; }); + } + + clearCompleted(): void { this.todoStore.removeBy((todo: Todo) => todo.completed); } +} \ No newline at end of file diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts new file mode 100644 index 0000000..57a0ad9 --- /dev/null +++ b/src/app/bootstrap.ts @@ -0,0 +1,5 @@ +import {bootstrap} from 'angular2/angular2'; +import {HeroService} from './hero_service'; +import {AppComponent} from './app_component'; + +bootstrap(AppComponent); diff --git a/src/app/data_service.ts b/src/app/data_service.ts new file mode 100644 index 0000000..1ea60d3 --- /dev/null +++ b/src/app/data_service.ts @@ -0,0 +1,47 @@ +import {Injectable} from 'angular2/angular2'; +import {ListWrapper, Predicate} from 'angular2/src/core/facade/collection'; + +// base model for RecordStore +export class KeyModel { + constructor(public key: number) {} +} + +export class Todo extends KeyModel { + constructor(key: number, public title: string, public completed: boolean) { super(key); } +} + +@Injectable() +export class TodoFactory { + _uid: number = 0; + + nextUid(): number { return ++this._uid; } + + create(title: string, isCompleted: boolean): Todo { + return new Todo(this.nextUid(), title, isCompleted); + } +} + +// Store manages any generic item that inherits from KeyModel +@Injectable() +export class Store { + list: KeyModel[] = []; + + add(record: KeyModel): void { this.list.push(record); } + + remove(record: KeyModel): void { this._spliceOut(record); } + + removeBy(callback: Predicate): void { + var records = ListWrapper.filter(this.list, callback); + ListWrapper.removeAll(this.list, records); + } + + private _spliceOut(record: KeyModel) { + var i = this._indexFor(record); + if (i > -1) { + return ListWrapper.splice(this.list, i, 1)[0]; + } + return null; + } + + private _indexFor(record: KeyModel) { return this.list.indexOf(record); } +} diff --git a/src/app/red_directive.ts b/src/app/red_directive.ts new file mode 100644 index 0000000..4fd5dc0 --- /dev/null +++ b/src/app/red_directive.ts @@ -0,0 +1,8 @@ +import {ElementRef, Component, Directive, Injectable} from 'angular2/angular2'; + +@Directive({selector: '[red]'}) +class RedDec { + constructor(el: ElementRef, renderer: Renderer) { + renderer.setElementStyle(el, 'color', 'red'); + } +} From 387ccd5f736f4fd3c111b99e6e98e38dd4c30249 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 13 Oct 2015 17:36:01 -0700 Subject: [PATCH 03/42] Set up for using tsc --- README.md | 7 ++++ karma.conf.js | 89 ++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- src/app/app_component.ts | 10 ++--- src/app/bootstrap.ts | 1 - src/app/data_service.ts | 11 +++-- src/app/red_directive.ts | 4 +- 7 files changed, 110 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c0b0261..d6d7a54 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,10 @@ ## The Build Pipeline +For now, manual via command line: + +Use tsc to build the files: + +``` +./node_modules/.bin/tsc --outDir build -p src --watch +``` diff --git a/karma.conf.js b/karma.conf.js index e69de29..33facf6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -0,0 +1,89 @@ +// Karma configuration +// Generated on Sat Oct 03 2015 15:04:14 GMT-0700 (PDT) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // // list of files / patterns to load in the browser + files: [ + // paths loaded by Karma + {pattern: 'node_modules/systemjs/dist/system.src.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}, + {pattern: 'src/app/karma-test-shim.js', included: true, watched: true}, + + // paths loaded via module imports + {pattern: 'src/**/*.js', included: false, watched: true}, + + // paths loaded via Angular's component compiler + // (these paths need to be rewritten, see proxies section) + {pattern: 'src/**/*.html', included: false, watched: true}, + {pattern: 'src/**/*.css', included: false, watched: true}, + + // paths to support debugging with source maps in dev tools + {pattern: 'src/**/*.ts', included: false, watched: false}, + {pattern: 'src/**/*.js.map', included: false, watched: false} + ], + + + // list of files to exclude + exclude: [ + ], + + + // proxied base paths + proxies: { + // required for component assests fetched by Angular's compiler + "/app/": "/base/src/app/" + }, + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'src/**/*.js': ['sourcemap'] + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false + }) +} diff --git a/package.json b/package.json index 061bfd3..312aec4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { }, "devDependencies": { - "angular2": "2.0.0-alpha.41", + "angular2": "2.0.0-alpha.42", "gulp": "^3.8.8", "jasmine": "2.3.2", "karma": "^0.12.23", @@ -18,6 +18,6 @@ "karma-cli": "^0.0.4", "karma-dart": "^0.2.8", "karma-jasmine": "^0.3.6", - "karma-sauce-launcher": "^0.2.11" + "typescript": "1.6.2" } } diff --git a/src/app/app_component.ts b/src/app/app_component.ts index 375f4f5..aec4a42 100644 --- a/src/app/app_component.ts +++ b/src/app/app_component.ts @@ -4,19 +4,19 @@ import {RedDec} from './red_directive'; @Component({selector: 'my-app', viewProviders: [Store, TodoFactory]}) @View({templateUrl: 'app_component.html', directives: [NgFor, RedDec]}) -class AppComponent { +export class AppComponent { todoEdit: Todo = null; constructor(public todoStore: Store, public factory: TodoFactory) {} - enterTodo(inputElement): void { + enterTodo(inputElement: any): void { this.addTodo(inputElement.value); inputElement.value = ''; } editTodo(todo: Todo): void { this.todoEdit = todo; } - doneEditing($event, todo: Todo): void { + doneEditing($event: any, todo: Todo): void { var which = $event.which; var target = $event.target; if (which === 13) { @@ -34,10 +34,10 @@ class AppComponent { deleteMe(todo: Todo): void { this.todoStore.remove(todo); } - toggleAll($event): void { + toggleAll($event: any): void { var isComplete = $event.target.checked; this.todoStore.list.forEach((todo: Todo) => { todo.completed = isComplete; }); } - clearCompleted(): void { this.todoStore.removeBy((todo: Todo) => todo.completed); } + // clearCompleted(): void { this.todoStore.removeBy((todo: Todo) => todo.completed); } } \ No newline at end of file diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts index 57a0ad9..6ff88e9 100644 --- a/src/app/bootstrap.ts +++ b/src/app/bootstrap.ts @@ -1,5 +1,4 @@ import {bootstrap} from 'angular2/angular2'; -import {HeroService} from './hero_service'; import {AppComponent} from './app_component'; bootstrap(AppComponent); diff --git a/src/app/data_service.ts b/src/app/data_service.ts index 1ea60d3..89fa72a 100644 --- a/src/app/data_service.ts +++ b/src/app/data_service.ts @@ -1,5 +1,4 @@ import {Injectable} from 'angular2/angular2'; -import {ListWrapper, Predicate} from 'angular2/src/core/facade/collection'; // base model for RecordStore export class KeyModel { @@ -30,15 +29,15 @@ export class Store { remove(record: KeyModel): void { this._spliceOut(record); } - removeBy(callback: Predicate): void { - var records = ListWrapper.filter(this.list, callback); - ListWrapper.removeAll(this.list, records); - } + // removeBy(callback: any): void { + // var records = this.list.filter(callback); + // this.list.removeAll(records); + // } private _spliceOut(record: KeyModel) { var i = this._indexFor(record); if (i > -1) { - return ListWrapper.splice(this.list, i, 1)[0]; + return this.list.splice(i, 1)[0]; } return null; } diff --git a/src/app/red_directive.ts b/src/app/red_directive.ts index 4fd5dc0..d4a26ca 100644 --- a/src/app/red_directive.ts +++ b/src/app/red_directive.ts @@ -1,7 +1,7 @@ -import {ElementRef, Component, Directive, Injectable} from 'angular2/angular2'; +import {ElementRef, Directive, Renderer} from 'angular2/angular2'; @Directive({selector: '[red]'}) -class RedDec { +export class RedDec { constructor(el: ElementRef, renderer: Renderer) { renderer.setElementStyle(el, 'color', 'red'); } From 74246f27af1b06d7bb4b7305c7af9be36fab10b9 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 13 Oct 2015 17:37:04 -0700 Subject: [PATCH 04/42] Ignore build directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 123ae94..84b1449 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ 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 +build \ No newline at end of file From 3d7b414036b7f7dde7c61e9d388d031abe09314d Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 13 Oct 2015 22:10:00 -0700 Subject: [PATCH 05/42] Remove gulp for now. Use npm scripts to run stuff. --- .gitignore | 3 +- gulpfile.js | 0 package.json | 11 +- src/app/app_component.html | 2 +- src/app/app_component.ts | 2 +- src/app/base.css | 379 +++++++++++++++++++++++++++++++++++++ src/index.html | 3 +- src/tsconfig.json | 2 +- 8 files changed, 394 insertions(+), 8 deletions(-) delete mode 100644 gulpfile.js create mode 100644 src/app/base.css diff --git a/.gitignore b/.gitignore index 84b1449..18f0b34 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ build/Release node_modules # Compiled output -build \ No newline at end of file +src/app/*.js +src/app/*.js.map \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index e69de29..0000000 diff --git a/package.json b/package.json index 312aec4..d995dc6 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,23 @@ "url": "https://github.com/angular/ng-test-seed.git" }, "scripts": { + "clean": "rimraf src/app/*.js src/app/*.js.map", + "build": "tsc -p src", + "watch": "tsc -p src --watch", + "serve": "http-server -p 9090 -c-1", + "test": "karma start karma.conf.js" }, "devDependencies": { "angular2": "2.0.0-alpha.42", - "gulp": "^3.8.8", + "del": "^2.0.2", + "http-server": "^0.8.5", "jasmine": "2.3.2", "karma": "^0.12.23", "karma-chrome-launcher": "^0.1.4", "karma-cli": "^0.0.4", - "karma-dart": "^0.2.8", "karma-jasmine": "^0.3.6", + "rimraf": "^2.4.3", + "systemjs": "^0.19.4", "typescript": "1.6.2" } } diff --git a/src/app/app_component.html b/src/app/app_component.html index b84b8b2..b995080 100644 --- a/src/app/app_component.html +++ b/src/app/app_component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/app_component.ts b/src/app/app_component.ts index aec4a42..cb4872b 100644 --- a/src/app/app_component.ts +++ b/src/app/app_component.ts @@ -3,7 +3,7 @@ import {Store, Todo, TodoFactory} from './data_service'; import {RedDec} from './red_directive'; @Component({selector: 'my-app', viewProviders: [Store, TodoFactory]}) -@View({templateUrl: 'app_component.html', directives: [NgFor, RedDec]}) +@View({templateUrl: 'app/app_component.html', directives: [NgFor, RedDec]}) export class AppComponent { todoEdit: Todo = null; diff --git a/src/app/base.css b/src/app/base.css new file mode 100644 index 0000000..8abb353 --- /dev/null +++ b/src/app/base.css @@ -0,0 +1,379 @@ +@charset "utf-8"; + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +button, +input[type="checkbox"] { + outline: none; +} + +.hidden { + display: none; +} + +.visible { + display: block !important; +} + +#todoapp { + background: #fff; + margin: 130px 0 40px 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} + +#todoapp input::-webkit-input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +#todoapp input::-moz-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +#todoapp input::input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +#todoapp h1 { + position: absolute; + top: -155px; + width: 100%; + font-size: 100px; + font-weight: 100; + text-align: center; + color: rgba(175, 47, 47, 0.15); + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + -ms-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +#new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + font-weight: inherit; + line-height: 1.4em; + border: 0; + outline: none; + color: inherit; + padding: 6px; + border: 1px solid #999; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + -ms-box-sizing: border-box; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); +} + +#main { + position: relative; + z-index: 2; + border-top: 1px solid #e6e6e6; +} + +label[for='toggle-all'] { + display: none; +} + +#toggle-all { + position: absolute; + top: -55px; + left: -12px; + width: 60px; + height: 34px; + text-align: center; + border: none; /* Mobile Safari */ +} + +#toggle-all:before { + content: '❯'; + font-size: 22px; + color: #e6e6e6; + padding: 10px 27px 10px 27px; +} + +#toggle-all:checked:before { + color: #737373; +} + +#todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +#todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; +} + +#todo-list li:last-child { + border-bottom: none; +} + +#todo-list li.editing { + border-bottom: none; + padding: 0; +} + +#todo-list li.editing .edit { + display: block; + width: 506px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +#todo-list li.editing .view { + display: none; +} + +#todo-list li .toggle { + text-align: center; + width: 40px; + /* auto, since non-WebKit browsers doesn't support input styling */ + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; /* Mobile Safari */ + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; +} + +#todo-list li .toggle:after { + content: url('data:image/svg+xml;charset=utf8,'); +} + +#todo-list li .toggle:checked:after { + content: url('data:image/svg+xml;charset=utf8,'); +} + +#todo-list li label { + white-space: pre; + word-break: break-word; + padding: 15px 60px 15px 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +#todo-list li.completed label { + color: #d9d9d9; + text-decoration: line-through; +} + +#todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; +} + +#todo-list li .destroy:hover { + color: #af5b5e; +} + +#todo-list li .destroy:after { + content: '×'; +} + +#todo-list li:hover .destroy { + display: block; +} + +#todo-list li .edit { + display: none; +} + +#todo-list li.editing:last-child { + margin-bottom: -1px; +} + +#footer { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +#footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), + 0 8px 0 -3px #f6f6f6, + 0 9px 1px -3px rgba(0, 0, 0, 0.2), + 0 16px 0 -6px #f6f6f6, + 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +#todo-count { + float: left; + text-align: left; +} + +#todo-count strong { + font-weight: 300; +} + +#filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +#filters li { + display: inline; +} + +#filters li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +#filters li a.selected, +#filters li a:hover { + border-color: rgba(175, 47, 47, 0.1); +} + +#filters li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} + +#clear-completed, +html #clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; + visibility: hidden; + position: relative; +} + +#clear-completed::after { + visibility: visible; + content: 'Clear completed'; + position: absolute; + right: 0; + white-space: nowrap; +} + +#clear-completed:hover::after { + text-decoration: underline; +} + +#info { + margin: 65px auto 0; + color: #bfbfbf; + font-size: 10px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-align: center; +} + +#info p { + line-height: 1; +} + +#info a { + color: inherit; + text-decoration: none; + font-weight: 400; +} + +#info a:hover { + text-decoration: underline; +} + +/* +Hack to remove background from Mobile Safari. +Can't use it globally since it destroys checkboxes in Firefox +*/ +@media screen and (-webkit-min-device-pixel-ratio:0) { + #toggle-all, + #todo-list li .toggle { + background: none; + } + + #todo-list li .toggle { + height: 40px; + } + + #toggle-all { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-appearance: none; + appearance: none; + } +} + +@media (max-width: 430px) { + #footer { + height: 50px; + } + + #filters { + bottom: 10px; + } +} diff --git a/src/index.html b/src/index.html index 99451d0..964761b 100644 --- a/src/index.html +++ b/src/index.html @@ -3,10 +3,9 @@ - + - diff --git a/src/tsconfig.json b/src/tsconfig.json index e5ba919..7db3b34 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -6,6 +6,6 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, - "noImplicitAny": true + "noImplicitAny": false } } From 9867e781cb9047c74427eda65c0b0e645153c801 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Wed, 14 Oct 2015 00:06:36 -0700 Subject: [PATCH 06/42] Add some tests and get them running --- .gitignore | 4 +++- README.md | 3 +++ karma.conf.js | 22 +++------------------- src/app/data_service.ts | 8 ++++---- src/karma-test-shim.js | 5 +++-- src/test/app_component_test.ts | 22 ++++++++++++++++++++++ src/test/data_service_test.ts | 25 +++++++++++++++++++++++++ 7 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 src/test/app_component_test.ts create mode 100644 src/test/data_service_test.ts diff --git a/.gitignore b/.gitignore index 18f0b34..c3ddf07 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ node_modules # Compiled output src/app/*.js -src/app/*.js.map \ No newline at end of file +src/app/*.map +src/test/*.js +src/test/*.map diff --git a/README.md b/README.md index d6d7a54..d389ac1 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,6 @@ Use tsc to build the files: ``` ./node_modules/.bin/tsc --outDir build -p src --watch ``` + +(Note: We use `module: system` instead of `commonjs`, but it makes tsc complain +about not being able to find the module `angular2`. Look into why this is.) \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 33facf6..b94f623 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -7,19 +7,17 @@ module.exports = function(config) { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', - // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], - // // list of files / patterns to load in the browser files: [ // paths loaded by Karma {pattern: 'node_modules/systemjs/dist/system.src.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}, - {pattern: 'src/app/karma-test-shim.js', included: true, watched: true}, + {pattern: 'node_modules/angular2/bundles/testing.js', included: true, watched: true}, + {pattern: 'src/karma-test-shim.js', included: true, watched: true}, // paths loaded via module imports {pattern: 'src/**/*.js', included: false, watched: true}, @@ -34,26 +32,12 @@ module.exports = function(config) { {pattern: 'src/**/*.js.map', included: false, watched: false} ], - - // list of files to exclude - exclude: [ - ], - - // proxied base paths proxies: { // required for component assests fetched by Angular's compiler "/app/": "/base/src/app/" }, - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'src/**/*.js': ['sourcemap'] - }, - - // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter @@ -70,7 +54,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, + logLevel: config.LOG_DEBUG, // enable / disable watching file and executing tests whenever any file changes diff --git a/src/app/data_service.ts b/src/app/data_service.ts index 89fa72a..cbf5c5b 100644 --- a/src/app/data_service.ts +++ b/src/app/data_service.ts @@ -29,10 +29,10 @@ export class Store { remove(record: KeyModel): void { this._spliceOut(record); } - // removeBy(callback: any): void { - // var records = this.list.filter(callback); - // this.list.removeAll(records); - // } + removeBy(callback: any): void { + var records = this.list.filter(callback); + this.list.removeAll(records); + } private _spliceOut(record: KeyModel) { var i = this._indexFor(record); diff --git a/src/karma-test-shim.js b/src/karma-test-shim.js index acb20d8..add673d 100644 --- a/src/karma-test-shim.js +++ b/src/karma-test-shim.js @@ -34,8 +34,9 @@ System.import('angular2/src/core/dom/browser_adapter').then(function(browser_ada return Promise.all( Object.keys(window.__karma__.files) // All files served by Karma. .filter(onlySpecFiles) - .map(filePath2moduleName) // Normalize paths to module names. + // .map(filePath2moduleName) // Normalize paths to module names. .map(function(moduleName) { + console.log('loading module: ' + moduleName); // loads all spec files via their global module names (e.g. 'base/src/app/hero.service.spec') return System.import(moduleName); })); @@ -60,5 +61,5 @@ function onlyAppFiles(filePath) { function onlySpecFiles(path) { - return /\.spec\.js$/.test(path); + return /_test\.js$/.test(path); } diff --git a/src/test/app_component_test.ts b/src/test/app_component_test.ts new file mode 100644 index 0000000..47241dd --- /dev/null +++ b/src/test/app_component_test.ts @@ -0,0 +1,22 @@ +import {AppComponent} from '../app/app_component'; +import { + it, xit, iit, describe, ddescribe, xdescribe, expect, + inject, injectAsync, beforeEachProviders, + TestComponentBuilder, RootTestComponent +} from 'angular2/testing'; +import {bind, DebugElement} from 'angular2/angular2'; +import {XHR} from 'angular2/src/core/compiler/xhr'; +import {XHRImpl} from 'angular2/src/core/compiler/xhr_impl'; + +describe('Todo App', () => { + beforeEachProviders(() => { + return [bind(XHR).toClass(XHRImpl)]; + }); + + it('should initialize', injectAsync([TestComponentBuilder], (tcb) => { + return tcb.createAsync(AppComponent).then((fixture) => { + fixture.detectChanges(); + expect(true).toEqual(false); + }); + })); +}); diff --git a/src/test/data_service_test.ts b/src/test/data_service_test.ts new file mode 100644 index 0000000..8bd5d2d --- /dev/null +++ b/src/test/data_service_test.ts @@ -0,0 +1,25 @@ +import {Store, Todo, TodoFactory} from '../app/data_service'; +import {it, xit, iit, describe, ddescribe, xdescribe, expect, inject, beforeEachProviders} from 'angular2/testing'; +import {bind} from 'angular2/angular2'; + +describe('store', () => { + it('should add items', () => { + var factory = new TodoFactory(); + var store = new Store(); + + store.add(factory.create('one', false)); + store.add(factory.create('two', true)); + + expect(store.list.length).toEqual(2); + }); + + beforeEachProviders(() => [ + bind(TodoFactory).toValue(new TodoFactory()), + bind(Store).toValue(new Store()) + ]); + + it('should use the injector', inject([TodoFactory, Store], (factory, store) => { + store.add(factory.create('one', false)); + expect(store.list.length).toEqual(1); + })); +}); From 84a8e5c49420aaa19e4e9cdd51f75c7f8d8c1e61 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Wed, 14 Oct 2015 00:25:18 -0700 Subject: [PATCH 07/42] Improve the readme, part I --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d389ac1..ceab9ea 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,77 @@ +## Intro + +This repository is a seed Angular2 application, with a focus on showing how +unit tests can be written and run. + ## Software Prerequisites +In order to run this seed, the following software is required + ### Git -### Node +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. + -### npm +## Getting Started -## The Build Pipeline +Begin by cloning the repository. -For now, manual via command line: +Run `npm install` to get dependencies. -Use tsc to build the files: +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. -``` -./node_modules/.bin/tsc --outDir build -p src --watch -``` +## 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 `app/tsconfig.json`. +The generated files are output in the same folder as their sources. (Note: We use `module: system` instead of `commonjs`, but it makes tsc complain -about not being able to find the module `angular2`. Look into why this is.) \ No newline at end of file +about not being able to find the module `angular2`. TODO: Look into why this is.) + +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 causes +it to recomiple whenever a `.ts` file changes. + +Run this 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/src/index.html`. + +### Test + +`npm run test` From 05ef5ec1ab2cae47d447d66c25e27e3d5e996572 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Wed, 14 Oct 2015 00:37:42 -0700 Subject: [PATCH 08/42] Add description of what's going on with the test to README --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ceab9ea..54fb6fc 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ The watch step can be run with: This runs the TypeScript compiler with the additional `--watch` flag, which causes it to recomiple whenever a `.ts` file changes. -Run this in a different tab or in the background, since the following commands -will use it. +Run this process indefinitely in a different tab or in the background, since +the following commands will use it. ### Serve @@ -74,4 +74,24 @@ and navigate to `localhost:9090/src/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 Kamra, we must include `systemjs` + from the node_modules folder, as well as the helper file `src/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 From 4ab7454239722693ad5727f478a0ef5321927649 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 15 Oct 2015 12:28:07 -0700 Subject: [PATCH 09/42] Move non-app files out of src folder --- README.md | 4 ++-- src/karma-test-shim.js => karma-test-shim.js | 0 karma.conf.js | 2 +- package.json | 4 ++-- src/tsconfig.json => tsconfig.json | 5 ++++- 5 files changed, 9 insertions(+), 6 deletions(-) rename src/karma-test-shim.js => karma-test-shim.js (100%) rename src/tsconfig.json => tsconfig.json (83%) diff --git a/README.md b/README.md index 54fb6fc..aebf3de 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ files and source maps from the `.ts` files. Run with: `npm run build` -You can examine the configuration for the TypeScript compiler in `app/tsconfig.json`. +You can examine the configuration for the TypeScript compiler in `tsconfig.json`. The generated files are output in the same folder as their sources. (Note: We use `module: system` instead of `commonjs`, but it makes tsc complain @@ -91,7 +91,7 @@ You can see the Karma configuration at `karma.conf.js`. A few things are notable This means that Karma will not run them automatically. - To get file imports to work correctly in Kamra, we must include `systemjs` - from the node_modules folder, as well as the helper file `src/karma-test-shim.js`. + 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/src/karma-test-shim.js b/karma-test-shim.js similarity index 100% rename from src/karma-test-shim.js rename to karma-test-shim.js diff --git a/karma.conf.js b/karma.conf.js index b94f623..6f15b13 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -17,7 +17,7 @@ module.exports = function(config) { {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true}, {pattern: 'node_modules/angular2/bundles/angular2.js', included: true, watched: true}, {pattern: 'node_modules/angular2/bundles/testing.js', included: true, watched: true}, - {pattern: 'src/karma-test-shim.js', included: true, watched: true}, + {pattern: 'karma-test-shim.js', included: true, watched: true}, // paths loaded via module imports {pattern: 'src/**/*.js', included: false, watched: true}, diff --git a/package.json b/package.json index d995dc6..3791dc0 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ }, "scripts": { "clean": "rimraf src/app/*.js src/app/*.js.map", - "build": "tsc -p src", - "watch": "tsc -p src --watch", + "build": "tsc", + "watch": "tsc --watch", "serve": "http-server -p 9090 -c-1", "test": "karma start karma.conf.js" }, diff --git a/src/tsconfig.json b/tsconfig.json similarity index 83% rename from src/tsconfig.json rename to tsconfig.json index 7db3b34..c06059f 100644 --- a/src/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,8 @@ "experimentalDecorators": true, "removeComments": false, "noImplicitAny": false - } + }, + "exclude": [ + "node_modules" + ] } From 81afb23d0351cf753a4485afc0803e42ffb8f127 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 15 Oct 2015 12:29:53 -0700 Subject: [PATCH 10/42] README typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aebf3de..f03765b 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ You can see the Karma configuration at `karma.conf.js`. A few things are notable - 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 Kamra, we must include `systemjs` + - 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. From d4486cb4a7fb836a7d820e26305aba3c30a1d3f1 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 15 Oct 2015 12:38:44 -0700 Subject: [PATCH 11/42] Remove old source --- src/app/app_component.html | 71 ------ src/app/app_component.ts | 43 ---- src/app/base.css | 379 --------------------------------- src/app/bootstrap.ts | 4 - src/app/data_service.ts | 46 ---- src/app/red_directive.ts | 8 - src/index.html | 21 -- src/style.css | 4 - src/test/app_component_test.ts | 22 -- src/test/data_service_test.ts | 25 --- 10 files changed, 623 deletions(-) delete mode 100644 src/app/app_component.html delete mode 100644 src/app/app_component.ts delete mode 100644 src/app/base.css delete mode 100644 src/app/bootstrap.ts delete mode 100644 src/app/data_service.ts delete mode 100644 src/app/red_directive.ts delete mode 100644 src/index.html delete mode 100644 src/style.css delete mode 100644 src/test/app_component_test.ts delete mode 100644 src/test/data_service_test.ts diff --git a/src/app/app_component.html b/src/app/app_component.html deleted file mode 100644 index b995080..0000000 --- a/src/app/app_component.html +++ /dev/null @@ -1,71 +0,0 @@ - - -
- - - -
- - - -
    - -
  • - -
    - - - - - - -
    - -
    - - - -
    - -
  • -
- -
- - - -
- - \ No newline at end of file diff --git a/src/app/app_component.ts b/src/app/app_component.ts deleted file mode 100644 index cb4872b..0000000 --- a/src/app/app_component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {NgFor, Component, View} from 'angular2/angular2'; -import {Store, Todo, TodoFactory} from './data_service'; -import {RedDec} from './red_directive'; - -@Component({selector: 'my-app', viewProviders: [Store, TodoFactory]}) -@View({templateUrl: 'app/app_component.html', directives: [NgFor, RedDec]}) -export class AppComponent { - todoEdit: Todo = null; - - constructor(public todoStore: Store, public factory: TodoFactory) {} - - enterTodo(inputElement: any): void { - this.addTodo(inputElement.value); - inputElement.value = ''; - } - - editTodo(todo: Todo): void { this.todoEdit = todo; } - - doneEditing($event: any, todo: Todo): void { - var which = $event.which; - var target = $event.target; - if (which === 13) { - todo.title = target.value; - this.todoEdit = null; - } else if (which === 27) { - this.todoEdit = null; - target.value = todo.title; - } - } - - addTodo(newTitle: string): void { this.todoStore.add(this.factory.create(newTitle, false)); } - - completeMe(todo: Todo): void { todo.completed = !todo.completed; } - - deleteMe(todo: Todo): void { this.todoStore.remove(todo); } - - toggleAll($event: any): void { - var isComplete = $event.target.checked; - this.todoStore.list.forEach((todo: Todo) => { todo.completed = isComplete; }); - } - - // clearCompleted(): void { this.todoStore.removeBy((todo: Todo) => todo.completed); } -} \ No newline at end of file diff --git a/src/app/base.css b/src/app/base.css deleted file mode 100644 index 8abb353..0000000 --- a/src/app/base.css +++ /dev/null @@ -1,379 +0,0 @@ -@charset "utf-8"; - -button { - margin: 0; - padding: 0; - border: 0; - background: none; - font-size: 100%; - vertical-align: baseline; - font-family: inherit; - font-weight: inherit; - color: inherit; - -webkit-appearance: none; - -ms-appearance: none; - appearance: none; - -webkit-font-smoothing: antialiased; - -moz-font-smoothing: antialiased; - -ms-font-smoothing: antialiased; - font-smoothing: antialiased; -} - -button, -input[type="checkbox"] { - outline: none; -} - -.hidden { - display: none; -} - -.visible { - display: block !important; -} - -#todoapp { - background: #fff; - margin: 130px 0 40px 0; - position: relative; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), - 0 25px 50px 0 rgba(0, 0, 0, 0.1); -} - -#todoapp input::-webkit-input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -#todoapp input::-moz-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -#todoapp input::input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -#todoapp h1 { - position: absolute; - top: -155px; - width: 100%; - font-size: 100px; - font-weight: 100; - text-align: center; - color: rgba(175, 47, 47, 0.15); - -webkit-text-rendering: optimizeLegibility; - -moz-text-rendering: optimizeLegibility; - -ms-text-rendering: optimizeLegibility; - text-rendering: optimizeLegibility; -} - -#new-todo, -.edit { - position: relative; - margin: 0; - width: 100%; - font-size: 24px; - font-family: inherit; - font-weight: inherit; - line-height: 1.4em; - border: 0; - outline: none; - color: inherit; - padding: 6px; - border: 1px solid #999; - box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); - -ms-box-sizing: border-box; - box-sizing: border-box; - -webkit-font-smoothing: antialiased; - -moz-font-smoothing: antialiased; - -ms-font-smoothing: antialiased; - font-smoothing: antialiased; -} - -#new-todo { - padding: 16px 16px 16px 60px; - border: none; - background: rgba(0, 0, 0, 0.003); - box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); -} - -#main { - position: relative; - z-index: 2; - border-top: 1px solid #e6e6e6; -} - -label[for='toggle-all'] { - display: none; -} - -#toggle-all { - position: absolute; - top: -55px; - left: -12px; - width: 60px; - height: 34px; - text-align: center; - border: none; /* Mobile Safari */ -} - -#toggle-all:before { - content: '❯'; - font-size: 22px; - color: #e6e6e6; - padding: 10px 27px 10px 27px; -} - -#toggle-all:checked:before { - color: #737373; -} - -#todo-list { - margin: 0; - padding: 0; - list-style: none; -} - -#todo-list li { - position: relative; - font-size: 24px; - border-bottom: 1px solid #ededed; -} - -#todo-list li:last-child { - border-bottom: none; -} - -#todo-list li.editing { - border-bottom: none; - padding: 0; -} - -#todo-list li.editing .edit { - display: block; - width: 506px; - padding: 13px 17px 12px 17px; - margin: 0 0 0 43px; -} - -#todo-list li.editing .view { - display: none; -} - -#todo-list li .toggle { - text-align: center; - width: 40px; - /* auto, since non-WebKit browsers doesn't support input styling */ - height: auto; - position: absolute; - top: 0; - bottom: 0; - margin: auto 0; - border: none; /* Mobile Safari */ - -webkit-appearance: none; - -ms-appearance: none; - appearance: none; -} - -#todo-list li .toggle:after { - content: url('data:image/svg+xml;charset=utf8,'); -} - -#todo-list li .toggle:checked:after { - content: url('data:image/svg+xml;charset=utf8,'); -} - -#todo-list li label { - white-space: pre; - word-break: break-word; - padding: 15px 60px 15px 15px; - margin-left: 45px; - display: block; - line-height: 1.2; - transition: color 0.4s; -} - -#todo-list li.completed label { - color: #d9d9d9; - text-decoration: line-through; -} - -#todo-list li .destroy { - display: none; - position: absolute; - top: 0; - right: 10px; - bottom: 0; - width: 40px; - height: 40px; - margin: auto 0; - font-size: 30px; - color: #cc9a9a; - margin-bottom: 11px; - transition: color 0.2s ease-out; -} - -#todo-list li .destroy:hover { - color: #af5b5e; -} - -#todo-list li .destroy:after { - content: '×'; -} - -#todo-list li:hover .destroy { - display: block; -} - -#todo-list li .edit { - display: none; -} - -#todo-list li.editing:last-child { - margin-bottom: -1px; -} - -#footer { - color: #777; - padding: 10px 15px; - height: 20px; - text-align: center; - border-top: 1px solid #e6e6e6; -} - -#footer:before { - content: ''; - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 50px; - overflow: hidden; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), - 0 8px 0 -3px #f6f6f6, - 0 9px 1px -3px rgba(0, 0, 0, 0.2), - 0 16px 0 -6px #f6f6f6, - 0 17px 2px -6px rgba(0, 0, 0, 0.2); -} - -#todo-count { - float: left; - text-align: left; -} - -#todo-count strong { - font-weight: 300; -} - -#filters { - margin: 0; - padding: 0; - list-style: none; - position: absolute; - right: 0; - left: 0; -} - -#filters li { - display: inline; -} - -#filters li a { - color: inherit; - margin: 3px; - padding: 3px 7px; - text-decoration: none; - border: 1px solid transparent; - border-radius: 3px; -} - -#filters li a.selected, -#filters li a:hover { - border-color: rgba(175, 47, 47, 0.1); -} - -#filters li a.selected { - border-color: rgba(175, 47, 47, 0.2); -} - -#clear-completed, -html #clear-completed:active { - float: right; - position: relative; - line-height: 20px; - text-decoration: none; - cursor: pointer; - visibility: hidden; - position: relative; -} - -#clear-completed::after { - visibility: visible; - content: 'Clear completed'; - position: absolute; - right: 0; - white-space: nowrap; -} - -#clear-completed:hover::after { - text-decoration: underline; -} - -#info { - margin: 65px auto 0; - color: #bfbfbf; - font-size: 10px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-align: center; -} - -#info p { - line-height: 1; -} - -#info a { - color: inherit; - text-decoration: none; - font-weight: 400; -} - -#info a:hover { - text-decoration: underline; -} - -/* -Hack to remove background from Mobile Safari. -Can't use it globally since it destroys checkboxes in Firefox -*/ -@media screen and (-webkit-min-device-pixel-ratio:0) { - #toggle-all, - #todo-list li .toggle { - background: none; - } - - #todo-list li .toggle { - height: 40px; - } - - #toggle-all { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - -webkit-appearance: none; - appearance: none; - } -} - -@media (max-width: 430px) { - #footer { - height: 50px; - } - - #filters { - bottom: 10px; - } -} diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts deleted file mode 100644 index 6ff88e9..0000000 --- a/src/app/bootstrap.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {bootstrap} from 'angular2/angular2'; -import {AppComponent} from './app_component'; - -bootstrap(AppComponent); diff --git a/src/app/data_service.ts b/src/app/data_service.ts deleted file mode 100644 index cbf5c5b..0000000 --- a/src/app/data_service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {Injectable} from 'angular2/angular2'; - -// base model for RecordStore -export class KeyModel { - constructor(public key: number) {} -} - -export class Todo extends KeyModel { - constructor(key: number, public title: string, public completed: boolean) { super(key); } -} - -@Injectable() -export class TodoFactory { - _uid: number = 0; - - nextUid(): number { return ++this._uid; } - - create(title: string, isCompleted: boolean): Todo { - return new Todo(this.nextUid(), title, isCompleted); - } -} - -// Store manages any generic item that inherits from KeyModel -@Injectable() -export class Store { - list: KeyModel[] = []; - - add(record: KeyModel): void { this.list.push(record); } - - remove(record: KeyModel): void { this._spliceOut(record); } - - removeBy(callback: any): void { - var records = this.list.filter(callback); - this.list.removeAll(records); - } - - private _spliceOut(record: KeyModel) { - var i = this._indexFor(record); - if (i > -1) { - return this.list.splice(i, 1)[0]; - } - return null; - } - - private _indexFor(record: KeyModel) { return this.list.indexOf(record); } -} diff --git a/src/app/red_directive.ts b/src/app/red_directive.ts deleted file mode 100644 index d4a26ca..0000000 --- a/src/app/red_directive.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {ElementRef, Directive, Renderer} from 'angular2/angular2'; - -@Directive({selector: '[red]'}) -export class RedDec { - constructor(el: ElementRef, renderer: Renderer) { - renderer.setElementStyle(el, 'color', 'red'); - } -} diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 964761b..0000000 --- a/src/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - Loading... - - - - diff --git a/src/style.css b/src/style.css deleted file mode 100644 index 8fa646e..0000000 --- a/src/style.css +++ /dev/null @@ -1,4 +0,0 @@ -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/app_component_test.ts b/src/test/app_component_test.ts deleted file mode 100644 index 47241dd..0000000 --- a/src/test/app_component_test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {AppComponent} from '../app/app_component'; -import { - it, xit, iit, describe, ddescribe, xdescribe, expect, - inject, injectAsync, beforeEachProviders, - TestComponentBuilder, RootTestComponent -} from 'angular2/testing'; -import {bind, DebugElement} from 'angular2/angular2'; -import {XHR} from 'angular2/src/core/compiler/xhr'; -import {XHRImpl} from 'angular2/src/core/compiler/xhr_impl'; - -describe('Todo App', () => { - beforeEachProviders(() => { - return [bind(XHR).toClass(XHRImpl)]; - }); - - it('should initialize', injectAsync([TestComponentBuilder], (tcb) => { - return tcb.createAsync(AppComponent).then((fixture) => { - fixture.detectChanges(); - expect(true).toEqual(false); - }); - })); -}); diff --git a/src/test/data_service_test.ts b/src/test/data_service_test.ts deleted file mode 100644 index 8bd5d2d..0000000 --- a/src/test/data_service_test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {Store, Todo, TodoFactory} from '../app/data_service'; -import {it, xit, iit, describe, ddescribe, xdescribe, expect, inject, beforeEachProviders} from 'angular2/testing'; -import {bind} from 'angular2/angular2'; - -describe('store', () => { - it('should add items', () => { - var factory = new TodoFactory(); - var store = new Store(); - - store.add(factory.create('one', false)); - store.add(factory.create('two', true)); - - expect(store.list.length).toEqual(2); - }); - - beforeEachProviders(() => [ - bind(TodoFactory).toValue(new TodoFactory()), - bind(Store).toValue(new Store()) - ]); - - it('should use the injector', inject([TodoFactory, Store], (factory, store) => { - store.add(factory.create('one', false)); - expect(store.list.length).toEqual(1); - })); -}); From 67df10ccdaab244d0e791dd65d70d6775cd73685 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 15 Oct 2015 13:54:35 -0700 Subject: [PATCH 12/42] Switch it up to tour of heroes source --- src/app/app.component.ts | 25 +++++++++++++++++ src/app/bootstrap.ts | 6 +++++ src/app/dashboard.component.css | 37 +++++++++++++++++++++++++ src/app/dashboard.component.html | 8 ++++++ src/app/dashboard.component.ts | 34 +++++++++++++++++++++++ src/app/hero-detail.component.html | 10 +++++++ src/app/hero-detail.component.ts | 31 +++++++++++++++++++++ src/app/hero.service.ts | 15 +++++++++++ src/app/hero.ts | 4 +++ src/app/heroes.component.css | 17 ++++++++++++ src/app/heroes.component.html | 14 ++++++++++ src/app/heroes.component.ts | 43 ++++++++++++++++++++++++++++++ src/app/mock-heroes.ts | 14 ++++++++++ src/app/route.config.ts | 26 ++++++++++++++++++ src/index.html | 26 ++++++++++++++++++ src/styles.css | 4 +++ 16 files changed, 314 insertions(+) create mode 100644 src/app/app.component.ts create mode 100644 src/app/bootstrap.ts create mode 100644 src/app/dashboard.component.css create mode 100644 src/app/dashboard.component.html create mode 100644 src/app/dashboard.component.ts create mode 100644 src/app/hero-detail.component.html create mode 100644 src/app/hero-detail.component.ts create mode 100644 src/app/hero.service.ts create mode 100644 src/app/hero.ts create mode 100644 src/app/heroes.component.css create mode 100644 src/app/heroes.component.html create mode 100644 src/app/heroes.component.ts create mode 100644 src/app/mock-heroes.ts create mode 100644 src/app/route.config.ts create mode 100644 src/index.html create mode 100644 src/styles.css diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000..43a864d --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,25 @@ +import {Component, CORE_DIRECTIVES} from 'angular2/angular2'; +import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; +import {Routes, APP_ROUTES} from './route.config'; + +@Component({ + selector: 'my-app', + template: ` +

{{title}}

+ {{routes.dashboard.caption}} + {{routes.heroes.caption}} + + `, + styles: [` + .router-link {padding: 5px;text-decoration: none;} + .router-link:visited, .router-link:link {color: #444;} + .router-link:hover {color: white; background-color: #1171a3; text-decoration: none;} + .router-link.router-link-active {color: white; background-color: #52b9e9; text-decoration: none;} + `], + directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] +}) +@RouteConfig(APP_ROUTES) +export class AppComponent { + public title = 'Tour of Heroes'; + public routes = Routes; +} diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts new file mode 100644 index 0000000..098faaa --- /dev/null +++ b/src/app/bootstrap.ts @@ -0,0 +1,6 @@ +import {bootstrap} from 'angular2/angular2'; +import {ROUTER_PROVIDERS} from 'angular2/router'; +import {HeroService} from './hero.service'; +import {AppComponent} from './app.component'; + +bootstrap(AppComponent, [ROUTER_PROVIDERS, HeroService]); diff --git a/src/app/dashboard.component.css b/src/app/dashboard.component.css new file mode 100644 index 0000000..26a8f30 --- /dev/null +++ b/src/app/dashboard.component.css @@ -0,0 +1,37 @@ +[class*='col-'] { float: left; } + +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +h3 { text-align: center; margin-bottom: 0; } + +[class*='col-'] { padding-right: 20px; padding-bottom: 20px;} +[class*='col-']:last-of-type { padding-right: 0; } + +.grid { margin: 0 10em; } +.col-1-4 { width: 25%; } +.module { + padding: 20px; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #1171a3; +} + +.module:hover { background-color: #52b9e9; cursor: pointer; } + +.grid-pad { padding: 20px 0 20px 20px; } +.grid-pad > [class*='col-']:last-of-type { padding-right: 20px; } + +@media (max-width: 600px) { + .module { font-size: 10px; max-height: 75px; } +} + +@media (max-width: 1024px) { + .grid { margin: 0; } + .module { min-width: 60px; } +} diff --git a/src/app/dashboard.component.html b/src/app/dashboard.component.html new file mode 100644 index 0000000..3544fdc --- /dev/null +++ b/src/app/dashboard.component.html @@ -0,0 +1,8 @@ +

Top Heroes

+
+
+
+

{{hero.name}}

+
+
+
diff --git a/src/app/dashboard.component.ts b/src/app/dashboard.component.ts new file mode 100644 index 0000000..9a7406c --- /dev/null +++ b/src/app/dashboard.component.ts @@ -0,0 +1,34 @@ +import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; +import {Router} from 'angular2/router'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; +import {Routes} from './route.config'; + +@Component({ + selector: 'my-dashboard', + templateUrl: 'app/dashboard.component.html', + styleUrls: ['app/dashboard.component.css'], + directives: [CORE_DIRECTIVES, FORM_DIRECTIVES] +}) +export class DashboardComponent implements OnInit { + public heroes: Hero[]; + + constructor(private _heroService: HeroService, private _router: Router) { } + + onInit() { + this.heroes = this.getHeroes(); + } + + gotoDetail(hero: Hero) { + this._router.navigate([`/${Routes.detail.as}`, { id: hero.id }]); + } + + getHeroes() { + this.heroes = []; + + this._heroService.getHeroes() + .then((heroes: Hero[]) => this.heroes = heroes); + + return this.heroes; + } +} diff --git a/src/app/hero-detail.component.html b/src/app/hero-detail.component.html new file mode 100644 index 0000000..8362f78 --- /dev/null +++ b/src/app/hero-detail.component.html @@ -0,0 +1,10 @@ +
+

{{hero.name}} details!

+
+ {{hero.id}}
+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/hero-detail.component.ts b/src/app/hero-detail.component.ts new file mode 100644 index 0000000..3365646 --- /dev/null +++ b/src/app/hero-detail.component.ts @@ -0,0 +1,31 @@ +import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; +import {RouteParams, Router} from 'angular2/router'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; +import {Routes} from './route.config'; + +// Here is a comment +@Component({ + selector: 'my-hero-detail', + templateUrl: 'app/hero-detail.component.html', + directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], + inputs: ['hero'] +}) +export class HeroDetailComponent implements OnInit { + public hero: Hero; + + constructor(private _heroService: HeroService, + private _routeParams: RouteParams, private _router: Router) { + } + + onInit() { + if (!this.hero) { + let id = +this._routeParams.get('id'); + this._heroService.getHero(id).then((hero: Hero) => this.hero = hero); + } + } + + gotoHeroes() { + this._router.navigate([`/${Routes.heroes.as}`]); + } +} diff --git a/src/app/hero.service.ts b/src/app/hero.service.ts new file mode 100644 index 0000000..a0d63d3 --- /dev/null +++ b/src/app/hero.service.ts @@ -0,0 +1,15 @@ +import {HEROES} from './mock-heroes'; +import {Hero} from './hero'; + +export class HeroService { + getHeroes() { + return Promise.resolve(HEROES); + } + + getHero(id: number) { + return Promise.resolve(HEROES) + .then((heroes: Hero[]) => { return heroes.filter((h) => { + return h.id === id; + })[0]}); + } +} diff --git a/src/app/hero.ts b/src/app/hero.ts new file mode 100644 index 0000000..e94ec61 --- /dev/null +++ b/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/src/app/heroes.component.css b/src/app/heroes.component.css new file mode 100644 index 0000000..febf0e1 --- /dev/null +++ b/src/app/heroes.component.css @@ -0,0 +1,17 @@ +.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;} + +.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } + +.heroes li:hover {color: #369; background-color: #EEE; left: .2em;} + +.heroes .badge { + font-size: small; + color: white; + padding: 0.1em 0.7em; + background-color: #369; + line-height: 1em; + position: relative; + left: -1px; + top: -1px; +} +.selected { background-color: #EEE; color: #369; } \ No newline at end of file diff --git a/src/app/heroes.component.html b/src/app/heroes.component.html new file mode 100644 index 0000000..db69712 --- /dev/null +++ b/src/app/heroes.component.html @@ -0,0 +1,14 @@ +
+

My Heroes

+
    +
  • + {{hero.id}} {{hero.name}} +
  • +
+
+

{{selectedHero.name | uppercase}} is my hero

+ +
+
diff --git a/src/app/heroes.component.ts b/src/app/heroes.component.ts new file mode 100644 index 0000000..1657e61 --- /dev/null +++ b/src/app/heroes.component.ts @@ -0,0 +1,43 @@ +import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; +import {Router} from 'angular2/router'; +import {HeroService} from './hero.service'; +import {HeroDetailComponent} from './hero-detail.component'; +import {Hero} from './hero'; +import {Routes} from './route.config'; + +@Component({ + selector: 'my-heroes', + templateUrl: 'app/heroes.component.html', + styleUrls: ['app/heroes.component.css'], + directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, HeroDetailComponent] +}) +export class HeroesComponent implements OnInit { + public heroes: Hero[]; + public selectedHero: Hero; + + constructor(private _heroService: HeroService, private _router: Router) { } + + getHeroes() { + this.selectedHero = undefined; + this.heroes = []; + + this._heroService.getHeroes() + .then((heroes: Hero[]) => this.heroes = heroes); + + return this.heroes; + } + + getSelectedClass(hero: Hero) { + return { 'selected': hero === this.selectedHero }; + } + + gotoDetail() { + this._router.navigate([`/${Routes.detail.as}`, { id: this.selectedHero.id }]); + } + + onInit() { + this.heroes = this.getHeroes(); + } + + onSelect(hero: Hero) { this.selectedHero = hero; } +} diff --git a/src/app/mock-heroes.ts b/src/app/mock-heroes.ts new file mode 100644 index 0000000..406e5eb --- /dev/null +++ b/src/app/mock-heroes.ts @@ -0,0 +1,14 @@ +import { Hero } from './hero'; + +export var HEROES: Hero[] = [ + {"id": 11, "name": "Mr. Nice"}, + {"id": 12, "name": "Narco"}, + {"id": 13, "name": "Bombasto"}, + {"id": 14, "name": "Celeritas"}, + {"id": 15, "name": "Magneta"}, + {"id": 16, "name": "RubberMan"}, + {"id": 17, "name": "Dynama"}, + {"id": 18, "name": "Dr IQ"}, + {"id": 19, "name": "Magma"}, + {"id": 20, "name": "Tornado"} +]; diff --git a/src/app/route.config.ts b/src/app/route.config.ts new file mode 100644 index 0000000..cc2fa60 --- /dev/null +++ b/src/app/route.config.ts @@ -0,0 +1,26 @@ +import {HeroesComponent} from './heroes.component'; +import {HeroDetailComponent} from './hero-detail.component'; +import {DashboardComponent} from './dashboard.component'; + +export var Routes = { + dashboard: { + path: '/', + as: 'Dashboard', + component: DashboardComponent, + caption: 'Dashboard' + }, + heroes: { + path: '/heroes', + as: 'Heroes', + caption: 'Heroes', + component: HeroesComponent + }, + detail: { + path: '/detail/:id', + as: 'Detail', + caption: 'Hero Detail', + component: HeroDetailComponent + } +}; + +export const APP_ROUTES = [Routes.dashboard, Routes.detail, Routes.heroes]; diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..a81ab94 --- /dev/null +++ b/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + 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} From a30cf73f722a85fd8cd44daa0f22d7fa6430964d Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 15 Oct 2015 21:54:47 -0700 Subject: [PATCH 13/42] Update Angular version, add a few simple tests --- karma-test-shim.js | 1 - karma.conf.js | 33 +------------------------- package.json | 5 ++-- src/test/hero-detail-component_test.ts | 20 ++++++++++++++++ src/test/hero-service_test.ts | 19 +++++++++++++++ src/test/sanity_test.ts | 11 +++++++++ tsconfig.json | 1 + 7 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 src/test/hero-detail-component_test.ts create mode 100644 src/test/hero-service_test.ts create mode 100644 src/test/sanity_test.ts diff --git a/karma-test-shim.js b/karma-test-shim.js index add673d..08f716c 100644 --- a/karma-test-shim.js +++ b/karma-test-shim.js @@ -36,7 +36,6 @@ System.import('angular2/src/core/dom/browser_adapter').then(function(browser_ada .filter(onlySpecFiles) // .map(filePath2moduleName) // Normalize paths to module names. .map(function(moduleName) { - console.log('loading module: ' + moduleName); // loads all spec files via their global module names (e.g. 'base/src/app/hero.service.spec') return System.import(moduleName); })); diff --git a/karma.conf.js b/karma.conf.js index 6f15b13..996e7f1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,17 +1,10 @@ -// Karma configuration -// Generated on Sat Oct 03 2015 15:04:14 GMT-0700 (PDT) - module.exports = function(config) { config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], - // // list of files / patterns to load in the browser files: [ // paths loaded by Karma {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true}, @@ -38,36 +31,12 @@ module.exports = function(config) { "/app/": "/base/src/app/" }, - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], - - - // web server port port: 9876, - - - // enable / disable colors in the output (reporters and logs) colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_DEBUG, - - - // enable / disable watching file and executing tests whenever any file changes + logLevel: config.LOG_INFO, autoWatch: true, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits singleRun: false }) } diff --git a/package.json b/package.json index 3791dc0..e10f4e7 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,14 @@ "url": "https://github.com/angular/ng-test-seed.git" }, "scripts": { - "clean": "rimraf src/app/*.js src/app/*.js.map", + "clean": "rimraf src/**/*.js src/**/*.js.map", "build": "tsc", "watch": "tsc --watch", "serve": "http-server -p 9090 -c-1", "test": "karma start karma.conf.js" }, "devDependencies": { - "angular2": "2.0.0-alpha.42", - "del": "^2.0.2", + "angular2": "2.0.0-alpha.44", "http-server": "^0.8.5", "jasmine": "2.3.2", "karma": "^0.12.23", diff --git a/src/test/hero-detail-component_test.ts b/src/test/hero-detail-component_test.ts new file mode 100644 index 0000000..6da814c --- /dev/null +++ b/src/test/hero-detail-component_test.ts @@ -0,0 +1,20 @@ +import { + iit, + it, + ddescribe, + describe, + expect, + injectAsync, + TestComponentBuilder +} from 'angular2/testing'; +import { + HeroDetailComponent +} from '../app/hero-detail.component'; + +describe('hero service', () => { + it('should return a hero', injectAsync([TestComponentBuilder], (tcb) => { + return tcb.createAsync(HeroDetailComponent).then((fixture) => { + + }); + })); +}); diff --git a/src/test/hero-service_test.ts b/src/test/hero-service_test.ts new file mode 100644 index 0000000..c7b4698 --- /dev/null +++ b/src/test/hero-service_test.ts @@ -0,0 +1,19 @@ +import { + iit, + it, + ddescribe, + describe, + expect +} from 'angular2/testing'; +import { + HeroService +} from '../app/hero.service'; + +describe('hero service', () => { + it('should return a hero', (done) => { + var service = new HeroService(); + service.getHero(20).then((hero) => { + expect(hero.name).toEqual('Tornado'); + }).then(done, done.fail); + }); +}); 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/tsconfig.json b/tsconfig.json index c06059f..7010bb6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "ES5", "module": "system", + "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, From 8b546445238058857503eb46caaff8d031f16397 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 15 Oct 2015 22:32:44 -0700 Subject: [PATCH 14/42] Remove router for now --- src/app/app.component.ts | 12 ++++------ src/app/bootstrap.ts | 4 ++-- src/app/dashboard.component.css | 37 ------------------------------ src/app/dashboard.component.html | 8 ------- src/app/dashboard.component.ts | 34 --------------------------- src/app/hero-detail.component.html | 17 ++++++-------- src/app/hero-detail.component.ts | 19 ++------------- src/app/hero.service.ts | 2 ++ src/app/heroes.component.html | 5 +++- src/app/heroes.component.ts | 15 +++++------- src/app/route.config.ts | 26 --------------------- src/index.html | 1 - 12 files changed, 27 insertions(+), 153 deletions(-) delete mode 100644 src/app/dashboard.component.css delete mode 100644 src/app/dashboard.component.html delete mode 100644 src/app/dashboard.component.ts delete mode 100644 src/app/route.config.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 43a864d..5dea14a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,14 +1,12 @@ import {Component, CORE_DIRECTIVES} from 'angular2/angular2'; -import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; -import {Routes, APP_ROUTES} from './route.config'; +import {HeroDetailComponent} from './hero-detail.component'; +import {HeroesComponent} from './heroes.component'; @Component({ selector: 'my-app', template: `

{{title}}

- {{routes.dashboard.caption}} - {{routes.heroes.caption}} - + `, styles: [` .router-link {padding: 5px;text-decoration: none;} @@ -16,10 +14,8 @@ import {Routes, APP_ROUTES} from './route.config'; .router-link:hover {color: white; background-color: #1171a3; text-decoration: none;} .router-link.router-link-active {color: white; background-color: #52b9e9; text-decoration: none;} `], - directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] + directives: [CORE_DIRECTIVES, HeroesComponent] }) -@RouteConfig(APP_ROUTES) export class AppComponent { public title = 'Tour of Heroes'; - public routes = Routes; } diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts index 098faaa..44acd8e 100644 --- a/src/app/bootstrap.ts +++ b/src/app/bootstrap.ts @@ -1,6 +1,6 @@ import {bootstrap} from 'angular2/angular2'; -import {ROUTER_PROVIDERS} from 'angular2/router'; import {HeroService} from './hero.service'; +// import {UserService} from './user.service'; import {AppComponent} from './app.component'; -bootstrap(AppComponent, [ROUTER_PROVIDERS, HeroService]); +bootstrap(AppComponent, [HeroService]); diff --git a/src/app/dashboard.component.css b/src/app/dashboard.component.css deleted file mode 100644 index 26a8f30..0000000 --- a/src/app/dashboard.component.css +++ /dev/null @@ -1,37 +0,0 @@ -[class*='col-'] { float: left; } - -*, *:after, *:before { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -h3 { text-align: center; margin-bottom: 0; } - -[class*='col-'] { padding-right: 20px; padding-bottom: 20px;} -[class*='col-']:last-of-type { padding-right: 0; } - -.grid { margin: 0 10em; } -.col-1-4 { width: 25%; } -.module { - padding: 20px; - text-align: center; - color: #eee; - max-height: 120px; - min-width: 120px; - background-color: #1171a3; -} - -.module:hover { background-color: #52b9e9; cursor: pointer; } - -.grid-pad { padding: 20px 0 20px 20px; } -.grid-pad > [class*='col-']:last-of-type { padding-right: 20px; } - -@media (max-width: 600px) { - .module { font-size: 10px; max-height: 75px; } -} - -@media (max-width: 1024px) { - .grid { margin: 0; } - .module { min-width: 60px; } -} diff --git a/src/app/dashboard.component.html b/src/app/dashboard.component.html deleted file mode 100644 index 3544fdc..0000000 --- a/src/app/dashboard.component.html +++ /dev/null @@ -1,8 +0,0 @@ -

Top Heroes

-
-
-
-

{{hero.name}}

-
-
-
diff --git a/src/app/dashboard.component.ts b/src/app/dashboard.component.ts deleted file mode 100644 index 9a7406c..0000000 --- a/src/app/dashboard.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; -import {Router} from 'angular2/router'; -import {Hero} from './hero'; -import {HeroService} from './hero.service'; -import {Routes} from './route.config'; - -@Component({ - selector: 'my-dashboard', - templateUrl: 'app/dashboard.component.html', - styleUrls: ['app/dashboard.component.css'], - directives: [CORE_DIRECTIVES, FORM_DIRECTIVES] -}) -export class DashboardComponent implements OnInit { - public heroes: Hero[]; - - constructor(private _heroService: HeroService, private _router: Router) { } - - onInit() { - this.heroes = this.getHeroes(); - } - - gotoDetail(hero: Hero) { - this._router.navigate([`/${Routes.detail.as}`, { id: hero.id }]); - } - - getHeroes() { - this.heroes = []; - - this._heroService.getHeroes() - .then((heroes: Hero[]) => this.heroes = heroes); - - return this.heroes; - } -} diff --git a/src/app/hero-detail.component.html b/src/app/hero-detail.component.html index 8362f78..027c84c 100644 --- a/src/app/hero-detail.component.html +++ b/src/app/hero-detail.component.html @@ -1,10 +1,7 @@ -
-

{{hero.name}} details!

-
- {{hero.id}}
-
- - -
- -
\ No newline at end of file +

{{hero.name}} details!

+
+ {{hero.id}}
+
+ + +
diff --git a/src/app/hero-detail.component.ts b/src/app/hero-detail.component.ts index 3365646..0388927 100644 --- a/src/app/hero-detail.component.ts +++ b/src/app/hero-detail.component.ts @@ -1,31 +1,16 @@ import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; -import {RouteParams, Router} from 'angular2/router'; import {Hero} from './hero'; import {HeroService} from './hero.service'; -import {Routes} from './route.config'; -// Here is a comment @Component({ selector: 'my-hero-detail', templateUrl: 'app/hero-detail.component.html', directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], inputs: ['hero'] }) -export class HeroDetailComponent implements OnInit { +export class HeroDetailComponent { public hero: Hero; - constructor(private _heroService: HeroService, - private _routeParams: RouteParams, private _router: Router) { - } - - onInit() { - if (!this.hero) { - let id = +this._routeParams.get('id'); - this._heroService.getHero(id).then((hero: Hero) => this.hero = hero); - } - } - - gotoHeroes() { - this._router.navigate([`/${Routes.heroes.as}`]); + constructor(private _heroService: HeroService) { } } diff --git a/src/app/hero.service.ts b/src/app/hero.service.ts index a0d63d3..892bb72 100644 --- a/src/app/hero.service.ts +++ b/src/app/hero.service.ts @@ -2,6 +2,8 @@ import {HEROES} from './mock-heroes'; import {Hero} from './hero'; export class HeroService { + selectedHeroId: number; + getHeroes() { return Promise.resolve(HEROES); } diff --git a/src/app/heroes.component.html b/src/app/heroes.component.html index db69712..cb136ea 100644 --- a/src/app/heroes.component.html +++ b/src/app/heroes.component.html @@ -9,6 +9,9 @@

My Heroes

{{selectedHero.name | uppercase}} is my hero

- +
+
+
+
diff --git a/src/app/heroes.component.ts b/src/app/heroes.component.ts index 1657e61..d0052ab 100644 --- a/src/app/heroes.component.ts +++ b/src/app/heroes.component.ts @@ -1,9 +1,7 @@ import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; -import {Router} from 'angular2/router'; import {HeroService} from './hero.service'; -import {HeroDetailComponent} from './hero-detail.component'; import {Hero} from './hero'; -import {Routes} from './route.config'; +import {HeroDetailComponent} from './hero-detail.component'; @Component({ selector: 'my-heroes', @@ -15,7 +13,7 @@ export class HeroesComponent implements OnInit { public heroes: Hero[]; public selectedHero: Hero; - constructor(private _heroService: HeroService, private _router: Router) { } + constructor(private _heroService: HeroService) { } getHeroes() { this.selectedHero = undefined; @@ -31,13 +29,12 @@ export class HeroesComponent implements OnInit { return { 'selected': hero === this.selectedHero }; } - gotoDetail() { - this._router.navigate([`/${Routes.detail.as}`, { id: this.selectedHero.id }]); - } - onInit() { this.heroes = this.getHeroes(); } - onSelect(hero: Hero) { this.selectedHero = hero; } + onSelect(hero: Hero) { + this._heroService.selectedHeroId = hero.id; + this.selectedHero = hero; + } } diff --git a/src/app/route.config.ts b/src/app/route.config.ts deleted file mode 100644 index cc2fa60..0000000 --- a/src/app/route.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {HeroesComponent} from './heroes.component'; -import {HeroDetailComponent} from './hero-detail.component'; -import {DashboardComponent} from './dashboard.component'; - -export var Routes = { - dashboard: { - path: '/', - as: 'Dashboard', - component: DashboardComponent, - caption: 'Dashboard' - }, - heroes: { - path: '/heroes', - as: 'Heroes', - caption: 'Heroes', - component: HeroesComponent - }, - detail: { - path: '/detail/:id', - as: 'Detail', - caption: 'Hero Detail', - component: HeroDetailComponent - } -}; - -export const APP_ROUTES = [Routes.dashboard, Routes.detail, Routes.heroes]; diff --git a/src/index.html b/src/index.html index a81ab94..541d645 100644 --- a/src/index.html +++ b/src/index.html @@ -6,7 +6,6 @@ - + + + + + + diff --git a/tsconfig.json b/tsconfig.json index 0394368..b9a5192 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, - "noImplicitAny": false + "noImplicitAny": false, + "outDir": "built/" }, "exclude": [ "node_modules", From 5d6d4419b13e9b320542d9effcf87e727e701317 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 26 Apr 2016 12:40:18 -0700 Subject: [PATCH 37/42] fix build/built typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68ac139..07fe76f 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ 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 `build/` folder. +The generated files are output in the `built/` folder. To remove all generated files, run: From 79b19e44f2c08455bbf5fbd848c60001f514c637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Tue, 27 Oct 2015 19:02:07 +0100 Subject: [PATCH 38/42] chore: add missing semicolon to karma-test-shim I a ported this workflow to [ng-bootstrap](https://github.com/ng-bootstrap/core) and our clang was doing crazy things, it was due that semicolon which would put the return on the same line and cry. Thanks for extracting the idea in here Julie, I was able to write tests at last :) --- karma-test-shim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/karma-test-shim.js b/karma-test-shim.js index 5fbc729..ac4c94c 100644 --- a/karma-test-shim.js +++ b/karma-test-shim.js @@ -20,7 +20,7 @@ System.config({ // 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] + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath]; return pathsMapping; }, {}) From 098fa7160e0ea918bbc27dee41d29df4e72411ec Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 26 Apr 2016 22:37:26 -0700 Subject: [PATCH 39/42] Fix up some syntax errors --- src/app/login-service.ts | 4 ++-- src/test/greeting-component_test.ts | 4 ++-- src/test/user-service_test.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/login-service.ts b/src/app/login-service.ts index b92cc47..d56cccb 100644 --- a/src/app/login-service.ts +++ b/src/app/login-service.ts @@ -5,12 +5,12 @@ export class LoginService { login(pin: number) { return new Promise((resolve, reject) => { setTimeout(() => { - if (pin == 2015) { + if (pin === 2015) { resolve(true); } else { resolve(false); } }, 1000); - }) + }); } } diff --git a/src/test/greeting-component_test.ts b/src/test/greeting-component_test.ts index a0ad680..2b76daa 100644 --- a/src/test/greeting-component_test.ts +++ b/src/test/greeting-component_test.ts @@ -50,7 +50,7 @@ describe('greeting component', () => { builder.createAsync(GreetingComponent).then((fixture) => { fixture.detectChanges(); - fixture.debugElement.componentInstance.greeting = "Foobar"; + fixture.debugElement.componentInstance.greeting = 'Foobar'; fixture.detectChanges(); var compiled = fixture.debugElement.nativeElement; @@ -77,7 +77,7 @@ describe('greeting component', () => { fixture.debugElement.componentInstance.pending.then(() => { fixture.detectChanges(); expect(compiled.querySelector('h3')).toHaveText('Status: Welcome!'); - });; + }); }); })); diff --git a/src/test/user-service_test.ts b/src/test/user-service_test.ts index ad791aa..52ff723 100644 --- a/src/test/user-service_test.ts +++ b/src/test/user-service_test.ts @@ -6,7 +6,7 @@ import { LoginService } from '../app/login-service'; describe('user service', () => { - beforeEachProviders(() => [LoginService, UserService]) + beforeEachProviders(() => [LoginService, UserService]); it('should validate pins', inject([UserService], (service) => { service.pin = 12345; From 106440046b9f08ade752154798166eeabef4c645 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Wed, 27 Apr 2016 15:09:08 -0700 Subject: [PATCH 40/42] WIP - add forms components and some not yet working tests --- src/app/app-component.ts | 4 +- src/app/form-component.html | 30 ++++++++++++ src/app/form-component.ts | 19 ++++++++ src/test/form-component_test.ts | 85 +++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/app/form-component.html create mode 100644 src/app/form-component.ts create mode 100644 src/test/form-component_test.ts diff --git a/src/app/app-component.ts b/src/app/app-component.ts index c0040fb..fecf2d9 100644 --- a/src/app/app-component.ts +++ b/src/app/app-component.ts @@ -1,6 +1,7 @@ import {Component} from 'angular2/core'; import {GreetingComponent} from './greeting-component'; import {BorderComponent} from './border-component'; +import {FormComponent} from './form-component'; @Component({ selector: 'my-app', @@ -8,8 +9,9 @@ import {BorderComponent} from './border-component'; + `, - directives: [GreetingComponent, BorderComponent] + directives: [GreetingComponent, BorderComponent, FormComponent] }) export class AppComponent { } diff --git a/src/app/form-component.html b/src/app/form-component.html new file mode 100644 index 0000000..86ed636 --- /dev/null +++ b/src/app/form-component.html @@ -0,0 +1,30 @@ +
+ Subscribe to our newsletter + +
+ + +
+ +
+
+ + + + + +
+ + + + + +
+ +
+ Confirmation: +

+ Sending to {{nickname}} at {{form.value.location.address}}, {{form.value.location.zip}} for {{form.value.shipping ? 'free' : '$50'}} +

+
+
\ No newline at end of file diff --git a/src/app/form-component.ts b/src/app/form-component.ts new file mode 100644 index 0000000..3a39f25 --- /dev/null +++ b/src/app/form-component.ts @@ -0,0 +1,19 @@ +import {Component, ViewChild} from 'angular2/core'; +import {FORM_DIRECTIVES, NgForm, FormBuilder} from 'angular2/common'; + +@Component({ + selector: 'my-form', + templateUrl: 'app/form-component.html', + directives: [FORM_DIRECTIVES] +}) +export class FormComponent { + @ViewChild(NgForm) form: NgForm; + + nickname: string = 'Jenny'; + + showConfirmation: boolean = false; + + register() { + this.showConfirmation = true; + } +} diff --git a/src/test/form-component_test.ts b/src/test/form-component_test.ts new file mode 100644 index 0000000..e0e5ec9 --- /dev/null +++ b/src/test/form-component_test.ts @@ -0,0 +1,85 @@ +import { + iit, + it, + 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 } from '../app/form-component'; + +ddescribe('form components', () => { + var fixture; + + beforeEach(async(inject([TestComponentBuilder], (tcb) => { + tcb.createAsync(FormComponent).then((componentFixture: ComponentFixture) => { + fixture = componentFixture; + }); + }))); + + iit('should change ngModel', async(() => { + fixture.detectChanges(); + + // var el = fixture.nativeElement.querySelector('#my-nickname'); + var el = fixture.debugElement.query(By.css('#my-nickname')).nativeElement; + + el.value = 'Ace'; + debugger; + el.dispatchEvent(new Event('input')); // Necessary and event has to be 'input' + + + // fixture.detectChanges(); // not necessary. + // This is RESETTING back to the original nickname if we do not do + // the first detectChanges at the start. + + console.log(fixture.componentInstance.nickname); + + setTimeout(() => { + console.log(fixture.componentInstance.nickname); + }, 100); + })); + + it('should display a form', async(() => { + fixture.detectChanges(); + console.log(fixture.componentInstance.form.value.location); + + // let nativeElement = fixture.nativeElement; + // debugger; + // nativeElement.querySelector('#my-address').value = 'New York'; + // debugger; + // // nativeElement.querySelector('#my-zip').value = '10001'; + // nativeElement.querySelector('#my-address').dispatchEvent(new Event('input')); + // // nativeElement.querySelector('button').click(); + + var el = fixture.debugElement.query(By.css('#my-address')).nativeElement; + el.value = 'New York'; + debugger; + + // fixture.detectChanges(); + debugger; + + var evt: Event = document.createEvent('Event'); + evt.initEvent('input', true, true); + + el.dispatchEvent(evt); + debugger; + + fixture.detectChanges(); + debugger; + + console.log(fixture.componentInstance.form.value.location); + + setTimeout(() => { + console.log(fixture.componentInstance.form.value.location); + }, 100); + })); +}); From 6afa3b35a20c07748e884e9f57559aa5c205011e Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Wed, 27 Apr 2016 16:09:23 -0700 Subject: [PATCH 41/42] Show asynchronous timing introduced by EventEmitter --- src/app/form-component.html | 10 +++++-- src/app/form-component.ts | 45 +++++++++++++++++++++++++++---- src/test/form-component_test.ts | 47 ++++++++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/src/app/form-component.html b/src/app/form-component.html index 86ed636..366733f 100644 --- a/src/app/form-component.html +++ b/src/app/form-component.html @@ -5,8 +5,14 @@ + {{nickname}} -
+ + + +
{{retreated}}
+ +
Confirmation: diff --git a/src/app/form-component.ts b/src/app/form-component.ts index 3a39f25..e21724c 100644 --- a/src/app/form-component.ts +++ b/src/app/form-component.ts @@ -1,19 +1,54 @@ -import {Component, ViewChild} from 'angular2/core'; +import {Component, ViewChild, EventEmitter, Output} from 'angular2/core'; import {FORM_DIRECTIVES, NgForm, FormBuilder} from 'angular2/common'; + +@Component({ + selector: 'simpleclick', + template: `{{done}}` +}) +export class SimpleClick { + done: string = 'nope'; + doTheThing() { + console.log('DID THE THING'); + this.done = 'yup'; + } +} + +@Component({ + selector: 'otherclick', + template: `{{status}}` +}) +export class OtherClick { + @Output() attack = new EventEmitter(); + + status: string = 'calm'; + + provoke() { + console.log('YOU POKED ME!!'); + this.status = 'angry'; + this.attack.emit('foo?'); + } +} + @Component({ selector: 'my-form', templateUrl: 'app/form-component.html', - directives: [FORM_DIRECTIVES] + directives: [FORM_DIRECTIVES, SimpleClick, OtherClick] }) export class FormComponent { - @ViewChild(NgForm) form: NgForm; + // @ViewChild(NgForm) form: NgForm; nickname: string = 'Jenny'; + retreated: string = 'Here'; showConfirmation: boolean = false; - register() { - this.showConfirmation = true; + // register() { + // this.showConfirmation = true; + // } + + retreat() { + console.log('Uh oh I made it mad'); + this.retreated = 'Run awaaay'; } } diff --git a/src/test/form-component_test.ts b/src/test/form-component_test.ts index e0e5ec9..ccbf456 100644 --- a/src/test/form-component_test.ts +++ b/src/test/form-component_test.ts @@ -1,6 +1,7 @@ import { iit, it, + xit, ddescribe, describe, expect, @@ -15,7 +16,7 @@ import { } from 'angular2/testing'; import {By} from 'angular2/platform/common_dom'; import { provide } from 'angular2/core'; -import { FormComponent } from '../app/form-component'; +import { FormComponent, SimpleClick, OtherClick } from '../app/form-component'; ddescribe('form components', () => { var fixture; @@ -26,7 +27,47 @@ ddescribe('form components', () => { }); }))); - iit('should change ngModel', async(() => { + it('should do a simple click', async(() => { + fixture.detectChanges(); + + // var el = fixture.nativeElement.querySelector('#my-nickname'); + var but = fixture.debugElement.query(By.css('#n1')).nativeElement; + but.click(); + var span = fixture.debugElement.query(By.css('#n2')).nativeElement; + + console.log(span); + + fixture.detectChanges(); + + console.log(span); + })); + + it('should do a simple click', async(() => { + fixture.detectChanges(); + + // var el = fixture.nativeElement.querySelector('#my-nickname'); + var but = fixture.debugElement.query(By.css('#m1')).nativeElement; + but.click(); + var span = fixture.debugElement.query(By.css('#m2')).nativeElement; + var otherSpan = fixture.debugElement.query(By.css('#m3')).nativeElement; + + console.log(span); + console.log(otherSpan); + + fixture.detectChanges(); + + console.log(span); + console.log(otherSpan); + + setTimeout(() => { + fixture.detectChanges(); + console.log(span); + console.log(otherSpan); + }, 200); + })); + + + xit('should change ngModel', async(() => { fixture.detectChanges(); // var el = fixture.nativeElement.querySelector('#my-nickname'); @@ -48,7 +89,7 @@ ddescribe('form components', () => { }, 100); })); - it('should display a form', async(() => { + xit('should display a form', async(() => { fixture.detectChanges(); console.log(fixture.componentInstance.form.value.location); From 7ae46a6256eea13f106cf7716f7c1ab26a64ba8b Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 28 Apr 2016 11:57:22 -0700 Subject: [PATCH 42/42] Show form tests doing funny things depending on builder or not --- src/app/form-component.html | 39 +--------- src/app/form-component.ts | 73 +++++++----------- src/test/form-component_test.ts | 129 +++++++++----------------------- 3 files changed, 68 insertions(+), 173 deletions(-) diff --git a/src/app/form-component.html b/src/app/form-component.html index 366733f..f2e89b8 100644 --- a/src/app/form-component.html +++ b/src/app/form-component.html @@ -1,36 +1,3 @@ -
- Subscribe to our newsletter - -
- - -
- {{nickname}} - - - - -
{{retreated}}
- - - -
- Confirmation: -

- Sending to {{nickname}} at {{form.value.location.address}}, {{form.value.location.zip}} for {{form.value.shipping ? 'free' : '$50'}} -

-
-
\ No newline at end of file + + + diff --git a/src/app/form-component.ts b/src/app/form-component.ts index e21724c..816ea20 100644 --- a/src/app/form-component.ts +++ b/src/app/form-component.ts @@ -1,54 +1,39 @@ import {Component, ViewChild, EventEmitter, Output} from 'angular2/core'; -import {FORM_DIRECTIVES, NgForm, FormBuilder} from 'angular2/common'; - - -@Component({ - selector: 'simpleclick', - template: `{{done}}` -}) -export class SimpleClick { - done: string = 'nope'; - doTheThing() { - console.log('DID THE THING'); - this.done = 'yup'; - } -} +import {FORM_DIRECTIVES, NgForm, FormBuilder, Control, ControlGroup, Validators} from 'angular2/common'; @Component({ - selector: 'otherclick', - template: `{{status}}` + selector: `my-form`, + template: ` +
+ +
+ + `, + directives: [FORM_DIRECTIVES] }) -export class OtherClick { - @Output() attack = new EventEmitter(); - - status: string = 'calm'; - - provoke() { - console.log('YOU POKED ME!!'); - this.status = 'angry'; - this.attack.emit('foo?'); - } +export class FormComponent { + login: Control; + @ViewChild(NgForm) userForm: ControlGroup; } @Component({ - selector: 'my-form', - templateUrl: 'app/form-component.html', - directives: [FORM_DIRECTIVES, SimpleClick, OtherClick] + selector: `my-form2`, + template: ` +
+ +
+ + `, + directives: [FORM_DIRECTIVES] }) -export class FormComponent { - // @ViewChild(NgForm) form: NgForm; - - nickname: string = 'Jenny'; - retreated: string = 'Here'; - - showConfirmation: boolean = false; - - // register() { - // this.showConfirmation = true; - // } - - retreat() { - console.log('Uh oh I made it mad'); - this.retreated = 'Run awaaay'; +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/test/form-component_test.ts b/src/test/form-component_test.ts index ccbf456..f46b28d 100644 --- a/src/test/form-component_test.ts +++ b/src/test/form-component_test.ts @@ -16,111 +16,54 @@ import { } from 'angular2/testing'; import {By} from 'angular2/platform/common_dom'; import { provide } from 'angular2/core'; -import { FormComponent, SimpleClick, OtherClick } from '../app/form-component'; +import { FormComponent, Form2Component} from '../app/form-component'; ddescribe('form components', () => { - var fixture; + var builder; beforeEach(async(inject([TestComponentBuilder], (tcb) => { - tcb.createAsync(FormComponent).then((componentFixture: ComponentFixture) => { - fixture = componentFixture; - }); + builder = tcb; }))); - it('should do a simple click', async(() => { - fixture.detectChanges(); - - // var el = fixture.nativeElement.querySelector('#my-nickname'); - var but = fixture.debugElement.query(By.css('#n1')).nativeElement; - but.click(); - var span = fixture.debugElement.query(By.css('#n2')).nativeElement; - - console.log(span); - - fixture.detectChanges(); - - console.log(span); - })); - - it('should do a simple click', async(() => { - fixture.detectChanges(); - - // var el = fixture.nativeElement.querySelector('#my-nickname'); - var but = fixture.debugElement.query(By.css('#m1')).nativeElement; - but.click(); - var span = fixture.debugElement.query(By.css('#m2')).nativeElement; - var otherSpan = fixture.debugElement.query(By.css('#m3')).nativeElement; - - console.log(span); - console.log(otherSpan); - - fixture.detectChanges(); - - console.log(span); - console.log(otherSpan); - - setTimeout(() => { + it('should display a form to register', async(() => { + builder.createAsync(FormComponent).then((fixture: ComponentFixture) => { fixture.detectChanges(); - console.log(span); - console.log(otherSpan); - }, 200); - })); - - - xit('should change ngModel', async(() => { - fixture.detectChanges(); - - // var el = fixture.nativeElement.querySelector('#my-nickname'); - var el = fixture.debugElement.query(By.css('#my-nickname')).nativeElement; - - el.value = 'Ace'; - debugger; - el.dispatchEvent(new Event('input')); // Necessary and event has to be 'input' - - - // fixture.detectChanges(); // not necessary. - // This is RESETTING back to the original nickname if we do not do - // the first detectChanges at the start. - - console.log(fixture.componentInstance.nickname); - - setTimeout(() => { - console.log(fixture.componentInstance.nickname); - }, 100); + // 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); + }); + }); })); - xit('should display a form', async(() => { - fixture.detectChanges(); - console.log(fixture.componentInstance.form.value.location); - - // let nativeElement = fixture.nativeElement; - // debugger; - // nativeElement.querySelector('#my-address').value = 'New York'; - // debugger; - // // nativeElement.querySelector('#my-zip').value = '10001'; - // nativeElement.querySelector('#my-address').dispatchEvent(new Event('input')); - // // nativeElement.querySelector('button').click(); - - var el = fixture.debugElement.query(By.css('#my-address')).nativeElement; - el.value = 'New York'; - debugger; - - // fixture.detectChanges(); - debugger; - - var evt: Event = document.createEvent('Event'); - evt.initEvent('input', true, true); + it('should display a form 2', async(() => { + builder.createAsync(Form2Component).then((fixture: ComponentFixture) => { + fixture.detectChanges(); + // given a form + let userForm = fixture.componentInstance.userForm; - el.dispatchEvent(evt); - debugger; + expect(userForm.value).toEqual({ login: '' }); - fixture.detectChanges(); - debugger; + // when adding values in the form + let nativeElement = fixture.nativeElement; + nativeElement.querySelector('input').value = 'Cédric'; + nativeElement.querySelector('input').dispatchEvent(new Event('input')); - console.log(fixture.componentInstance.form.value.location); + fixture.detectChanges(); - setTimeout(() => { - console.log(fixture.componentInstance.form.value.location); - }, 100); + expect(userForm.value).toEqual({ login: 'Cédric' }); + }); })); });