test(ivy): update todo app to http://todomvc.com (#23168)
PR Close #23168
This commit is contained in:
parent
50030f650b
commit
f1db789450
|
@ -78,6 +78,7 @@ export {
|
|||
ComponentDef as ɵComponentDef,
|
||||
DirectiveDef as ɵDirectiveDef,
|
||||
PipeDef as ɵPipeDef,
|
||||
whenRendered as ɵwhenRendered,
|
||||
} from './render3/index';
|
||||
export {
|
||||
bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml,
|
||||
|
|
|
@ -36,6 +36,7 @@ ts_library(
|
|||
srcs = glob(["*_spec.ts"]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/core",
|
||||
"//packages/core/testing",
|
||||
],
|
||||
)
|
||||
|
@ -75,6 +76,8 @@ ts_devserver(
|
|||
static_files = [
|
||||
"index.html",
|
||||
":tslib",
|
||||
"todo.css",
|
||||
"base.css",
|
||||
],
|
||||
deps = [":todo"],
|
||||
)
|
||||
|
@ -87,5 +90,7 @@ ts_devserver(
|
|||
":bundle.min_debug.js",
|
||||
":bundle.min.js",
|
||||
"index.html",
|
||||
"todo.css",
|
||||
"base.css",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
## `Todo` app
|
||||
- [X] Clicking archive removes todo item.
|
||||
- [ ] Update `Todo` app to match http://todomvc.com/
|
||||
- [X] Update `Todo` app to match http://todomvc.com/.
|
||||
- [ ] Make it work with `[(ngModel)]`.
|
||||
- [ ] Make it work with `(keyup.Enter)`.
|
||||
|
||||
## Compiler
|
||||
- [ ] Remove ` tslib_1.__decorate([core_1.Input(), tslib_1.__metadata("design:type", Object)], TodoComponent.prototype, "todo", void 0);` from generated output.
|
||||
|
@ -14,10 +16,13 @@
|
|||
- [ ] `ViewContainerRef` must separate creation mode from update mode otherwise {{todo.done}} fails for `NgFor` because `todo` is not set during creation mode.
|
||||
- [ ] Injector should be optional
|
||||
|
||||
## Testing
|
||||
- [ ] Create a debug mode which would publish components into DOM for easier writing of tests.
|
||||
|
||||
|
||||
## Bazel
|
||||
|
||||
- [ ] Have action verb on the `ng_rollup_bundle` to display sourcemaps.
|
||||
- [ ] Have action verb on the `ng_rollup_bundle` to display source maps.
|
||||
|
||||
# NOTES
|
||||
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
hr {
|
||||
margin: 20px 0;
|
||||
border: 0;
|
||||
border-top: 1px dashed #c5c5c5;
|
||||
border-bottom: 1px dashed #f7f7f7;
|
||||
}
|
||||
|
||||
.learn a {
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #b83f45;
|
||||
}
|
||||
|
||||
.learn a:hover {
|
||||
text-decoration: underline;
|
||||
color: #787e7e;
|
||||
}
|
||||
|
||||
.learn h3,
|
||||
.learn h4,
|
||||
.learn h5 {
|
||||
margin: 10px 0;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.learn h3 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.learn h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.learn h5 {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.learn ul {
|
||||
padding: 0;
|
||||
margin: 0 0 30px 25px;
|
||||
}
|
||||
|
||||
.learn li {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.learn p {
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
line-height: 1.3;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue-count {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quote {
|
||||
border: none;
|
||||
margin: 20px 0 60px 0;
|
||||
}
|
||||
|
||||
.quote p {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.quote p:before {
|
||||
content: '“';
|
||||
font-size: 50px;
|
||||
opacity: .15;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.quote p:after {
|
||||
content: '”';
|
||||
font-size: 50px;
|
||||
opacity: .15;
|
||||
position: absolute;
|
||||
bottom: -42px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.quote footer {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.quote footer img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.quote footer a {
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.speech-bubble {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, .04);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.speech-bubble:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 30px;
|
||||
border: 13px solid transparent;
|
||||
border-top-color: rgba(0, 0, 0, .04);
|
||||
}
|
||||
|
||||
.learn-bar > .learn {
|
||||
position: absolute;
|
||||
width: 272px;
|
||||
top: 8px;
|
||||
left: -300px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
transition-property: left;
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
@media (min-width: 899px) {
|
||||
.learn-bar {
|
||||
width: auto;
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
.learn-bar > .learn {
|
||||
left: 8px;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,4 @@
|
|||
[
|
||||
{
|
||||
"name": "AnonymousSubject"
|
||||
},
|
||||
{
|
||||
"name": "AppState"
|
||||
},
|
||||
{
|
||||
"name": "CIRCULAR$2"
|
||||
},
|
||||
|
@ -32,9 +26,6 @@
|
|||
{
|
||||
"name": "EmbeddedViewRef$1"
|
||||
},
|
||||
{
|
||||
"name": "EventEmitter"
|
||||
},
|
||||
{
|
||||
"name": "IterableChangeRecord_"
|
||||
},
|
||||
|
@ -53,15 +44,15 @@
|
|||
{
|
||||
"name": "NgForOfContext"
|
||||
},
|
||||
{
|
||||
"name": "NgIf"
|
||||
},
|
||||
{
|
||||
"name": "NgIfContext"
|
||||
},
|
||||
{
|
||||
"name": "NgOnChangesFeature"
|
||||
},
|
||||
{
|
||||
"name": "ObjectUnsubscribedError"
|
||||
},
|
||||
{
|
||||
"name": "Observable"
|
||||
},
|
||||
{
|
||||
"name": "Optional"
|
||||
},
|
||||
|
@ -77,30 +68,12 @@
|
|||
{
|
||||
"name": "RecordViewTuple"
|
||||
},
|
||||
{
|
||||
"name": "SafeSubscriber"
|
||||
},
|
||||
{
|
||||
"name": "SimpleChange"
|
||||
},
|
||||
{
|
||||
"name": "SkipSelf"
|
||||
},
|
||||
{
|
||||
"name": "Subject"
|
||||
},
|
||||
{
|
||||
"name": "SubjectSubscriber"
|
||||
},
|
||||
{
|
||||
"name": "SubjectSubscription"
|
||||
},
|
||||
{
|
||||
"name": "Subscriber"
|
||||
},
|
||||
{
|
||||
"name": "Subscription"
|
||||
},
|
||||
{
|
||||
"name": "TemplateRef$1"
|
||||
},
|
||||
|
@ -111,14 +84,14 @@
|
|||
"name": "ToDoAppModule"
|
||||
},
|
||||
{
|
||||
"name": "TodoComponent"
|
||||
"name": "Todo"
|
||||
},
|
||||
{
|
||||
"name": "TodoStore"
|
||||
},
|
||||
{
|
||||
"name": "UNDEFINED_RENDERER_TYPE_ID"
|
||||
},
|
||||
{
|
||||
"name": "UnsubscriptionError"
|
||||
},
|
||||
{
|
||||
"name": "ViewContainerRef$1"
|
||||
},
|
||||
|
@ -137,21 +110,6 @@
|
|||
{
|
||||
"name": "__extends"
|
||||
},
|
||||
{
|
||||
"name": "__extends$1"
|
||||
},
|
||||
{
|
||||
"name": "__extends$2"
|
||||
},
|
||||
{
|
||||
"name": "__extends$4"
|
||||
},
|
||||
{
|
||||
"name": "__extends$5"
|
||||
},
|
||||
{
|
||||
"name": "__extends$6"
|
||||
},
|
||||
{
|
||||
"name": "__global"
|
||||
},
|
||||
|
@ -174,10 +132,52 @@
|
|||
"name": "_c1"
|
||||
},
|
||||
{
|
||||
"name": "_devMode"
|
||||
"name": "_c10"
|
||||
},
|
||||
{
|
||||
"name": "_enable_super_gross_mode_that_will_cause_bad_things"
|
||||
"name": "_c11"
|
||||
},
|
||||
{
|
||||
"name": "_c12"
|
||||
},
|
||||
{
|
||||
"name": "_c13"
|
||||
},
|
||||
{
|
||||
"name": "_c14"
|
||||
},
|
||||
{
|
||||
"name": "_c15"
|
||||
},
|
||||
{
|
||||
"name": "_c16"
|
||||
},
|
||||
{
|
||||
"name": "_c2"
|
||||
},
|
||||
{
|
||||
"name": "_c3"
|
||||
},
|
||||
{
|
||||
"name": "_c4"
|
||||
},
|
||||
{
|
||||
"name": "_c5"
|
||||
},
|
||||
{
|
||||
"name": "_c6"
|
||||
},
|
||||
{
|
||||
"name": "_c7"
|
||||
},
|
||||
{
|
||||
"name": "_c8"
|
||||
},
|
||||
{
|
||||
"name": "_c9"
|
||||
},
|
||||
{
|
||||
"name": "_devMode"
|
||||
},
|
||||
{
|
||||
"name": "_getComponentHostLElementNode"
|
||||
|
@ -206,6 +206,9 @@
|
|||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
{
|
||||
"name": "assertTemplate"
|
||||
},
|
||||
{
|
||||
"name": "baseDirectiveCreate"
|
||||
},
|
||||
|
@ -239,9 +242,6 @@
|
|||
{
|
||||
"name": "componentRefresh"
|
||||
},
|
||||
{
|
||||
"name": "config"
|
||||
},
|
||||
{
|
||||
"name": "container"
|
||||
},
|
||||
|
@ -314,15 +314,9 @@
|
|||
{
|
||||
"name": "elementStart"
|
||||
},
|
||||
{
|
||||
"name": "empty"
|
||||
},
|
||||
{
|
||||
"name": "enterView"
|
||||
},
|
||||
{
|
||||
"name": "errorObject"
|
||||
},
|
||||
{
|
||||
"name": "executeHooks"
|
||||
},
|
||||
|
@ -362,9 +356,6 @@
|
|||
{
|
||||
"name": "firstTemplatePass"
|
||||
},
|
||||
{
|
||||
"name": "flattenUnsubscriptionErrors"
|
||||
},
|
||||
{
|
||||
"name": "generateInitialInputs"
|
||||
},
|
||||
|
@ -407,9 +398,6 @@
|
|||
{
|
||||
"name": "getPreviousOrParentNode"
|
||||
},
|
||||
{
|
||||
"name": "getPromiseCtor"
|
||||
},
|
||||
{
|
||||
"name": "getRenderer"
|
||||
},
|
||||
|
@ -428,9 +416,6 @@
|
|||
{
|
||||
"name": "hostElement"
|
||||
},
|
||||
{
|
||||
"name": "hostReportError"
|
||||
},
|
||||
{
|
||||
"name": "initBindings"
|
||||
},
|
||||
|
@ -458,9 +443,6 @@
|
|||
{
|
||||
"name": "invertObject"
|
||||
},
|
||||
{
|
||||
"name": "isArray"
|
||||
},
|
||||
{
|
||||
"name": "isCssClassMatching"
|
||||
},
|
||||
|
@ -470,9 +452,6 @@
|
|||
{
|
||||
"name": "isDifferent"
|
||||
},
|
||||
{
|
||||
"name": "isFunction"
|
||||
},
|
||||
{
|
||||
"name": "isJsObject"
|
||||
},
|
||||
|
@ -485,18 +464,12 @@
|
|||
{
|
||||
"name": "isNodeMatchingSelectorList"
|
||||
},
|
||||
{
|
||||
"name": "isObject"
|
||||
},
|
||||
{
|
||||
"name": "isPositive"
|
||||
},
|
||||
{
|
||||
"name": "isProceduralRenderer"
|
||||
},
|
||||
{
|
||||
"name": "isTrustedSubscriber"
|
||||
},
|
||||
{
|
||||
"name": "iterateListLike"
|
||||
},
|
||||
|
@ -506,6 +479,9 @@
|
|||
{
|
||||
"name": "listener"
|
||||
},
|
||||
{
|
||||
"name": "load"
|
||||
},
|
||||
{
|
||||
"name": "locateHostElement"
|
||||
},
|
||||
|
@ -524,18 +500,9 @@
|
|||
{
|
||||
"name": "markViewDirty"
|
||||
},
|
||||
{
|
||||
"name": "noop"
|
||||
},
|
||||
{
|
||||
"name": "notImplemented"
|
||||
},
|
||||
{
|
||||
"name": "observable"
|
||||
},
|
||||
{
|
||||
"name": "pipeFromArray"
|
||||
},
|
||||
{
|
||||
"name": "queueComponentIndexForCheck"
|
||||
},
|
||||
|
@ -590,9 +557,6 @@
|
|||
{
|
||||
"name": "resolveRendererType2"
|
||||
},
|
||||
{
|
||||
"name": "rxSubscriber"
|
||||
},
|
||||
{
|
||||
"name": "saveNameToExportMap"
|
||||
},
|
||||
|
@ -641,18 +605,9 @@
|
|||
{
|
||||
"name": "tick"
|
||||
},
|
||||
{
|
||||
"name": "toSubscriber"
|
||||
},
|
||||
{
|
||||
"name": "trackByIdentity"
|
||||
},
|
||||
{
|
||||
"name": "tryCatch"
|
||||
},
|
||||
{
|
||||
"name": "tryCatcher"
|
||||
},
|
||||
{
|
||||
"name": "viewAttached"
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
<head>
|
||||
<title>Angular Todo Example</title>
|
||||
<link rel="stylesheet" href="base.css">
|
||||
<link rel="stylesheet" href="todo.css">
|
||||
<style>
|
||||
.done {
|
||||
text-decoration: line-through;
|
||||
|
|
|
@ -6,73 +6,148 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule, NgForOf} from '@angular/common';
|
||||
import {CommonModule, NgForOf, NgIf} from '@angular/common';
|
||||
import {ChangeDetectionStrategy, Component, EventEmitter, InjectFlags, Injectable, Input, IterableDiffers, NgModule, Output, createInjector, defineInjector, inject, ɵComponentDef as ComponentDef, ɵComponentType as ComponentType, ɵDirectiveDef as DirectiveDef, ɵDirectiveType as DirectiveType, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefaultIterableDiffers as defaultIterableDiffers, ɵdefineDirective as defineDirective, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
|
||||
export interface ToDo {
|
||||
text: string;
|
||||
done: boolean;
|
||||
|
||||
export class Todo {
|
||||
editing: boolean;
|
||||
|
||||
private _title: string;
|
||||
get title() { return this._title; }
|
||||
set title(value: string) { this._title = value.trim(); }
|
||||
|
||||
constructor(title: string, public completed: boolean = false) {
|
||||
this.editing = false;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class AppState {
|
||||
todos: ToDo[] = [
|
||||
{text: 'Demonstrate Components', done: false},
|
||||
{text: 'Demonstrate Structural Directives', done: true},
|
||||
{text: 'Demonstrate NgModules', done: false},
|
||||
{text: 'Demonstrate zoneless change detection', done: false},
|
||||
{text: 'Demonstrate internationalization', done: false},
|
||||
|
||||
@Injectable()
|
||||
export class TodoStore {
|
||||
todos: Array<Todo> = [
|
||||
new Todo('Demonstrate Components'),
|
||||
new Todo('Demonstrate Structural Directives', true),
|
||||
new Todo('Demonstrate NgModules'),
|
||||
new Todo('Demonstrate zoneless change detection'),
|
||||
new Todo('Demonstrate internationalization'),
|
||||
];
|
||||
|
||||
static DEFAULT_TODO = {text: '', done: false};
|
||||
}
|
||||
private getWithCompleted(completed: boolean) {
|
||||
return this.todos.filter((todo: Todo) => todo.completed === completed);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'todo',
|
||||
// TODO(misko): `[class.done]` and `[value]` should be `todo.done` not `todo && todo.todo.done`
|
||||
// The reason for the guard is that template executes creation and binding together
|
||||
// but NgForOf expects creation and binding separate.
|
||||
template: `
|
||||
<div>
|
||||
<input type="checkbox" [checked]="todo && todo.done" (change)="onCheckboxClick()">&ngsp;
|
||||
<span [class.done]="todo && todo.done">{{todo && todo.text}}</span>&ngsp;
|
||||
<button (click)="onArchiveClick()">archive</button>
|
||||
</div>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TodoComponent {
|
||||
@Input()
|
||||
todo: ToDo = AppState.DEFAULT_TODO;
|
||||
allCompleted() { return this.todos.length === this.getCompleted().length; }
|
||||
|
||||
@Output()
|
||||
archive = new EventEmitter();
|
||||
setAllTo(completed: boolean) { this.todos.forEach((t: Todo) => t.completed = completed); }
|
||||
|
||||
onCheckboxClick() { this.todo.done = !this.todo.done; }
|
||||
removeCompleted() { this.todos = this.getWithCompleted(false); }
|
||||
|
||||
onArchiveClick() { this.archive.emit(this.todo); }
|
||||
getRemaining() { return this.getWithCompleted(false); }
|
||||
|
||||
getCompleted() { return this.getWithCompleted(true); }
|
||||
|
||||
toggleCompletion(todo: Todo) { todo.completed = !todo.completed; }
|
||||
|
||||
remove(todo: Todo) { this.todos.splice(this.todos.indexOf(todo), 1); }
|
||||
|
||||
add(title: string) { this.todos.push(new Todo(title)); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'todo-app',
|
||||
// TODO(misko) remove all `foo && foo.something` once `ViewContainerRef` can separate creation and
|
||||
// update block.
|
||||
// TODO(misko): make this work with `[(ngModel)]`
|
||||
template: `
|
||||
<h1>ToDo Application</h1>
|
||||
<div>
|
||||
<todo *ngFor="let todo of appState.todos" [todo]="todo" (archive)="onArchive($event)"></todo>
|
||||
</div>
|
||||
<span>count: {{appState.todos.length}}.</span>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" placeholder="What needs to be done?" autofocus=""
|
||||
[value]="newTodoText"
|
||||
(keyup)="$event.code == 'Enter' ? addTodo() : newTodoText = $event.target.value">
|
||||
</header>
|
||||
<section *ngIf="todoStore.todos.length > 0" class="main">
|
||||
<input *ngIf="todoStore.todos.length"
|
||||
#toggleall class="toggle-all" type="checkbox"
|
||||
[checked]="todoStore.allCompleted()"
|
||||
(click)="todoStore.setAllTo(toggleall.checked)">
|
||||
<ul class="todo-list">
|
||||
<li *ngFor="let todo of todoStore.todos"
|
||||
[class.completed]="todo && todo.completed"
|
||||
[class.editing]="todo && todo.editing">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox"
|
||||
(click)="toggleCompletion(todo)"
|
||||
[checked]="todo && todo.completed">
|
||||
<label (dblclick)="editTodo(todo)">{{todo && todo.title}}</label>
|
||||
<button class="destroy" (click)="remove(todo)"></button>
|
||||
</div>
|
||||
<input *ngIf="todo && todo.editing"
|
||||
class="edit" #editedtodo
|
||||
[value]="todo && todo.title"
|
||||
(blur)="stopEditing(todo, editedtodo.value)"
|
||||
(keyup)="todo.title = $event.target.value"
|
||||
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
||||
(keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer *ngIf="todoStore.todos.length > 0" class="footer">
|
||||
<span class="todo-count">
|
||||
<strong>{{todoStore.getRemaining().length}}</strong>
|
||||
{{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left
|
||||
</span>
|
||||
<button *ngIf="todoStore.getCompleted().length > 0"
|
||||
class="clear-completed"
|
||||
(click)="removeCompleted()">
|
||||
Clear completed
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
`,
|
||||
// TODO(misko): switch oven to OnPush
|
||||
// changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ToDoAppComponent {
|
||||
public appState: AppState;
|
||||
todoStore: TodoStore;
|
||||
newTodoText = '';
|
||||
|
||||
constructor(/**appState: AppState*/) {
|
||||
// TODO(misko): Injection is broken because the compiler generates incorrect code.
|
||||
this.appState = new AppState();
|
||||
// TODO(misko) Fix injection
|
||||
// constructor(todoStore: TodoStore) { this.todoStore = todoStore; }
|
||||
constructor() { this.todoStore = new TodoStore(); }
|
||||
|
||||
stopEditing(todo: Todo, editedTitle: string) {
|
||||
todo.title = editedTitle;
|
||||
todo.editing = false;
|
||||
}
|
||||
|
||||
onArchive(item: ToDo) {
|
||||
const todos = this.appState.todos;
|
||||
todos.splice(todos.indexOf(item), 1);
|
||||
cancelEditingTodo(todo: Todo) { todo.editing = false; }
|
||||
|
||||
updateEditingTodo(todo: Todo, editedTitle: string) {
|
||||
editedTitle = editedTitle.trim();
|
||||
todo.editing = false;
|
||||
|
||||
if (editedTitle.length === 0) {
|
||||
return this.todoStore.remove(todo);
|
||||
}
|
||||
|
||||
todo.title = editedTitle;
|
||||
}
|
||||
|
||||
editTodo(todo: Todo) { todo.editing = true; }
|
||||
|
||||
removeCompleted() { this.todoStore.removeCompleted(); }
|
||||
|
||||
toggleCompletion(todo: Todo) { this.todoStore.toggleCompletion(todo); }
|
||||
|
||||
remove(todo: Todo) { this.todoStore.remove(todo); }
|
||||
|
||||
addTodo() {
|
||||
if (this.newTodoText.trim().length) {
|
||||
this.todoStore.add(this.newTodoText);
|
||||
this.newTodoText = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,9 +175,18 @@ export class ToDoAppComponent {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
|
||||
(NgIf as any).ngDirectiveDef = defineDirective({
|
||||
type: NgIf,
|
||||
selectors: [['', 'ngIf', '']],
|
||||
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()),
|
||||
inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'}
|
||||
});
|
||||
|
||||
@NgModule({declarations: [ToDoAppComponent, TodoComponent], imports: [CommonModule]})
|
||||
|
||||
@NgModule({declarations: [ToDoAppComponent, ToDoAppComponent], imports: [CommonModule]})
|
||||
export class ToDoAppModule {
|
||||
}
|
||||
|
||||
renderComponent(ToDoAppComponent);
|
||||
// TODO(misko): create cleaner way to publish component into global location for tests.
|
||||
(window as any).toDoAppComponent = renderComponent(ToDoAppComponent);
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
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;
|
||||
appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4em;
|
||||
background: #f5f5f5;
|
||||
color: #4d4d4d;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="checkbox"] {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
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);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-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;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:after {
|
||||
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked:after {
|
||||
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
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;
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ɵwhenRendered as whenRendered} from '@angular/core';
|
||||
import {withBody} from '@angular/core/testing';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
@ -19,11 +20,16 @@ const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js'];
|
|||
describe('functional test for todo', () => {
|
||||
BUNDLES.forEach(bundle => {
|
||||
describe(bundle, () => {
|
||||
it('should render todo', withBody('<todo-app></todo-app>', () => {
|
||||
it('should render todo', withBody('<todo-app></todo-app>', async() => {
|
||||
require(path.join(PACKAGE, bundle));
|
||||
expect(document.body.textContent).toContain('ToDo Application');
|
||||
expect(document.body.textContent).toContain('count: 5.');
|
||||
// TODO(misko): have cleaner way to do this for tests.
|
||||
const toDoAppComponent = (window as any).toDoAppComponent;
|
||||
expect(document.body.textContent).toContain('todos');
|
||||
expect(document.body.textContent).toContain('Demonstrate Components');
|
||||
expect(document.body.textContent).toContain('4 items left');
|
||||
document.querySelector('button') !.click();
|
||||
await whenRendered(toDoAppComponent);
|
||||
expect(document.body.textContent).toContain('3 items left');
|
||||
}));
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue