diff --git a/Sprint-3/todo-list/index.html b/Sprint-3/todo-list/index.html index 4d12c4654..081ef1507 100644 --- a/Sprint-3/todo-list/index.html +++ b/Sprint-3/todo-list/index.html @@ -18,6 +18,8 @@

My ToDo List

+ + diff --git a/Sprint-3/todo-list/package.json b/Sprint-3/todo-list/package.json index ce181158a..3676a1836 100644 --- a/Sprint-3/todo-list/package.json +++ b/Sprint-3/todo-list/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "serve": "http-server", - "test": "NODE_OPTIONS=--experimental-vm-modules jest" + "test": "set NODE_OPTIONS=--experimental-vm-modules && jest" }, "repository": { "type": "git", @@ -20,4 +20,4 @@ "http-server": "^14.1.1", "jest": "^30.0.4" } -} +} \ No newline at end of file diff --git a/Sprint-3/todo-list/script.mjs b/Sprint-3/todo-list/script.mjs index ba0b2ceae..b2d0f88ce 100644 --- a/Sprint-3/todo-list/script.mjs +++ b/Sprint-3/todo-list/script.mjs @@ -8,6 +8,13 @@ const todos = []; window.addEventListener("load", () => { document.getElementById("add-task-btn").addEventListener("click", addNewTodo); + document + .getElementById("delete-completed-btn") + .addEventListener("click", () => { + Todos.deleteCompleted(todos); + render(); + }); + // Populate sample data Todos.addTask(todos, "Wash the dishes", false); Todos.addTask(todos, "Do the shopping", true); diff --git a/Sprint-3/todo-list/todos.js b/Sprint-3/todo-list/todos.js new file mode 100644 index 000000000..e617c472e --- /dev/null +++ b/Sprint-3/todo-list/todos.js @@ -0,0 +1,23 @@ +// Add a new task +export function addTask(todos, task, completed) { + todos.push({ task, completed }); +} + +// Delete a task by index +export function deleteTask(todos, index) { + if (index < 0 || index >= todos.length) return; + todos.splice(index, 1); +} + +// Toggle completed status +export function toggleCompletedOnTask(todos, index) { + if (index < 0 || index >= todos.length) return; + todos[index].completed = !todos[index].completed; +} + +// Delete all completed tasks +export function deleteCompleted(todos) { + const remaining = todos.filter((todo) => !todo.completed); + todos.length = 0; + todos.push(...remaining); +} \ No newline at end of file diff --git a/Sprint-3/todo-list/todos.test.js b/Sprint-3/todo-list/todos.test.js new file mode 100644 index 000000000..36da5134f --- /dev/null +++ b/Sprint-3/todo-list/todos.test.js @@ -0,0 +1,169 @@ +// The tests is prepared to demonstrate we can test the functions +// in a module independently. + +// Command to execute this script: +// npm test todos.test.mjs + +// Import all the exported members through an object +import * as Todos from "./todos.js"; + +// Return a mock ToDo List data with exactly 4 elements. +function createMockTodos() { + return [ + { task: "Task 1 description", completed: true }, + { task: "Task 2 description", completed: false }, + { task: "Task 3 description", completed: true }, + { task: "Task 4 description", completed: false }, + ]; +} + +// A mock task to simulate user input +const theTask = { task: "The Task", completed: false }; + +describe("addTask()", () => { + test("Add a task to an empty ToDo list", () => { + let todos = []; + Todos.addTask(todos, theTask.task, theTask.completed); + expect(todos).toHaveLength(1); + expect(todos[0]).toEqual(theTask); + }); + + test("Should append a new task to the end of a ToDo list", () => { + + const todos = createMockTodos(); + const lengthBeforeAddition = todos.length; + Todos.addTask(todos, theTask.task, theTask.completed); + // todos should now have one more task + expect(todos).toHaveLength(lengthBeforeAddition + 1); + + // New task should be appended to the todos + expect(todos[todos.length - 1]).toEqual(theTask); + }); +}); + +describe("deleteTask()", () => { + + test("Delete the first task", () => { + const todos = createMockTodos(); + const todosBeforeDeletion = createMockTodos(); + const lengthBeforeDeletion = todos.length; + Todos.deleteTask(todos, 0); + + expect(todos).toHaveLength(lengthBeforeDeletion - 1); + + expect(todos[0]).toEqual(todosBeforeDeletion[1]); + expect(todos[1]).toEqual(todosBeforeDeletion[2]); + expect(todos[2]).toEqual(todosBeforeDeletion[3]); + }); + + test("Delete the second task (a middle task)", () => { + const todos = createMockTodos(); + const todosBeforeDeletion = createMockTodos(); + const lengthBeforeDeletion = todos.length; + Todos.deleteTask(todos, 1); + + expect(todos).toHaveLength(lengthBeforeDeletion - 1); + + expect(todos[0]).toEqual(todosBeforeDeletion[0]); + expect(todos[1]).toEqual(todosBeforeDeletion[2]); + expect(todos[2]).toEqual(todosBeforeDeletion[3]); + }); + + test("Delete the last task", () => { + const todos = createMockTodos(); + const todosBeforeDeletion = createMockTodos(); + const lengthBeforeDeletion = todos.length; + Todos.deleteTask(todos, todos.length - 1); + + expect(todos).toHaveLength(lengthBeforeDeletion - 1); + + expect(todos[0]).toEqual(todosBeforeDeletion[0]); + expect(todos[1]).toEqual(todosBeforeDeletion[1]); + expect(todos[2]).toEqual(todosBeforeDeletion[2]); + }); + + test("Delete a non-existing task", () => { + const todos = createMockTodos(); + const todosBeforeDeletion = createMockTodos(); + Todos.deleteTask(todos, 10); + expect(todos).toEqual(todosBeforeDeletion); + + Todos.deleteTask(todos, -1); + expect(todos).toEqual(todosBeforeDeletion); + }); +}); + +describe("toggleCompletedOnTask()", () => { + + test("Expect the 'completed' property to toggle on an existing task", () => { + const todos = createMockTodos(); + const taskIndex = 1; + const completedStateBeforeToggle = todos[taskIndex].completed; + Todos.toggleCompletedOnTask(todos, taskIndex); + expect(todos[taskIndex].completed).toEqual(!completedStateBeforeToggle); + + // Toggle again + Todos.toggleCompletedOnTask(todos, taskIndex); + expect(todos[taskIndex].completed).toEqual(completedStateBeforeToggle); + }); + + test("Expect toggling on a task does not affect other tasks", () => { + const todos = createMockTodos(); + const todosBeforeToggle = createMockTodos(); + Todos.toggleCompletedOnTask(todos, 1); + + expect(todos[0]).toEqual(todosBeforeToggle[0]); + expect(todos[2]).toEqual(todosBeforeToggle[2]); + expect(todos[3]).toEqual(todosBeforeToggle[3]); + }); + + + test("Expect no change when toggling on a non-existing task", () => { + const todos = createMockTodos(); + const todosBeforeToggle = createMockTodos(); + + Todos.toggleCompletedOnTask(todos, 10); + expect(todos).toEqual(todosBeforeToggle); + + Todos.toggleCompletedOnTask(todos, -1); + expect(todos).toEqual(todosBeforeToggle); + }); +}); + +describe("deleteCompleted()", () => { + test("removes all completed tasks from the list", () => { + const todos = createMockTodos(); + + Todos.deleteCompleted(todos); + + expect(todos).toEqual([ + { task: "Task 2 description", completed: false }, + { task: "Task 4 description", completed: false }, + ]); + }); + + test("does nothing if no tasks are completed", () => { + const todos = [ + { task: "Task A", completed: false }, + { task: "Task B", completed: false }, + ]; + + Todos.deleteCompleted(todos); + + expect(todos).toEqual([ + { task: "Task A", completed: false }, + { task: "Task B", completed: false }, + ]); + }); + + test("removes all tasks if all are completed", () => { + const todos = [ + { task: "Task A", completed: true }, + { task: "Task B", completed: true }, + ]; + + Todos.deleteCompleted(todos); + + expect(todos).toEqual([]); + }); +}); \ No newline at end of file