From b8fc2a5624777faf1b34d1be5d94d5e3371ed063 Mon Sep 17 00:00:00 2001 From: Victoria Wika Date: Sun, 25 Apr 2021 19:43:02 +0200 Subject: [PATCH] Finish task #29 and update tests --- src/main/webapp/app/app.component.html | 8 +++++- src/main/webapp/app/app.component.ts | 8 ++++++ .../app/tasks/default-task.service.spec.ts | 24 ++++++++++++++++ .../webapp/app/tasks/default-task.service.ts | 8 ++++++ .../app/tasks/local-task.service.spec.ts | 21 +++++++++++++- .../webapp/app/tasks/local-task.service.ts | 21 ++++++++++++-- .../task-form/task-form.component.spec.ts | 8 +++--- .../tasks/task-list/task-list.component.html | 4 ++- .../tasks/task-list/task-list.component.scss | 19 +++++++++++++ .../task-list/task-list.component.spec.ts | 28 +++++++++++++++++-- .../tasks/task-list/task-list.component.ts | 14 ++++++++++ src/main/webapp/app/tasks/task.service.ts | 15 ++++++++++ src/main/webapp/app/tasks/task.ts | 1 + src/main/webapp/app/tasks/tasks.module.ts | 6 ++-- 14 files changed, 171 insertions(+), 14 deletions(-) diff --git a/src/main/webapp/app/app.component.html b/src/main/webapp/app/app.component.html index a0a038e6..92047be0 100644 --- a/src/main/webapp/app/app.component.html +++ b/src/main/webapp/app/app.component.html @@ -9,7 +9,13 @@
- + +
diff --git a/src/main/webapp/app/app.component.ts b/src/main/webapp/app/app.component.ts index 79b6c817..f77989f7 100644 --- a/src/main/webapp/app/app.component.ts +++ b/src/main/webapp/app/app.component.ts @@ -27,4 +27,12 @@ export class AppComponent implements OnInit { deleted(): void { this.tasks$ = this.taskService.getAll(); } + + togglecompleted(): void { + this.tasks$ = this.taskService.getAll(); + } + + clearcompleted(): void { + this.tasks$ = this.taskService.getAll(); + } } diff --git a/src/main/webapp/app/tasks/default-task.service.spec.ts b/src/main/webapp/app/tasks/default-task.service.spec.ts index e71377b3..3d5b847c 100644 --- a/src/main/webapp/app/tasks/default-task.service.spec.ts +++ b/src/main/webapp/app/tasks/default-task.service.spec.ts @@ -61,4 +61,28 @@ describe('DefaultTaskService', () => { // finally req.flush({}); }); + + it('should update completed status of a task', () => { + // when + taskService.togglecomplete('id123').subscribe(); + + // then + const req = httpTestingController.expectOne(request => request.url === 'http://backend.tld/tasks/id123/completed'); + expect(req.request.method).toEqual('PUT'); + + // finally + req.flush({}); + }); + + it('should delete all tasks', () => { + // when + taskService.clearcomplete().subscribe(); + + // then + const req = httpTestingController.expectOne(request => request.url === 'http://backend.tld/tasks/completed'); + expect(req.request.method).toEqual('DELETE'); + + // finally + req.flush({}); + }); }); diff --git a/src/main/webapp/app/tasks/default-task.service.ts b/src/main/webapp/app/tasks/default-task.service.ts index 594b78b7..2a83e9c8 100644 --- a/src/main/webapp/app/tasks/default-task.service.ts +++ b/src/main/webapp/app/tasks/default-task.service.ts @@ -20,6 +20,14 @@ export class DefaultTaskService implements TaskService { return this.http.delete(this.baseUrl + '/tasks/' + id); } + togglecomplete(id:string): Observable { + return this.http.put(this.baseUrl + '/tasks/' + id + '/completed', {id} as Task); + } + + clearcomplete(): Observable { + return this.http.delete(this.baseUrl + '/tasks/completed'); + } + getAll(): Observable { return this.http.get(this.baseUrl + '/tasks'); } diff --git a/src/main/webapp/app/tasks/local-task.service.spec.ts b/src/main/webapp/app/tasks/local-task.service.spec.ts index 9acad508..272eef47 100644 --- a/src/main/webapp/app/tasks/local-task.service.spec.ts +++ b/src/main/webapp/app/tasks/local-task.service.spec.ts @@ -6,7 +6,8 @@ import { Task } from './task'; describe('LocalTaskService', () => { const id = 'de4f576e-d1b5-488a-8c77-63d4c8726909'; const name = 'Doing the do!'; - const mockTask = `{"id":"${id}","name":"${name}"}`; + const completed = false; + const mockTask = `{"id":"${id}","name":"${name}", "completed":"${completed}"}`; let taskService: LocalTaskService; @@ -52,4 +53,22 @@ describe('LocalTaskService', () => { expect(localStorage.getItem).toHaveBeenCalled(); expect(localStorage.setItem).toHaveBeenCalled(); }); + + it('should update the completed status of a task in local storage', () => { + // when + taskService.togglecomplete(id); + + // then + expect(localStorage.getItem).toHaveBeenCalled(); + expect(localStorage.setItem).toHaveBeenCalled(); + }); + + it('should delete all completed tasks from local storage', () => { + // when + taskService.clearcomplete(); + + // then + expect(localStorage.getItem).toHaveBeenCalled(); + expect(localStorage.setItem).toHaveBeenCalled(); + }); }); diff --git a/src/main/webapp/app/tasks/local-task.service.ts b/src/main/webapp/app/tasks/local-task.service.ts index 7c384e5a..a7c6fdf6 100644 --- a/src/main/webapp/app/tasks/local-task.service.ts +++ b/src/main/webapp/app/tasks/local-task.service.ts @@ -11,12 +11,12 @@ export class LocalTaskService implements TaskService { private static readonly STORAGE_KEY: string = 'tiny.tasks'; getAll(): Observable { - return of(this.readTasks()); + return of(this.readTasks().sort((x,y) => x.completed === y.completed ? 0 : x? 1 : -1)); } create(name: string): Observable { const tasks = this.readTasks(); - const task = {id: uuid(), name}; + const task = {id: uuid(), name, completed: false}; tasks.push(task); this.writeTasks(tasks); return of(task); @@ -32,6 +32,23 @@ export class LocalTaskService implements TaskService { return of(null); } + togglecomplete(id:string): Observable { + const tasks = this.readTasks(); + tasks.map(task => { + if ( task.id === id) { + task.completed = !task.completed; + this.writeTasks(tasks); + } + }) + return of(null); + } + + clearcomplete(): Observable { + const tasks = this.readTasks(); + this.writeTasks(tasks.filter(task => task.completed === false)); + return of(null); + } + private readTasks(): Task[] { const tasks = localStorage.getItem(LocalTaskService.STORAGE_KEY); return tasks ? JSON.parse(tasks) : []; diff --git a/src/main/webapp/app/tasks/task-form/task-form.component.spec.ts b/src/main/webapp/app/tasks/task-form/task-form.component.spec.ts index bf10acbb..df11867a 100644 --- a/src/main/webapp/app/tasks/task-form/task-form.component.spec.ts +++ b/src/main/webapp/app/tasks/task-form/task-form.component.spec.ts @@ -40,7 +40,7 @@ describe('TaskFormComponent', () => { it('should create a task', () => { // given component.taskForm.setValue({name: 'My task'}); - taskService.create.and.returnValue(of({id: 'id', name: 'My task'})); + taskService.create.and.returnValue(of({id: 'id', name: 'My task', completed: false})); // when component.onSubmit(); @@ -52,20 +52,20 @@ describe('TaskFormComponent', () => { it('should emit the task after creation', () => { // given component.taskForm.setValue({name: 'My task'}); - taskService.create.and.returnValue(of({id: 'id', name: 'My task'})); + taskService.create.and.returnValue(of({id: 'id', name: 'My task', completed: false})); const createEmitter = spyOn(component.created, 'emit'); // when component.onSubmit(); // then - expect(createEmitter).toHaveBeenCalledWith({id: 'id', name: 'My task'}); + expect(createEmitter).toHaveBeenCalledWith({id: 'id', name: 'My task', completed: false}); }); it('should reset the form after creation', () => { // given component.taskForm.setValue({name: 'My task'}); - taskService.create.and.returnValue(of({id: 'id', name: 'My task'})); + taskService.create.and.returnValue(of({id: 'id', name: 'My task', completed: false})); const formReset = spyOn(component.taskForm, 'reset'); // when diff --git a/src/main/webapp/app/tasks/task-list/task-list.component.html b/src/main/webapp/app/tasks/task-list/task-list.component.html index 0e23f84d..eefa9d1d 100644 --- a/src/main/webapp/app/tasks/task-list/task-list.component.html +++ b/src/main/webapp/app/tasks/task-list/task-list.component.html @@ -1,9 +1,11 @@ assignment -

{{task.name}}

+ +

{{task.name}}

+
diff --git a/src/main/webapp/app/tasks/task-list/task-list.component.scss b/src/main/webapp/app/tasks/task-list/task-list.component.scss index 9da605dd..c1b2f929 100644 --- a/src/main/webapp/app/tasks/task-list/task-list.component.scss +++ b/src/main/webapp/app/tasks/task-list/task-list.component.scss @@ -1,3 +1,9 @@ +.mat-list > button { + display: block; + margin: 0 auto; + padding: 5px 20px; +} + .mat-list-item { background: #fff; margin: 16px 0; @@ -8,6 +14,19 @@ user-select: none; } + .mat-list-checkbox { + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + } + + .completed { + text-decoration: line-through; + color: #c8c8c8; + } + button .mat-icon { color: #101d30; } diff --git a/src/main/webapp/app/tasks/task-list/task-list.component.spec.ts b/src/main/webapp/app/tasks/task-list/task-list.component.spec.ts index 5ee0eaa7..2a2a409d 100644 --- a/src/main/webapp/app/tasks/task-list/task-list.component.spec.ts +++ b/src/main/webapp/app/tasks/task-list/task-list.component.spec.ts @@ -36,7 +36,7 @@ describe('TaskListComponent', () => { taskService.delete.and.returnValue(of(null)); // when - component.delete({id: 'id', name: 'My task'}); + component.delete({id: 'id', name: 'My task', completed: false}); // then expect(taskService.delete).toHaveBeenCalledWith('id'); @@ -48,9 +48,31 @@ describe('TaskListComponent', () => { const deleteEmitter = spyOn(component.deleted, 'emit'); // when - component.delete({id: 'id', name: 'My task'}); + component.delete({id: 'id', name: 'My task', completed: false}); // then - expect(deleteEmitter).toHaveBeenCalledWith({id: 'id', name: 'My task'}); + expect(deleteEmitter).toHaveBeenCalledWith({id: 'id', name: 'My task', completed: false}); + }); + + it('should mark a task as completed', () => { + // given + taskService.togglecomplete.and.returnValue(of(null)); + + // when + component.togglecomplete({id: 'id', name: 'My task', completed: false}); + + // then + expect(taskService.togglecomplete).toHaveBeenCalledWith('id'); + }); + + it('should delete all completed tasks', () => { + // given + taskService.clearcomplete.and.returnValue(of(null)); + + // when + component.clearcomplete(); + + // then + expect(taskService.clearcomplete).toHaveBeenCalled(); }); }); diff --git a/src/main/webapp/app/tasks/task-list/task-list.component.ts b/src/main/webapp/app/tasks/task-list/task-list.component.ts index 0bb38687..c6e538a4 100644 --- a/src/main/webapp/app/tasks/task-list/task-list.component.ts +++ b/src/main/webapp/app/tasks/task-list/task-list.component.ts @@ -18,6 +18,10 @@ export class TaskListComponent { @Output() deleted: EventEmitter = new EventEmitter(); + @Output() togglecompleted: EventEmitter = new EventEmitter(); + + @Output() clearcompleted: EventEmitter = new EventEmitter(); + constructor(@Inject('TaskService') private taskService: TaskService) { } delete(task: Task): void { @@ -25,4 +29,14 @@ export class TaskListComponent { this.deleted.emit(task); }); } + togglecomplete(task: Task) { + this.taskService.togglecomplete(task.id).subscribe(() => { + this.togglecompleted.emit(task); + }); + } + clearcomplete() { + this.taskService.clearcomplete().subscribe(() => { + this.clearcompleted.emit(); + }); + } } diff --git a/src/main/webapp/app/tasks/task.service.ts b/src/main/webapp/app/tasks/task.service.ts index a1afa21b..d8c654eb 100644 --- a/src/main/webapp/app/tasks/task.service.ts +++ b/src/main/webapp/app/tasks/task.service.ts @@ -29,4 +29,19 @@ export interface TaskService { * @returns an empty `Observable` */ delete(id: string): Observable; + + /** + * Toggles the completion status of a task with the given ID from the list of tasks. + * + * @param id the ID of the task to be removed + * @returns an empty `Observable` + */ + togglecomplete(id: string): Observable; + + /** + * Clears the completed tasks from the list of tasks. + * + * @returns an empty `Observable` + */ + clearcomplete(): Observable; } diff --git a/src/main/webapp/app/tasks/task.ts b/src/main/webapp/app/tasks/task.ts index 81947a34..62350489 100644 --- a/src/main/webapp/app/tasks/task.ts +++ b/src/main/webapp/app/tasks/task.ts @@ -4,4 +4,5 @@ export interface Task { id: string; name: string; + completed: boolean; } diff --git a/src/main/webapp/app/tasks/tasks.module.ts b/src/main/webapp/app/tasks/tasks.module.ts index 5a9fe4fe..e882d5b2 100644 --- a/src/main/webapp/app/tasks/tasks.module.ts +++ b/src/main/webapp/app/tasks/tasks.module.ts @@ -2,13 +2,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; - import { TaskFormComponent } from './task-form/task-form.component'; import { TaskListComponent } from './task-list/task-list.component'; + @NgModule({ declarations: [TaskFormComponent, TaskListComponent], imports: [ @@ -17,7 +18,8 @@ import { TaskListComponent } from './task-list/task-list.component'; MatButtonModule, MatIconModule, MatInputModule, - MatListModule + MatListModule, + MatCheckboxModule ], exports: [TaskFormComponent, TaskListComponent] })