diff --git a/.gitignore b/.gitignore index 9491a2fd..d5f43e64 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +.DS_Store + # User-specific files *.rsuser *.suo @@ -360,4 +362,4 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd diff --git a/domain-model.md b/domain-model.md new file mode 100644 index 00000000..0aba8dbf --- /dev/null +++ b/domain-model.md @@ -0,0 +1,20 @@ +> Note that this does not reflect actuall solution as changes has been made to classes. + +| Class | Feature | Method/Property | Input | Output | +|-------|---------|-----------------|-------|--------| +| List | Add task to list | addTask() | Task() | | +| List | Edit status on task | setStatus() | Task(), Bool | Bool | +| List | Edit status on task by ID | setStatusOnTask() | int, Bool | Bool | +| List | Remove task from list | removeTask() | int | Bool | +| List | Get smallest/largest time delta | getTimeDelta() | Bool | TimeSpan | +| List | Get all tasks | getAllTasks() | | [Task()] | +| List | Get all tasks in ascending/descending order | getAllTasks() | Bool | [Task()] | +| List | Get all completed/incomplete tasks | getAllTasks() | Bool | [Task()] | +| List | Get task by search | getTask() | String | Task() / Error | +| List | Get task by ID | getTask() | int | Task() / Error | +| Task | Set task priority | _priority | Enum | | +| Task | Set task ID | _uid | int | | +| Task | Set task name | _name | string | | +| Task | Set task description | _text | string | | +| Task | See creation date/time | _created | DateTime | | +| Task | See completion date/time | _completed | DateTime | | \ No newline at end of file diff --git a/tdd-todo-list.CSharp.Main/Program.cs b/tdd-todo-list.CSharp.Main/Program.cs index 3751555c..ee4e361d 100644 --- a/tdd-todo-list.CSharp.Main/Program.cs +++ b/tdd-todo-list.CSharp.Main/Program.cs @@ -1,2 +1,11 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using tdd_todo_list.CSharp.Main; +using Task = tdd_todo_list.CSharp.Main.Task; +using TaskStatus = tdd_todo_list.CSharp.Main.TaskStatus; + +TodoList list = new TodoList(); +list.addTask("Task 1", "Desc 1", TaskPriority.Medium, TaskCategory.Admin); + +bool changedToComplete = list.setStatus(0, TaskStatus.Complete); + +List completes = list.getAllTasks(TaskStatus.Complete); +completes.ForEach(t => Console.WriteLine(t)); diff --git a/tdd-todo-list.CSharp.Main/ToDoList.cs b/tdd-todo-list.CSharp.Main/ToDoList.cs index 835cb600..ee646ee5 100644 --- a/tdd-todo-list.CSharp.Main/ToDoList.cs +++ b/tdd-todo-list.CSharp.Main/ToDoList.cs @@ -8,5 +8,183 @@ namespace tdd_todo_list.CSharp.Main { public class TodoList { + private int _taskCounter = 0; + private List _tasks = new List(); + + public void addTask(string name, string description, TaskPriority priority, TaskCategory category) + { + _tasks.Add(new Task(_taskCounter++, name, description, TaskStatus.Incomplete, priority, category)); + } + + public Task? getTask(int id) + { + return _tasks.FirstOrDefault(t => t.uid.Equals(id)); + } + + public List getAllTasks() + { + return _tasks; + } + + public List getAllTasks(bool ascending) + { + if (ascending) + { + return [.. _tasks.OrderBy(t => t.name)]; + } + return [.. _tasks.OrderByDescending(t => t.name)]; + } + + public List getAllTasks(TaskStatus status) + { + return [.. _tasks.Where(t => t.status.Equals(status))]; + } + + public List getAllTasks(TaskCategory category) + { + return [.. _tasks.Where(t => t.category.Equals(category))]; + } + + public List getTasksLongerThan(TimeSpan timeDate) + { + return [.. _tasks.Where(t => t.timeDelta() > timeDate)]; + } + + public TimeSpan? getLargestTimeDelta() + { + TimeSpan? result = null; + + foreach (Task t in _tasks) + { + if (t.status != TaskStatus.Complete) continue; + TimeSpan? d = t.timeDelta(); + if (d == null) continue; + if (result == null || d > result) result = d; + } + + return result; + } + + public TimeSpan? getSmallestTimeDelta() + { + TimeSpan? result = null; + + foreach (Task t in _tasks) + { + if (t.status != TaskStatus.Complete) continue; + TimeSpan? d = t.timeDelta(); + if (d == null) continue; + if (result == null || d < result) result = d; + } + + return result; + } + + public bool setStatus(int id, TaskStatus status) + { + Task? t = _tasks.FirstOrDefault(t => t.uid.Equals(id)); + if (t == null) return false; + t.setStatus(status); + return true; + } + + public bool setName(int id, string name) + { + Task? t = _tasks.FirstOrDefault(t => t.uid.Equals(id)); + if (t == null) return false; + + t.name = name; + return true; + + + } + + public Task findTask(string name) + { + Task? t = _tasks.FirstOrDefault(t => t.name.Equals(name)); + + if (t != null) + { + return t; + } + + throw new KeyNotFoundException($"No task with name \"{name}\""); + } + + public bool removeTask(int id) + { + Task? t = _tasks.FirstOrDefault(t => t.uid.Equals(id)); + if (t == null) return false; + return _tasks.Remove(t); + } + + public override string ToString() + { + StringBuilder s = new(); + foreach (Task t in _tasks) + { + s.Append($"{t}"); + } + + return s.ToString(); + } + } + + public class Task(int id, string name, string description, TaskStatus status, TaskPriority priority, TaskCategory category) + { + public int uid = id; + public string name = name; + public string description = description; + public TaskStatus status = status; + public TaskPriority priority = priority; + public TaskCategory category = category; + public DateTime created = DateTime.Now; + public DateTime? completed = null; + + public TimeSpan? timeDelta() + { + if (completed == null) return DateTime.Now - created; + + return completed - created; + } + + public void setStatus(TaskStatus newStatus) + { + this.status = newStatus; + if (newStatus == TaskStatus.Complete) + { + this.completed = DateTime.Now; + } + else // Incomplete + { + this.completed = null; + } + } + + public override string ToString() + { + return $"{uid} | {category} | {status} | {(completed != null ? completed : "-")} | {created} | {priority} \n\tName: {name} \n\tDescriptions: {description}\n"; + } + } + + public enum TaskPriority + { + Low, + Medium, + High, + None + } + + public enum TaskStatus + { + Complete, + Incomplete + } + + public enum TaskCategory + { + Study, + Work, + Admin } } diff --git a/tdd-todo-list.CSharp.Test/CoreTests.cs b/tdd-todo-list.CSharp.Test/CoreTests.cs index 084cce19..43bcab90 100644 --- a/tdd-todo-list.CSharp.Test/CoreTests.cs +++ b/tdd-todo-list.CSharp.Test/CoreTests.cs @@ -1,17 +1,302 @@ using tdd_todo_list.CSharp.Main; using NUnit.Framework; +using Task = tdd_todo_list.CSharp.Main.Task; +using TaskStatus = tdd_todo_list.CSharp.Main.TaskStatus; namespace tdd_todo_list.CSharp.Test { [TestFixture] public class CoreTests { + [Test] + public void AddTasks_AddsWithFieldsAndAutoIds() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.High, TaskCategory.Work); + + List all = list.getAllTasks(); + Assert.That(all.Count, Is.EqualTo(2)); + + Assert.That(all[0].uid, Is.EqualTo(0)); + Assert.That(all[0].name, Is.EqualTo("Task 1")); + Assert.That(all[0].description, Is.EqualTo("Desc 1")); + Assert.That(all[0].priority, Is.EqualTo(TaskPriority.Low)); + Assert.That(all[0].category, Is.EqualTo(TaskCategory.Study)); + + Assert.That(all[1].uid, Is.EqualTo(1)); + Assert.That(all[1].name, Is.EqualTo("Task 2")); + Assert.That(all[1].description, Is.EqualTo("Desc 2")); + Assert.That(all[1].priority, Is.EqualTo(TaskPriority.High)); + Assert.That(all[1].category, Is.EqualTo(TaskCategory.Work)); + } + + [Test] + public void GetAllTasks_ReturnsAll() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + + List all = list.getAllTasks(); + Assert.That(all[0].name, Is.EqualTo("Task 1")); + Assert.That(all[1].name, Is.EqualTo("Task 2")); + } + + [Test] + public void ChangeStatus_IncompleteToCompleteAndBack() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Medium, TaskCategory.Admin); + + bool changedToComplete = list.setStatus(0, TaskStatus.Complete); + Assert.That(changedToComplete, Is.True); + + List completes = list.getAllTasks(TaskStatus.Complete); + Assert.That(completes.Count, Is.EqualTo(1)); + Assert.That(completes[0].name, Is.EqualTo("Task 1")); + + bool changedToIncomplete = list.setStatus(0, TaskStatus.Incomplete); + Assert.That(changedToIncomplete, Is.True); + + List incompletes = list.getAllTasks(TaskStatus.Incomplete); + Assert.That(incompletes.Count, Is.EqualTo(1)); + Assert.That(incompletes[0].name, Is.EqualTo("Task 1")); + } + + [Test] + public void GetOnlyCompleteTasks() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + + list.setStatus(1, TaskStatus.Complete); + + List completes = list.getAllTasks(TaskStatus.Complete); + Assert.That(completes.Count, Is.EqualTo(1)); + Assert.That(completes[0].name, Is.EqualTo("Task 2")); + } [Test] - public void FirstTest() + public void GetOnlyIncompleteTasks() { - TodoList core = new TodoList(); - Assert.Pass(); + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + + list.setStatus(1, TaskStatus.Complete); + + List incompletes = list.getAllTasks(TaskStatus.Incomplete); + Assert.That(incompletes.Count, Is.EqualTo(1)); + Assert.That(incompletes[0].name, Is.EqualTo("Task 1")); + } + + [Test] + public void SearchByName_ThrowsWhenNotFound() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + + KeyNotFoundException? ex = Assert.Throws(() => list.findTask("Missing")); + StringAssert.Contains("No task with name \"Missing\"", ex?.Message); + } + + [Test] + public void RemoveTask_RemovesById() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + + bool removed = list.removeTask(0); + Assert.That(removed, Is.True); + + List remaining = list.getAllTasks(); + Assert.That(remaining.Count, Is.EqualTo(1)); + Assert.That(remaining[0].name, Is.EqualTo("Task 2")); + } + + [Test] + public void OrderAscending_ByName() + { + TodoList list = new TodoList(); + list.addTask("Task 3", "Desc 3", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + + List asc = list.getAllTasks(true); + Assert.That(asc[0].name, Is.EqualTo("Task 1")); + Assert.That(asc[1].name, Is.EqualTo("Task 2")); + Assert.That(asc[2].name, Is.EqualTo("Task 3")); + } + + [Test] + public void OrderDescending_ByName() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 3", "Desc 3", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + + List desc = list.getAllTasks(false); + Assert.That(desc[0].name, Is.EqualTo("Task 3")); + Assert.That(desc[1].name, Is.EqualTo("Task 2")); + Assert.That(desc[2].name, Is.EqualTo("Task 1")); + } + + [Test] + public void PrioritiseTasks_PriorityIsStored() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Medium, TaskCategory.Study); + list.addTask("Task 3", "Desc 3", TaskPriority.High, TaskCategory.Study); + + List all = list.getAllTasks(); + Assert.That(all[0].priority, Is.EqualTo(TaskPriority.Low)); + Assert.That(all[1].priority, Is.EqualTo(TaskPriority.Medium)); + Assert.That(all[2].priority, Is.EqualTo(TaskPriority.High)); + } + + [Test] + public void ListAllTasksByPriority_FilterByPriority() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.High, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 3", "Desc 3", TaskPriority.High, TaskCategory.Study); + + List highPriority = list.getAllTasks().Where(t => t.priority == TaskPriority.High).ToList(); + Assert.That(highPriority.Count, Is.EqualTo(2)); + } + + [Test] + public void GetTaskById_ReturnsTask() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Work); + + Task? t = list.getTask(1); + Assert.That(t?.name, Is.EqualTo("Task 2")); + } + + [Test] + public void UpdateNameById_ChangesName() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + list.addTask("Task 2", "Desc 2", TaskPriority.Medium, TaskCategory.Work); + + bool ok = list.setName(1, "Task X"); + Assert.That(ok, Is.True); + + Assert.That(list.getTask(1)?.name, Is.EqualTo("Task X")); + Assert.That(list.getTask(0)?.name, Is.EqualTo("Task 1")); + } + [Test] + public void ChangeStatusById_SetsStatus() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + + list.setStatus(0, TaskStatus.Complete); + Assert.That(list.getTask(0)?.status, Is.EqualTo(TaskStatus.Complete)); + } + + [Test] + public void CreatedDate_IsSetWhenCreated() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + + Assert.That(list.getTask(0)?.created, Is.Not.EqualTo(default(DateTime))); + } + + [Test] + public void CompletedDate_IsSetWhenMarkedComplete() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + + list.setStatus(0, TaskStatus.Complete); + Assert.That(list.getTask(0)?.completed, Is.Not.Null); + } + + [Test] + public void LongestTimeToComplete_ReturnsMaxDelta() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Admin); + + Task t1 = list.getAllTasks()[0]; + t1.status = TaskStatus.Complete; + t1.created = new DateTime(2024, 01, 01); + t1.completed = new DateTime(2024, 01, 03); + + Task t2 = list.getAllTasks()[1]; + t2.status = TaskStatus.Complete; + t2.created = new DateTime(2024, 02, 01); + t2.completed = new DateTime(2024, 02, 10); + + Assert.That(list.getLargestTimeDelta(), Is.EqualTo(TimeSpan.FromDays(9))); + } + + [Test] + public void CategoriseTasks_CategoryIsStored() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Work); + + List work = list.getAllTasks(TaskCategory.Work); + Assert.That(work.Count, Is.EqualTo(1)); + Assert.That(work[0].name, Is.EqualTo("Task 2")); + } + + [Test] + public void ListAllTasksByCategory_UsesFilter() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 2", "Desc 2", TaskPriority.Low, TaskCategory.Study); + list.addTask("Task 3", "Desc 3", TaskPriority.Low, TaskCategory.Admin); + + List study = list.getAllTasks(TaskCategory.Study); + Assert.That(study.Count, Is.EqualTo(2)); + Assert.That(study[0].name, Is.EqualTo("Task 1")); + Assert.That(study[1].name, Is.EqualTo("Task 2")); + } + + [Test] + public void TasksCompletedLongerThanFiveDays_FiltersByDuration() + { + TodoList list = new TodoList(); + list.addTask("Task 1", "Desc 1", TaskPriority.Low, TaskCategory.Admin); + list.addTask("Task 2", "Desc 2", TaskPriority.Medium, TaskCategory.Work); + list.addTask("Task 3", "Desc 3", TaskPriority.High, TaskCategory.Study); + + Task t1 = list.getAllTasks()[0]; // 3 days + t1.status = TaskStatus.Complete; + t1.created = new DateTime(2024, 01, 01); + t1.completed = new DateTime(2024, 01, 04); + + Task t2 = list.getAllTasks()[1]; // 7 days + t2.status = TaskStatus.Complete; + t2.created = new DateTime(2024, 02, 01); + t2.completed = new DateTime(2024, 02, 08); + + Task t3 = list.getAllTasks()[2]; // exactly 5 days + t3.status = TaskStatus.Complete; + t3.created = new DateTime(2024, 03, 01); + t3.completed = new DateTime(2024, 03, 06); + + List over5 = list.getTasksLongerThan(TimeSpan.FromDays(5)); + + Assert.That(over5.Count, Is.EqualTo(1)); + Assert.That(over5[0].name, Is.EqualTo("Task 2")); } } } \ No newline at end of file