diff --git a/Sprint-3/todo-list/index.html b/Sprint-3/todo-list/index.html
index 4d12c4654..5e1785496 100644
--- a/Sprint-3/todo-list/index.html
+++ b/Sprint-3/todo-list/index.html
@@ -15,6 +15,7 @@
My ToDo List
+
diff --git a/Sprint-3/todo-list/script.mjs b/Sprint-3/todo-list/script.mjs
index ba0b2ceae..5e0f23519 100644
--- a/Sprint-3/todo-list/script.mjs
+++ b/Sprint-3/todo-list/script.mjs
@@ -10,7 +10,7 @@ window.addEventListener("load", () => {
// Populate sample data
Todos.addTask(todos, "Wash the dishes", false);
- Todos.addTask(todos, "Do the shopping", true);
+ Todos.addTask(todos, "Do the shopping", false); // if 2nd argument is true the msg 'Do the shopping' is crossed out
render();
});
@@ -21,8 +21,11 @@ window.addEventListener("load", () => {
function addNewTodo() {
const taskInput = document.getElementById("new-task-input");
const task = taskInput.value.trim();
+ const deadlineInput = document.getElementById("new-task-deadline");
+ const deadline = deadlineInput.value || null;
+
if (task) {
- Todos.addTask(todos, task, false);
+ Todos.addTask(todos, task, false, deadline);
render();
}
@@ -73,4 +76,4 @@ function createListItem(todo, index) {
});
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..2afd54b07 100644
--- a/Sprint-3/todo-list/style.css
+++ b/Sprint-3/todo-list/style.css
@@ -2,106 +2,215 @@
box-sizing: border-box;
margin: 0;
padding: 0;
- font-family: Arial, sans-serif;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
- background-color: #f4f4f4;
- padding: 40px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ padding: 40px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.todo-container {
- max-width: 500px;
+ max-width: 650px;
+ width: 100%;
margin: 0 auto;
- background: white;
- border-radius: 10px;
- padding: 20px;
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+ background: rgba(255, 255, 255, 0.98);
+ border-radius: 32px;
+ padding: 36px 32px 44px;
+ box-shadow: 0 25px 45px -12px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
+ transition: transform 0.2s ease;
+}
+
+.todo-container:hover {
+ transform: scale(1.01);
}
h1 {
text-align: center;
- margin-bottom: 20px;
+ font-size: 2.2rem;
+ font-weight: 700;
+ background: linear-gradient(120deg, #2d3748, #4a5568);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ margin-bottom: 32px;
+ letter-spacing: -0.5px;
}
+/* Input area – both field and button fully inside the rounded container */
.todo-input {
display: flex;
- gap: 10px;
- margin-bottom: 20px;
+ gap: 14px;
+ margin-bottom: 36px;
+ background: #f8fafc;
+ /* Extra right padding to give more space after the Add button */
+ padding: 10px 18px 10px 10px;
+ border-radius: 80px;
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.02), 0 2px 6px rgba(0,0,0,0.05);
+ min-height: 70px;
+ align-items: center;
}
.todo-input input {
flex: 1;
- padding: 10px;
- font-size: 16px;
- border-radius: 6px;
- border: 1px solid #ccc;
+ padding: 16px 22px;
+ font-size: 1rem;
+ border: none;
+ background: white;
+ border-radius: 60px;
+ outline: none;
+ transition: all 0.2s;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
+ font-weight: 500;
+ color: #1a202c;
+}
+
+.todo-input input:focus {
+ box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.2);
+ background: #ffffff;
}
.todo-input button {
- padding: 10px 20px;
- font-size: 16px;
- background-color: #4CAF50;
+ padding: 14px 32px;
+ font-size: 1rem;
+ font-weight: 600;
+ background: linear-gradient(95deg, #4c6ef5, #3b4fcf);
color: white;
border: none;
- border-radius: 6px;
+ border-radius: 60px;
cursor: pointer;
+ transition: all 0.2s ease;
+ box-shadow: 0 8px 18px rgba(60, 80, 200, 0.3);
+ letter-spacing: 0.5px;
+ white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ /* No margin-right needed; the parent's padding-right provides the space */
}
.todo-input button:hover {
- background-color: #45a049;
+ background: linear-gradient(95deg, #5f7cf5, #4a62e0);
+ transform: translateY(-2px);
+ box-shadow: 0 14px 24px -8px rgba(60, 80, 200, 0.5);
+}
+
+.todo-input button:active {
+ transform: translateY(1px);
+ box-shadow: 0 6px 12px rgba(60, 80, 200, 0.25);
}
+/* Todo list styling */
.todo-list {
list-style-type: none;
padding-left: 0;
+ margin-top: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
}
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
- padding: 12px 10px;
- margin-bottom: 10px;
- border: 1px solid #ddd;
- border-radius: 6px;
- background-color: #fff;
+ padding: 14px 20px;
+ background: #ffffff;
+ border-radius: 80px;
+ border: 1px solid #edf2f7;
+ transition: all 0.2s;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.03);
+}
+
+.todo-item:hover {
+ background: #fafcff;
+ border-color: #cbd5e1;
+ box-shadow: 0 6px 14px -8px rgba(0,0,0,0.12);
}
.description {
flex: 1;
- margin-right: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ margin-right: 16px;
+ font-size: 1rem;
+ font-weight: 500;
+ color: #2d3e50;
+ word-break: break-word;
+ white-space: normal;
+ line-height: 1.4;
+}
+
+/* Completed tasks */
+.todo-item.completed .description {
+ text-decoration: line-through;
+ color: #94a3b8;
}
+/* Action buttons */
.actions {
display: flex;
- gap: 10px;
+ gap: 12px;
}
.actions button {
background: none;
border: none;
cursor: pointer;
- font-size: 18px;
- display: flex;
+ font-size: 1.2rem;
+ width: 40px;
+ height: 40px;
+ border-radius: 40px;
+ display: inline-flex;
align-items: center;
justify-content: center;
- width: 32px;
- height: 32px;
+ transition: all 0.2s;
+ background: #f1f5f9;
+ color: #475569;
}
-.complete-btn i {
- color: green;
+.actions button:hover {
+ transform: scale(1.05);
}
-.delete-btn i {
- color: red;
+.complete-btn:hover {
+ background: #e0f2fe;
+ color: #0f6b3a;
}
-.todo-item.completed .description {
- text-decoration: line-through;
- color: gray;
+.delete-btn:hover {
+ background: #fee2e2;
+ color: #c2410c;
+}
+
+/* Responsive */
+@media (max-width: 550px) {
+ .todo-container {
+ padding: 24px 18px 32px;
+ }
+ .todo-input {
+ flex-wrap: wrap;
+ background: transparent;
+ padding: 0;
+ border-radius: 0;
+ gap: 16px;
+ min-height: auto;
+ }
+ .todo-input input,
+ .todo-input button {
+ width: 100%;
+ border-radius: 60px;
+ }
+ .todo-item {
+ flex-wrap: wrap;
+ gap: 12px;
+ border-radius: 32px;
+ padding: 16px;
+ }
+ .actions {
+ width: 100%;
+ justify-content: flex-end;
+ }
}
diff --git a/Sprint-3/todo-list/todos.mjs b/Sprint-3/todo-list/todos.mjs
index f17ab6a25..c5602621c 100644
--- a/Sprint-3/todo-list/todos.mjs
+++ b/Sprint-3/todo-list/todos.mjs
@@ -3,27 +3,35 @@
the following manner:
[
- { task: "Description of task 1", completed: false},
- { task: "Description of task 2", completed: true}
+ { task: "Description of task 1", completed: false, deadline: null },
+ { task: "Description of task 2", completed: true, deadline: "2026-12-31T23:59" }
]
-
*/
// Append a new task to todos[]
-export function addTask(todos, task, completed = false) {
- todos.push({ task, completed });
+export function addTask(todos, task, completed = false, deadline = null) {
+ todos.push({ task, completed, deadline });
}
-// Delete todos[taskIndex] if it exists
+// Delete todos[taskIndex]
export function deleteTask(todos, taskIndex) {
if (todos[taskIndex]) {
todos.splice(taskIndex, 1);
}
}
-// Toggle the "completed" property of todos[taskIndex] if the task exists.
+// Toggle the "completed" property of todos[taskIndex]
export function toggleCompletedOnTask(todos, taskIndex) {
if (todos[taskIndex]) {
todos[taskIndex].completed = !todos[taskIndex].completed;
}
-}
\ No newline at end of file
+}
+
+// Delete all completed tasks
+export function deleteCompleted(todos) {
+ 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..a58896827 100644
--- a/Sprint-3/todo-list/todos.test.mjs
+++ b/Sprint-3/todo-list/todos.test.mjs
@@ -1,132 +1,154 @@
-// 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
+// node --test todos.test.mjs
-// Import all the exported members through an object
import * as Todos from "./todos.mjs";
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
// 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 },
+ { task: "Task 1 description", completed: true, deadline: "2026-04-01" },
+ { task: "Task 2 description", completed: false, deadline: null },
+ { task: "Task 3 description", completed: true, deadline: "2026-04-05" },
+ { task: "Task 4 description", completed: false, deadline: null },
];
}
// A mock task to simulate user input
-const theTask = { task: "The Task", completed: false };
+const theTask = { task: "The Task", completed: false, deadline: null };
describe("addTask()", () => {
- test("Add a task to an empty ToDo list", () => {
+ it("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);
+ assert.strictEqual(todos.length, 1);
+ assert.deepEqual(todos[0], theTask);
});
- test("Should append a new task to the end of a ToDo list", () => {
-
+ it("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);
+ assert.strictEqual(todos.length, lengthBeforeAddition + 1);
+ assert.deepEqual(todos[todos.length - 1], theTask);
});
});
describe("deleteTask()", () => {
-
- test("Delete the first task", () => {
+ it("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]);
+ assert.strictEqual(todos.length, lengthBeforeDeletion - 1);
+ assert.deepEqual(todos[0], todosBeforeDeletion[1]);
+ assert.deepEqual(todos[1], todosBeforeDeletion[2]);
+ assert.deepEqual(todos[2], todosBeforeDeletion[3]);
});
- test("Delete the second task (a middle task)", () => {
+ it("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]);
+ assert.strictEqual(todos.length, lengthBeforeDeletion - 1);
+ assert.deepEqual(todos[0], todosBeforeDeletion[0]);
+ assert.deepEqual(todos[1], todosBeforeDeletion[2]);
+ assert.deepEqual(todos[2], todosBeforeDeletion[3]);
});
- test("Delete the last task", () => {
+ it("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]);
+ assert.strictEqual(todos.length, lengthBeforeDeletion - 1);
+ assert.deepEqual(todos[0], todosBeforeDeletion[0]);
+ assert.deepEqual(todos[1], todosBeforeDeletion[1]);
+ assert.deepEqual(todos[2], todosBeforeDeletion[2]);
});
- test("Delete a non-existing task", () => {
+ it("Delete a non-existing task", () => {
const todos = createMockTodos();
const todosBeforeDeletion = createMockTodos();
Todos.deleteTask(todos, 10);
- expect(todos).toEqual(todosBeforeDeletion);
+ assert.deepEqual(todos, todosBeforeDeletion);
Todos.deleteTask(todos, -1);
- expect(todos).toEqual(todosBeforeDeletion);
+ assert.deepEqual(todos, todosBeforeDeletion);
});
});
describe("toggleCompletedOnTask()", () => {
-
- test("Expect the 'completed' property to toggle on an existing task", () => {
+ it("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);
+ assert.strictEqual(todos[taskIndex].completed, !completedStateBeforeToggle);
// Toggle again
Todos.toggleCompletedOnTask(todos, taskIndex);
- expect(todos[taskIndex].completed).toEqual(completedStateBeforeToggle);
+ assert.strictEqual(todos[taskIndex].completed, completedStateBeforeToggle);
});
- test("Expect toggling on a task does not affect other tasks", () => {
+ it("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]);
- });
+ assert.deepEqual(todos[0], todosBeforeToggle[0]);
+ assert.deepEqual(todos[2], todosBeforeToggle[2]);
+ assert.deepEqual(todos[3], todosBeforeToggle[3]);
+ });
- test("Expect no change when toggling on a non-existing task", () => {
+ it("Expect no change when toggling on a non-existing task", () => {
const todos = createMockTodos();
const todosBeforeToggle = createMockTodos();
Todos.toggleCompletedOnTask(todos, 10);
- expect(todos).toEqual(todosBeforeToggle);
+ assert.deepEqual(todos, todosBeforeToggle);
Todos.toggleCompletedOnTask(todos, -1);
- expect(todos).toEqual(todosBeforeToggle);
+ assert.deepEqual(todos, todosBeforeToggle);
});
});
+describe("deleteCompleted()", () => {
+ it("Remove all completed tasks", () => {
+ const todos = createMockTodos();
+
+ Todos.deleteCompleted(todos);
+
+ assert.strictEqual(todos.length, 2);
+ assert.strictEqual(todos[0].completed, false);
+ assert.strictEqual(todos[1].completed, false);
+ assert.strictEqual(todos[0].task, "Task 2 description");
+ assert.strictEqual(todos[1].task, "Task 4 description");
+ });
+
+ it("No change if no tasks are completed", () => {
+ const todos = [
+ { task: "Task A", completed: false },
+ { task: "Task B", completed: false }
+ ];
+
+ const before = JSON.parse(JSON.stringify(todos));
+ Todos.deleteCompleted(todos);
+ assert.deepEqual(todos, before);
+ });
+
+ it("All tasks removed if all are completed", () => {
+ const todos = [
+ { task: "Task A", completed: true },
+ { task: "Task B", completed: true }
+ ];
+
+ Todos.deleteCompleted(todos);
+ assert.strictEqual(todos.length, 0);
+ });
+});