+
diff --git a/Sprint-3/todo-list/package.json b/Sprint-3/todo-list/package.json
index ce181158a..440674b2a 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",
@@ -17,7 +17,8 @@
},
"homepage": "https://github.com/CodeYourFuture/CYF-Coursework-Template#readme",
"devDependencies": {
+ "cross-env": "^10.1.0",
"http-server": "^14.1.1",
- "jest": "^30.0.4"
+ "jest": "^30.2.0"
}
}
diff --git a/Sprint-3/todo-list/script.mjs b/Sprint-3/todo-list/script.mjs
index ba0b2ceae..d65768b42 100644
--- a/Sprint-3/todo-list/script.mjs
+++ b/Sprint-3/todo-list/script.mjs
@@ -1,4 +1,4 @@
-// Store everything imported from './todos.mjs' module as properties of an object named Todos
+// Store everything imported from './todos.mjs' module as properties of an object named Todos
import * as Todos from "./todos.mjs";
// To store the todo tasks
@@ -7,16 +7,18 @@ const todos = [];
// Set up tasks to be performed once on page load
window.addEventListener("load", () => {
document.getElementById("add-task-btn").addEventListener("click", addNewTodo);
+ document
+ .getElementById("delete-completed-btn")
+ .addEventListener("click", deleteCompletedTodos);
// Populate sample data
- Todos.addTask(todos, "Wash the dishes", false);
+ Todos.addTask(todos, "Wash the dishes", false);
Todos.addTask(todos, "Do the shopping", true);
render();
});
-
-// A callback that reads the task description from an input field and
+// A callback that reads the task description from an input field and
// append a new task to the todo list.
function addNewTodo() {
const taskInput = document.getElementById("new-task-input");
@@ -29,6 +31,12 @@ function addNewTodo() {
taskInput.value = "";
}
+// A callback that deletes all completed tasks
+function deleteCompletedTodos() {
+ Todos.deleteCompleted(todos);
+ render();
+}
+
// Note:
// - Store the reference to the
element with id "todo-list" here
// to avoid querying the DOM repeatedly inside render().
@@ -45,12 +53,11 @@ function render() {
});
}
-
// Note:
// - First child of #todo-item-template is a
element.
// We will create each ToDo list item as a clone of this node.
// - This variable is declared here to be close to the only function that uses it.
-const todoListItemTemplate =
+const todoListItemTemplate =
document.getElementById("todo-item-template").content.firstElementChild;
// Create a
element for the given todo task
@@ -62,15 +69,15 @@ function createListItem(todo, index) {
li.classList.add("completed");
}
- li.querySelector('.complete-btn').addEventListener("click", () => {
+ li.querySelector(".complete-btn").addEventListener("click", () => {
Todos.toggleCompletedOnTask(todos, index);
render();
});
-
- li.querySelector('.delete-btn').addEventListener("click", () => {
+
+ li.querySelector(".delete-btn").addEventListener("click", () => {
Todos.deleteTask(todos, index);
render();
});
return li;
-}
\ No newline at end of file
+}
diff --git a/Sprint-3/todo-list/style.css b/Sprint-3/todo-list/style.css
index 535e91227..bb2f80654 100644
--- a/Sprint-3/todo-list/style.css
+++ b/Sprint-3/todo-list/style.css
@@ -41,7 +41,7 @@ h1 {
.todo-input button {
padding: 10px 20px;
font-size: 16px;
- background-color: #4CAF50;
+ background-color: #4caf50;
color: white;
border: none;
border-radius: 6px;
@@ -105,3 +105,23 @@ h1 {
text-decoration: line-through;
color: gray;
}
+
+/* Styling for delete completed button */
+.todo-actions {
+ margin-top: 20px;
+ text-align: center;
+}
+
+.delete-completed-btn {
+ padding: 10px 20px;
+ font-size: 16px;
+ background-color: #f44336;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+.delete-completed-btn:hover {
+ background-color: #da190b;
+}
diff --git a/Sprint-3/todo-list/todos.mjs b/Sprint-3/todo-list/todos.mjs
index f17ab6a25..a7b245601 100644
--- a/Sprint-3/todo-list/todos.mjs
+++ b/Sprint-3/todo-list/todos.mjs
@@ -26,4 +26,15 @@ export function toggleCompletedOnTask(todos, taskIndex) {
if (todos[taskIndex]) {
todos[taskIndex].completed = !todos[taskIndex].completed;
}
-}
\ No newline at end of file
+}
+
+// Delete all completed tasks from the todos list
+export function deleteCompleted(todos) {
+ // Filter out all completed tasks, keeping only incomplete ones
+ // We iterate backwards to avoid index shifting issues
+ for (let i = todos.length - 1; i >= 0; i--) {
+ if (todos[i].completed) {
+ todos.splice(i, 1);
+ }
+ }
+}
diff --git a/Sprint-3/todo-list/todos.test.mjs b/Sprint-3/todo-list/todos.test.mjs
index bae7ae491..0dd18bea9 100644
--- a/Sprint-3/todo-list/todos.test.mjs
+++ b/Sprint-3/todo-list/todos.test.mjs
@@ -13,7 +13,7 @@ function createMockTodos() {
{ task: "Task 1 description", completed: true },
{ task: "Task 2 description", completed: false },
{ task: "Task 3 description", completed: true },
- { task: "Task 4 description", completed: false },
+ { task: "Task 4 description", completed: false },
];
}
@@ -29,7 +29,6 @@ describe("addTask()", () => {
});
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);
@@ -42,7 +41,6 @@ describe("addTask()", () => {
});
describe("deleteTask()", () => {
-
test("Delete the first task", () => {
const todos = createMockTodos();
const todosBeforeDeletion = createMockTodos();
@@ -53,7 +51,7 @@ describe("deleteTask()", () => {
expect(todos[0]).toEqual(todosBeforeDeletion[1]);
expect(todos[1]).toEqual(todosBeforeDeletion[2]);
- expect(todos[2]).toEqual(todosBeforeDeletion[3]);
+ expect(todos[2]).toEqual(todosBeforeDeletion[3]);
});
test("Delete the second task (a middle task)", () => {
@@ -66,7 +64,7 @@ describe("deleteTask()", () => {
expect(todos[0]).toEqual(todosBeforeDeletion[0]);
expect(todos[1]).toEqual(todosBeforeDeletion[2]);
- expect(todos[2]).toEqual(todosBeforeDeletion[3]);
+ expect(todos[2]).toEqual(todosBeforeDeletion[3]);
});
test("Delete the last task", () => {
@@ -79,7 +77,7 @@ describe("deleteTask()", () => {
expect(todos[0]).toEqual(todosBeforeDeletion[0]);
expect(todos[1]).toEqual(todosBeforeDeletion[1]);
- expect(todos[2]).toEqual(todosBeforeDeletion[2]);
+ expect(todos[2]).toEqual(todosBeforeDeletion[2]);
});
test("Delete a non-existing task", () => {
@@ -94,7 +92,6 @@ describe("deleteTask()", () => {
});
describe("toggleCompletedOnTask()", () => {
-
test("Expect the 'completed' property to toggle on an existing task", () => {
const todos = createMockTodos();
const taskIndex = 1;
@@ -111,13 +108,12 @@ describe("toggleCompletedOnTask()", () => {
const todos = createMockTodos();
const todosBeforeToggle = createMockTodos();
Todos.toggleCompletedOnTask(todos, 1);
-
- expect(todos[0]).toEqual(todosBeforeToggle[0]);
+
+ 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();
@@ -130,3 +126,78 @@ describe("toggleCompletedOnTask()", () => {
});
});
+describe("deleteCompleted()", () => {
+ test("Delete all completed tasks from a mixed list", () => {
+ const todos = createMockTodos();
+ // Mock list has 2 completed tasks (Task 1 and Task 3)
+ Todos.deleteCompleted(todos);
+
+ expect(todos).toHaveLength(2);
+ expect(todos[0]).toEqual({ task: "Task 2 description", completed: false });
+ expect(todos[1]).toEqual({ task: "Task 4 description", completed: false });
+ });
+
+ test("Delete completed tasks from a list with no completed tasks", () => {
+ const todos = [
+ { task: "Task 1", completed: false },
+ { task: "Task 2", completed: false },
+ ];
+ const todosBeforeDeletion = [...todos];
+
+ Todos.deleteCompleted(todos);
+
+ expect(todos).toEqual(todosBeforeDeletion);
+ expect(todos).toHaveLength(2);
+ });
+
+ test("Delete completed tasks from a list where all tasks are completed", () => {
+ const todos = [
+ { task: "Task 1", completed: true },
+ { task: "Task 2", completed: true },
+ { task: "Task 3", completed: true },
+ ];
+
+ Todos.deleteCompleted(todos);
+
+ expect(todos).toHaveLength(0);
+ });
+
+ test("Delete completed tasks from an empty list", () => {
+ const todos = [];
+
+ Todos.deleteCompleted(todos);
+
+ expect(todos).toHaveLength(0);
+ });
+
+ test("Delete completed tasks from a mixed list where the last item is completed", () => {
+ const todos = [
+ { task: "Task 1", completed: false },
+ { task: "Task 2", completed: true },
+ { task: "Task 3", completed: false },
+ { task: "Task 4", completed: true },
+ ];
+
+ Todos.deleteCompleted(todos);
+
+ expect(todos).toHaveLength(2);
+ expect(todos[0]).toEqual({ task: "Task 1", completed: false });
+ expect(todos[1]).toEqual({ task: "Task 3", completed: false });
+ });
+
+ test("Delete completed tasks from a mixed list with consecutive completed tasks", () => {
+ const todos = [
+ { task: "Task 1", completed: false },
+ { task: "Task 2", completed: true },
+ { task: "Task 3", completed: true },
+ { task: "Task 4", completed: true },
+ { task: "Task 5", completed: false },
+ ];
+
+ Todos.deleteCompleted(todos);
+
+ expect(todos).toHaveLength(2);
+ expect(todos[0]).toEqual({ task: "Task 1", completed: false });
+ expect(todos[1]).toEqual({ task: "Task 5", completed: false });
+ });
+});