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); + }); +});