diff --git a/README.md b/README.md index 13f5c77403f..273c3efeebb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2223S1-CS2103T-F11-2/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S1-CS2103T-F11-2/tp/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-F11-2/tp/branch/master/graph/badge.svg?token=IA3TT58GTK)](https://codecov.io/gh/AY2223S1-CS2103T-F11-2/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
+* This is a **brownfield project** completed by Team CS2103T-F11-2.
Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. + * Used by NUS students who want to track the progress of assignments and tasks completed for the module + * Used by busy NUS students who want to organise their tasks for different modules +* The project simulates an ongoing software project for a desktop application (called _MODPRO_) used for tracking the progress of modules taken. * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +* It is named `MODPRO` because it was intended to help NUS students to track the progress of the modules taken. +* For the detailed documentation of this project, see the **[MODPRO Product Website](https://ay2223s1-cs2103t-f11-2.github.io/tp)**. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org) diff --git a/build.gradle b/build.gradle index 108397716bd..4db8855ad76 100644 --- a/build.gradle +++ b/build.gradle @@ -69,4 +69,8 @@ shadowJar { archiveFileName = 'addressbook.jar' } +run { + enableAssertions = true +} + defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..f90da328bdb 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,54 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Douglas Lim - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/dlimyy)] +[[portfolio](team/dlimyy.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: Integration, Deliverables and deadlines + +### Tan Li Xin -### Jane Doe + - -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/tlx02)] +[[portfolio](team/tlx02.md)] -* Role: Team Lead -* Responsibilities: UI -### Johnny Doe +* Role: Developer +* Responsibilities: Code Quality + +### Phua Li Ting - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/phualiting)] +[[portfolio](team/phualiting.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Scheduling and tracking -### Jean Doe +### Rachel Chua - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/RachelChua)] +[[portfolio](team/rachelchua.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Deliverable and deadlines -### James Doe +### Samuel Pang - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/Sampy147)] +[[portfolio](team/sampy147.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Documentation and Code quality diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..19ab346e652 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,14 +2,66 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} +## **Table of Contents** +* [Acknowledgements](#acknowledgements) +* [Setting up](#setting-up-getting-started) +* [Design](#design) + * [Architecture](#architecture) + * [UI component](#ui-component) + * [Logic component](#logic-component) + * [Model component](#model-component) + * [Storage component](#storage-component) + * [Common classes](#common-classes) +* [Implementation](#implementation) + * [Sort Task Command](#sort-task-command) + * [Filter Tasks Command](#filter-tasks-command) + * [Mark Task Command](#mark-task-command) + * [Edit Task Command](#edit-task-command) + * [Delete Task Command](#delete-task-command) + * [Link Exam Feature](#link-exam-feature) + * [Add Exam Feature](#add-exam-feature) + * [Unink Exam Command](#unlink-exam-command) + * [Find Tasks Feature](#find-tasks-feature) +* [Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops) +* [Appendix: Requirements](#ui-component) + * [Product scope](#product-scope) + * [User stories](#user-stories) + * [Use cases](#use-cases) + * [Non-Functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) +* [Appendix: Instructions for manual testing](#appendix-instructions-for-manual-testing) + * [Launch and Shutdown](#launch-and-shutdown) + * [Adding a module](#adding-a-module) + * [Adding a tag to a task](#adding-a-tag-to-a-task) + * [Editing a tag of a task](#editing-a-tag-of-a-task) + * [Deleting a tag of a task](#deleting-a-tag-of-a-task) + * [Sorting the task list](#sorting-the-task-list) + * [Linking the exam to a task](#linking-the-exam-to-a-task) + * [Viewing the help window](#viewing-the-help-window) + * [Adding an exam](#adding-an-exam) + * [Editing an exam](#editing-an-exam) + * [Finding a task](#finding-a-task) + * [Finding a module](#finding-a-module) + * [Listing modules](#listing-modules) + * [Listing tasks](#listing-tasks) + * [Marking a task](#marking-a-task) + * [Editing a task](#editing-a-task) + * [Adding a task](#adding-a-task) + * [Filtering the task list](#filtering-the-task-list) + * [Clearing the task list](#clearing-the-task-list) + * [Deleting an exam](#deleting-an-exam) + * [Unlinking an exam](#unlinking-an-exam) + * [Showing tasks of an exam](#showing-tasks-of-an-exam) + * [Clearing all lists](#clearing-all-lists) -------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* This project is based on the [AddressBook-Level3](https://github.com/se-edu/addressbook-level3) project created by the [SE-EDU initiative](https://se-education.org/) +* List of libraries used + * [JavaFx](https://openjfx.io/) + * [Junit5](https://junit.org/junit5/) -------------------------------------------------------------------------------------------------------------------- @@ -26,6 +78,12 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). :bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +
+ +:information_source: **Note:** For some classes and methods such as AddressBookParser, our team has decided to +stick with the naming convention used in AB3. +
+ ### Architecture @@ -36,7 +94,11 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called +[`Main`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/Main.java) +and +[`MainApp`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/MainApp.java). +It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -52,7 +114,7 @@ The rest of the App consists of four components. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `t delete 3`. @@ -69,24 +131,26 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TaskListPanel`, +`ModuleListPanel`, `ExamListPanel`, `StatusBarFooter` etc. +All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* depends on some classes in the `Model` component, as it displays `Task`, `Exam`, `Module` objects residing in the `Model`. ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -94,15 +158,15 @@ Here's a (partial) class diagram of the `Logic` component: How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). +1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddModuleCommand`) which is executed by the `LogicManager`. +1. The command can communicate with the `Model` when it is executed (e.g. to add a module). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("t mark 1")` API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `t mark 1` Command](images/MarkTaskSequenceDiagram.png) -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
:information_source: **Note:** The lifeline for `MarkCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: @@ -110,32 +174,31 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddTaskCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddTaskCommand`) which the `AddressBookParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddTaskCommandParser`, `DeleteTaskCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/model/Model.java) The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data + * all `Module` objects (which are contained in a `DistinctModuleList` object). + * all `Task` objects (which are contained in a `DistinctTaskList` object) + * all `Exam` objects (which are contained in a `DistinctExamList` object) +* stores the currently 'selected' `Module` objects (e.g after executing the find module commands) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the currently 'selected' `Task` objects (e.g. after executing the find task command) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the currently 'selected' `Exam` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - - -
- ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) @@ -154,89 +217,530 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Sort Task Command + +#### Command Format + +`t sort c/CRITERIA` where `CRITERIA` refers to the criteria selected by the user. + +`CRITERIA` can be one of the following criteria: +* `priority` +* `deadline` +* `module` +* `description` + +#### What is the feature about + +The `sort` command allows users to sort the task list by priority status, +deadline, module code and task description with ease. + +#### How does the feature work + +The sort task feature is currently implemented through the `SortTaskCommand` which extends the abstract class `Command`. +The `SortTaskCommand`operates directly on the `ObservableList` stored under `DistinctTaskList` in `AddressBook` so the +`ObservableList` in `DistinctTaskList` will be permanently sorted to the criteria. +* When sorting by priority, all tasks with `HIGH` priority status will be positioned at the top of the +displayed task list, followed by `MEDIUM`, `LOW` and +lastly all tasks with no priority tags. +* When sorting by deadline, all tasks with deadline tags will be displayed +at the top of the displayed task list with the task with the earliest deadline +being displayed at the top. All the remaining tasks with no deadline tags will +be displayed below all tasks with deadline tags. + +#### Why is the feature implemented in this manner + +Our team has made the decision to not remove tasks with no priority status from the displayed task list when +sorting by priority status as we believed that users should be given the freedom to view all tasks that +they have added after sorting. + +Our team has also made a similar decision to not remove tasks with no deadlines from the displayed task list when +sorting by deadline. + + +#### UML Diagrams + + +Shown below is a sequence diagram of what occurs when the `execute` method of +`LogicManager` is invoked. + +| ![SequenceDiagram](images/SortTaskCommandSequenceDiagram.png) | +|:-------------------------------------------------------------:| +| Sequence Diagram of Sort Task Command | + +
+ +:information_source: **Note:** The lifeline for `SortTaskCommandParser` and `SortTaskCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + +
+ +**Sequence of actions made when `execute` method of `LogicManager` is invoked** +1. `LogicManager` object takes in `t sort c/priority` which the user keys into the command line. +2. `LogicManager` object calls the `parseCommand` of the `AddressBookParser` object created during the initialisation +of `LogicManager` object and passes `t sort c/priority` as the arguments of `parseCommand` +3. `SortTaskCommandParser` object is created during execution of `parseCommand` of `AddressBookParser` +4. `SortTaskCommandParser` object calls its `parse` method with `c/priority` being passed in as argument. +5. `SortTaskCommand` object called st is created from `SortTaskCommandParser` +6. `execute` method of `SortTaskCommand` object st is invoked and model is passed in as +an argument. +7. `sortTaskList` method of model is called with `criteria` being passed as an +argument of the method. +8. The `getFilteredTaskList` method of model is called and it returns the `FilteredList` stored in model. +9. There is a check performed to see if the size of the `FilteredList` returned in the previous step is greater than 0. +If it is not greater than 0, an error message will be displayed. +8. The `execute` method of `SortTaskCommand` object returns a `CommandResult` object with +the sorted successfully message as its argument to the `LogicManager` object. + +The following activity diagram summarises what happens when SortTaskCommand is executed + +| ![ActivityDiagram](images/SortTaskCommandActivityDiagram.png) | +|:-------------------------------------------------------------:| +| Activity Diagram of SortTaskCommand | + +### Filter Tasks Command + +#### Command Format + +`t filter [m/MODULE/]* [c/COMPLETED]* [l/LINKED]*` where `MODULE` refers to the module code of the module to be filtered out, `COMPLETED` refers to the completion status of the tasks to be filtered out, and `LINKED` refers to the link status of the tasks to be filtered out. + +#### What is the feature about +The `filter` command allows users to filter the task list by module, completion status, and/or link status. +* `COMPLETED` will be `y` for filtering out tasks with `status` of `COMPLETE` and `n` for filtering out tasks with `TaskStatus` of `INCOMPLETE`. +* `COMPLETED` will be `y` for filtering out tasks with `linkedExam` which is not `null` and `n` for filtering out tasks with `linkedExam` of `null`. + +#### How does the feature work + +The filter tasks feature is currently implemented through the `FilterTasksCommand` which extends the abstract class `Command`. +The `FilterTasksCommand` operates by producing a `FilterPredicate` used to update the `FilteredTaskList`. +Executing a filter command will always filter the full `DistinctTaskList` and not the `FilteredTaskList` if two filter commands are executed one after another. + +#### UML Diagrams + +Shown below is a sequence diagram of what occurs when the `execute` method of `LogicManager` is invoked. + +| ![FilterSequenceDiagram](images/FilterSequenceDiagram.png) | +|:----------------------------------------------------------:| +| Sequence diagram of FilterTasksCommand | + +
:information_source: **Note:** The lifeline for `FilterTasksCommand` and `FilterTasksCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + +
+ +1. The user executes `t filter m/CS2103T c/y l/n` command to filter the task list to show all CS2103T tasks that have been marked complete and are not linked to any exam. +2. `LogicManager` calls `AddressBookParser#parseCommand()` with `"t filter m/CS2103T c/y l/n"` as the argument. +3. `AddressBookParser` calls `FilterTasksCommandParser#parse()` with `"m/CS2103T c/y l/n"` as the argument. +4. `FilterTasksCommandParser` creates a new `FilterTasksCommand` object with a new `FilterPredicate` object as argument, representing the filter conditions specified by the user. +5. `LogicManager`calls `FilterTasksCommand#execute()` with `model` as the argument. +6. `FilterTasksCommand` calls `Model#updateFilteredTaskList()` with `FilterPredicate` as an argument, causing the task list to be updated based on the new conditions for `module`, `isCompleted` and `isLinked` based on the `FilterPredicate`. +7. `FilterTasksCommand#execute()` returns a `CommandResult` object with the `MESSAGE_SUCCESS` message as argument. + +
:information_source: **Note:** If the `module`, `isCompleted` or `isLinked` input is invalid, there will be an error message shown and the address book will continue to show the current `taskFilteredList`. + +
+ +The following activity diagram summarizes what happens when FilterTasksCommand is executed. + +| | +|:----------------------------------------------------------:| +| Activity diagram of FilterTasksCommand | + +### Mark Task Command + +#### Command Format + +`t mark INDEX` where `INDEX` is the index (shown in the displayed task list) of the task to be marked. + +#### What is the feature about + +The `t mark` command allows users to indicate a specific task is completed. +The task specified will be ticked. + +#### How does the feature work + +The mark task feature is currently implemented through the `MarkTaskCommand` which extends the abstract class `Command`. +A copy of the task to be marked will be created, with its `TaskStatus` set to `COMPLETE`. This marked task will then replace the +original task in the `DistinctTaskList`. + +#### UML diagrams +Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked. + +| ![MarkTaskSequenceDiagram](images/MarkTaskSequenceDiagram.png) | +|:----------------------------------------------------------------:| +| ![MarkTaskReferenceDiagram](images/MarkTaskReferenceDiagram.png) | +| Sequence diagram of MarkTaskCommand | + +
+ +:information_source: **Note:** The lifeline for `MarkCommandParser` and `MarkCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifelines reach the end of the diagram. +
+ +
+ +:information_source: **Note:** If the command fails, `Model#replaceTask()` will not be called, so the task list will not change. If so, `MarkCommand` will return an error to the user rather than attempting to perform the command. +
+ +**Sequence of actions made when `execute` method of `LogicManager` is invoked** + +1. The user types the `t mark 1` command. +2. The `execute()` method of the `LogicManager` is called. +3. The `LogicManager` then calls `AddressBookParser#parseCommand()` which parses `t mark 1`, creating a `MarkCommandParser` object. +4. The `AddressBookParser` calls `MarkCommandParser#parse()` which parses `1` and creates a `MarkCommand` object with an `Index` object storing the target index `1`. +5. Then, the `LogicManager`calls `MarkCommand#execute()`. +6. The `MarkCommand` retrieves the task at the `Index`, which is the first task in the filtered task list, from the `Model`. +7. The `MarkCommand` command calls `Task#mark()` to create a marked copy of the `taskToMark`. +8. This `markedTask` has all fields similar to the original task, except its `TaskStatus` is `COMPLETE`. +9. Then, `MarkCommand` calls `Model#replaceTask()` which replaces the `taskToMark` in the filtered task list in `Model` with the `markedTask`. -#### Proposed Implementation +
+ +:information_source: **Note:** The `UnmarkCommand` works the same — the only difference is that it calls `Task#unmark()`, which returns a copy of the task with `TaskStatus` set to `INCOMPLETE`. +
+ +The following activity diagram summarizes what happens when MarkCommand is executed + +| ![MarkTaskActivityDiagram](images/MarkTaskActivityDiagram.png) | +|:--------------------------------------------------------------:| +| Activity diagram of MarkTaskCommand | -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +### Edit Task Command -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +#### Command Format -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +`t edit INDEX [m/MODULE]* [d/DESCRIPTION]*` where `INDEX` is the index of the task to edit, and `MODULE` and `DESCRIPTION` are the module and description to replace the current values of the specified task. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +#### What is the feature about -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +The `t edit` command allows users to update the specified task with the fields provided. The provided fields will replace the existing fields. -![UndoRedoState0](images/UndoRedoState0.png) +#### How does the feature work +The edit task feature is currently implemented through the `EditTaskCommand` which extends the abstract class `Command`. +A copy of the task to be edited will be created, with its existing `MODULE` and `DESCRIPTION` replaced with the new +values provided. This edited task will then replace the +original task in the `DistinctTaskList`. -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +#### UML diagrams +Shown below is a sequence diagram of what occurs when the `execute` method of +`LogicManager` is invoked. -![UndoRedoState1](images/UndoRedoState1.png) +| ![EditTaskSequenceDiagram](images/EditTaskSequenceDiagram.png) | +|:----------------------------------------------------------------:| +| ![EditTaskReferenceDiagram](images/EditTaskReferenceDiagram.png) | +| Sequence diagram of EditTaskCommand | -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +
-![UndoRedoState2](images/UndoRedoState2.png) +:information_source: **Note:** The lifelines for `EditTaskCommandParser` and `EditTaskCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifelines reach the end of the diagram. +
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +
+:information_source: **Note:** If the command is invalid, `Model#replaceTask()` will not be called, so the task list will not change. If so, `EditTaskCommand` will return an error to the user rather than attempting to perform the command.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +**Sequence of actions made when `execute` method of `LogicManager` is invoked** + +1. The user types the `t edit 1 d/task 1` command. +2. The `execute()` method of the `LogicManager` is called. +3. The `LogicManager` then calls `AddressBookParser#parseCommand()` which parses `t edit 1 d/task 1`, creating an `EditTaskCommandParser` object. +4. The `AddressBookParser` calls `EditTaskCommandParser#parse()` which parses `1 d/task 1` and creates an `EditTaskCommand` object with an `Index` object storing the target index `1` and an `EditTaskDescriptor` object storing the description `task 1` in a `TaskDescription` object. +5. Then, the `LogicManager`calls `EditTaskCommand#execute()`. +6. The `EditTaskCommand` retrieves the task at the `Index`, which is the first task in the filtered task list, from the `Model`. +7. The `EditTaskCommand` command calls `Task#edit()` with the `EditTaskDescriptor` passed as the argument. +8. The `Task#edit()` method checks that the module of the `taskToEdit` is not changed, so it creates a copy of the `taskToEdit`, with only the description changed. If `taskToEdit` has a linked exam, `linkedEditedTask` is also linked to the same exam. +9. This `linkedEditedTask` has all fields similar to the original task, except its `TaskDescription` is changed to `Task 1`. +10. Then, the `EditTaskCommand` calls `Model#replaceTask()` which replaces the `taskToEdit` in the filtered task list in `Model` with the `linkedEditedTask`. + +The following activity diagram summarizes what happens when EditTaskCommand is executed + +| ![EditTaskActivityDiagram](images/EditTaskActivityDiagram.png) | +|:----------------------------------------------------------------:| +| Activity diagram of EditTaskCommand | -![UndoRedoState3](images/UndoRedoState3.png) +### Delete Task Command + +#### Command Format +`t del INDEX` where `INDEX` is the index (shown in the displayed task list) of the task to be deleted. + +#### What is the feature about +The `t del` command allows users to delete a specific task from the displayed task list. + +#### How does the feature work +The delete task feature is currently implemented through the `DeleteTaskCommand` which extends the abstract class `Command`. +The task to be deleted will be retrieved from the `Model` based on the `INDEX` given. This task will then be deleted from the +the `DistinctTaskList`. + +#### UML diagrams +Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked. + +| ![DeleteTaskSequenceDiagram](images/DeleteTaskSequenceDiagram.png) | +|:------------------------------------------------------------------:| +| Sequence diagram of DeleteTaskCommand | + +
+ +:information_source: **Note:** The lifeline for `DeleteTaskCommandParser` and `DeleteTaskCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifelines reach the end of the diagram. +
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +
+:information_source: **Note:** If the command fails, `Model#deleteTask()` will not be called, so the task list will not change. If so, `DeleteTaskCommand` will return an error to the user rather than attempting to perform the command.
-The following sequence diagram shows how the undo operation works: +**Sequence of actions made when `execute` method of `LogicManager` is invoked** + +1. The user types the `t del 1` command. +2. The `execute()` method of the `LogicManager` is called. +3. The `LogicManager` then calls `AddressBookParser#parseCommand()` which parses `t del 1`, creating a `DeleteTaskCommandParser` object. +4. The `AddressBookParser` calls `DeleteTaskCommandParser#parse()` which parses `1` and creates a `DeleteTaskCommand` object with an `Index` object storing the target index `1`. +5. Then, the `LogicManager`calls `DeleteTaskCommand#execute()`. +6. The `DeleteTaskCommand` retrieves the task at the `Index`, which is the first task in the filtered task list, from the `Model`. +7. The `DeleteTaskCommand` command calls `Model#deleteTask()` to delete the `taskToDelete`. + +The following activity diagram summarizes what happens when DeleteTaskCommand is executed + +| ![DeleteTaskActivityDiagram](images/DeleteTaskActivityDiagram.png) | +|:------------------------------------------------------------------:| +| Activity diagram of DeleteTaskCommand | + +### Link Exam feature + +#### Command Format + +`e link e/EXAM_INDEX t/TASK_INDEX` where `EXAM_INDEX` refers to the index number of the displayed exam list +and `TASK_INDEX` refers to the index number of the displayed task list. + +#### What is the feature about + +The link exam feature allows users to link an exam in the exam list to a task in the task list. + +#### How does the feature work + +The link exam feature is currently implemented through the `LinkExamCommand` class which extends the abstract class `Command` +. The `LinkExamCommand` takes in two `Index` objects, one being the exam index and the other being +the task index. + +When the user invokes the`execute` method of `LinkExamCommand`, +the `Task` and `Exam` stored at the specified index of the `FilteredList` and the +`FilteredList` respectively are retrieved. + +There will be checks to see if the `Task` is already linked to the exam and if +the module code of the `Exam` is same as that of `Task`. Once these checks are passed, +the `Task` will be linked to the `Exam` + +#### UML Diagrams + +Shown below is a sequence diagram of what occurs when the `execute` method of +`LogicManager` is invoked. + +| ![SequenceDiagram](images/LinkExamCommandSequenceDiagram.png) | +|:--------------------------------------------------------------:| +| Sequence diagram of LinkExamCommand | -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +:information_source: **Note:** The lifeline for `LinkExamCommandParser` and `LinkExamCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
+**Sequence of actions made when `execute` method of `LogicManager` is invoked** +1. `LogicManager` object takes in `e link e/1 t/1` which the user keys into the command line. +2. `LogicManager` object calls the `parseCommand` of the `AddressBookParser` object created during the initialisation + of `LogicManager` object and passes `e link e/1 t/1` as the arguments of `parseCommand`. +3. `LinkExamCommandParser` object is created during execution of `parseCommand` of `AddressBookParser`. +4. `LinkExamCommandParser` object calls its `parse` method with `e/1 t/1` being passed in as argument. +5. `LinkExamCommand` object called le is created from `LinkExamCommandParser`. +6. `execute` method of `LinkExamCommand` object le is invoked and model is passed in as + an argument. +7. `LinkExamCommand` calls the `getFilteredTaskList` method of `Model` and the `FilteredList` stored in model is + returned. +8. `LinkExamCommand` calls the `getFilteredExamList` method of `Model` and the `FilteredList` stored in model is + returned. +9. `Task` object called task is returned when `get` method of `List` is executed. +10. task calls its own method `isLinked` and returns whether the task is linked. If the task is linked, + an error message will be displayed. +11. `Exam` object called exam is returned when `get` method of `List` is executed. +12. task calls its own method `getModule` and the `Module` stored in task will be returned. +13. exam calls its own method `getModule` and the `Module` stored in Exam will be returned. +14. The static method `isSameModule` of `Module` class is executed, and it checks whether task and exam + have the same module code. If they do not have the same module code, an error message will be displayed. +15. The `linkTask` method of `model` will be executed, and it will create a new `Task` object called linkedTask +16. The `replaceTask` method of `model` will be executed and replaces task in `DistinctTaskList` in model with + linkedTask +17. The `execute` method of `LinkExamCommand` returns a `CommandResult` object with + the exam linked successfully message as its argument to the `LogicManager` object. + +The following activity diagram summarises what happens when LinkExamCommand is executed + +| ![ActivityDiagram](images/LinkExamCommandActivityDiagram.png) | +|:--------------------------------------------------------------:| +| Activity diagram of LinkExamCommand | + + +### Add Exam Feature + +#### Command Format: +`e add m/MODULE ex/EXAM DESCRIPTION ed/EXAM DATE` +where `MODULE` refers to the exam module, `EXAM DESCRIPTION` refers to the exam description and `EXAM DATE` refers to the exam date of the exam +to be added into the exam list. + +#### What is the feature about: +This command allows users to add an exam to the exam list, with the following fields of exam module, exam description +and exam date. + +#### How does the feature work: +This add exam feature is currently implemented through AddExamCommand class which extends the abstract class Command. +If the module, exam description and exam date are valid, and the module exists in the module list, and the exam is not a duplicate of any existing exam, +the exam will be added to the `DistinctExamList`. + + +#### UML Diagrams +Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked. + +| ![AddExamCommand](images/AddExamCommandSequenceDiagram.png) | +|:------------------------------------------------------------:| +| Sequence diagram of AddExamCommand | + +1. The user types an `e add m/cs2030s ex/midterms ed/20-12-2022` command. + +2. The command calls `LogicManager#execute()` with +the command input as the argument, which then calls `AddressBookParser#parseCommand() ` +with command input as the argument. + +3. `AddressBookParser#parseCommand()` matches the command to be +an add exam command through the command word and the feature type, which then calls `AddExamCommandParser#parse()`. + +4. `AddExamCommandParser#parse()` then parses `m/cs2030s ex/midterms ed/20-12-2022` to get `Module`, `ExamDescription` +and the `ExamDate` objects of the exam by calling their respective `ParserUtil` parse methods. +Then, an `Exam` object is created with the three objects as arguments. + +5. `AddExamCommandParser#parse()` returns a new `AddExamCommand` object created with the `Exam` +object created previously as the argument in the constructor. `LogicManager` object will call +`AddExamCommand#execute()` with a `Model` object as the argument. + +6. `AddExamCommand#execute()` will check if model already contains the module of the exam +through `Model#hasModule()`. If it does not contain the module, an exception will be thrown to +indicate the module is not found. It will also check if the model already contains the exam +through `Model#hasExam()`. If it contains the exam, an exception will be thrown +to indicate that the exam already exists in MODPRO. + +Otherwise, it will call `ModelManager#addExam()` with the exam as the argument, +which calls `AddressBook#addExam()` that will add the exam to the `DistinctExamList`, +which stores all the exams.`AddExamCommand#execute()` method returns a `CommandResult` +object to display that the exam was successfully added. + +
+ +:information_source: **Note:** + +* For step 4, the `Exam` object will not be created if the exam description or exam date or module + is not valid. Exam description is not valid if it is an empty string, exam date is not valid + if it is not in DD-MM-YYYY or if it is earlier than the current date. Module is not valid if it is not at least 6 characters long + with the first 2 being alphabetical characters. Hence, in such cases, `Exam` object is not created, and the exam will not be added.
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The following activity diagram summarises what happens when AddExamCommand is executed + +| ![AddExamActivityDiagram](images/AddExamActivityDiagram.png) | +|:-------------------------------------------------------------:| +| Activity diagram of AddExamCommand | + + +### Unlink Exam Command + +#### Command format +`e unlink INDEX` where `INDEX` refers to the index number shown on the displayed task list of the task to be unlinked. + +#### What is the feature about +The `e unlink` command allows users to unlink a task from its exam. + +#### How does the feature work +The unlink exam feature is currently implemented through the `UnlinkExamCommand` class which extends the abstract class `Command`. +A copy of the task to be unlinked will be created, with its `linkedExam` field set to `null`. +The original task will be replaced with the new unlinked task in the `DistinctTaskList`. + +#### UML Diagrams -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +Shown below is a sequence diagram of what occurs when the `execute` method of `LogicManager` is invoked. + +| ![UnlinkExamSequenceDiagram](images/UnlinkExamSequenceDiagram.png) | +|:------------------------------------------------------------------:| +| Sequence diagram of UnlinkExamCommand | + +
:information_source: **Note:** The lifeline for `UnlinkExamCommand` and `UnlinkExamCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +**Sequence of actions made when `execute` method of `LogicManager` is invoked** + +1. The user executes `e unlink 1` to unlink the first task in the `FilteredTaskList`. +2. `LogicManager` calls `AddressBookParser#parseCommand()` with `"e unlink 1"` as the argument. +3. `AddressBookParser` calls `UnlinkExamCommandParser#parse()` with `"1"` as the argument. +4. `UnlinkExamCommandParser` creates a new `UnlinkExamCommand` object with a new `Index` object as argument, representing the first index of the task list. +5. `LogicManager` calls `UnlinkExamCommand#execute()` with `model` as the argument. +6. `UnlinkExamCommand` calls `Model#getFilteredTaskList()` which returns the current filtered task list. +7. `List#get()` is called which returns the first task in the filtered task list. +8. `Task#unlinkTask()` is called which returns a new `Task` object with the same fields as the first task in the filtered task list, but with its `linkedExam` field set to `null`. +9. `UnlinkExamCommand` calls `Model#replaceTask()` to replace the original task at the first index with the new unlinked task. +10. `UnlinkExamCommand#execute()` returns a `CommandResult` object with the `EXAM_UNLINKED_SUCCESS` message as argument. + +The following activity diagram summarizes what happens when UnlinkExamCommand is executed. -![UndoRedoState4](images/UndoRedoState4.png) +| | +|:---------------------------------------------------------------:| +| Activity diagram of UnlinkExamCommand | -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. + +### Find Tasks Feature -![UndoRedoState5](images/UndoRedoState5.png) +#### Command Format: +`t find KEYWORD` where `KEYWORD` is the keyword inputted by user. -The following activity diagram summarizes what happens when a user executes a new command: +#### What is the feature about: +The command allows users to find tasks whose task description matches the keyword inputted by them fully or partially. +The keyword is case-insensitive. For example, "TASK" will match "task". - +#### Why is the feature implemented in this way: +It is implemented such that users can find tasks that contain the keyword not just fully but also partially. +This can be helpful especially if they cannot remember the full description of the task. +Also, if they want quick access to the task, they can simply type a partial description to find the task instead of writing the whole description. -#### Design considerations: +#### UML Diagrams: +Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked. -**Aspect: How undo & redo executes:** +| ![FindTasksCommand](images/FindTasksCommandSequenceDiagram.png) | +|:----------------------------------------------------------------:| +| Sequence diagram of FindTasksCommand | -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +1. The user types an `t find task` command. -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. +2. The command calls `LogicManager#execute()` with +the command input as the argument, which then calls `AddressBookParser#parseCommand() ` +with command input as the argument. -_{more aspects and alternatives to be added}_ +3. `AddressBookParser#parseCommand()` matches the command to be +a find tasks command through the command word and the feature type, which then calls `FindTasksCommandParser#parse()` which takes in `task` as its argument. -### \[Proposed\] Data archiving +4. `FindTasksCommandParser#parse()` will create a `DescriptionContainsKeywordsPredicate` object which is a predicate that takes in the keyword inputted by user and +tests if the task description matches the keyword- `task` +Then it will create and return `FindTasksCommand` object,that takes in a single argument-the `DescriptionContainsKeywordsPredicate` object, + to `LogicManager` object. + +5. `LogicManager` object will call `FindTasksCommand#execute()` with a `Model` object as the argument. +`FindTasksCommand#execute()` will call `Model#updateFilteredTaskList` to update the filtered task list +by the predicate (which is the `DescriptionContainsKeywordsPredicate` object created previously) to only +display the tasks which match the keyword. Then, a `CommandResult` object is returned to the `LogicManager` object. + +
+ +:information_source: **Note:** + +* For Step 4, `FindTasksCommandParser#parse` will not return a new `FindTasksCommand` object if the keyword is empty. +
+ +The following activity diagram summarises what happens when FindTasksCommand is executed + +| ![FindTasksActicityDiagram](images/FindTasksActivityDiagram.png) | +|:----------------------------------------------------------------:| +| Activity diagram of FindTasksCommand | -_{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- @@ -257,71 +761,637 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts +* has a need to manage a significant number of modules, tasks and exams * prefer desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: manage module tasks faster than a typical mouse/GUI driven app ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|-------------|-------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| +| `* * *` | NUS student | view the list of tasks I need to complete | start implementing those tasks. | +| `* * *` | NUS student | create the tasks in the tasklist | add the list of tasks that need to be completed | +| `* * *` | NUS student | mark a task as complete | have a better idea of what I have completed. | +| `* * *` | NUS student | delete the tasks in my tasklist | remove them if added wrongly. | +| `* * *` | NUS student | delete the modules in my modulelist | remove them if added wrongly. | +| `* * *` | NUS student | edit the modules in my modulelist | remove them if added wrongly. | +| `* *` | NUS student | filter the task list by module, completion status and link status | easily look for a task. | +| `* * *` | NUS student | delete the exams in my exam list | remove exams that I no longer want to track. | +| `* * *` | NUS student | unlink a task from its exam | remove links that I linked wrongly. | +| `* *` | NUS student | look at all tasks specific to an exam | easily track tasks of my next exam. | +| `* *` | NUS student | clear all the tasks in my task list | quickly start with an empty task list. | +| `* *` | NUS student | clear all tasks, exams and modules in the respective lists | quickly start with an empty task, module and exam list. | +| `* * *` | NUS student | view a help guide on how to use the list of commands | refer to this guide when I forget some of the commands | +| `* * *` | NUS student | indicate a task is completed | spend more time on other tasks. | +| `* * *` | NUS student | add modules to my module list | add the modules that I am currently taking to the module list | +| `* * *` | NUS student | link the task in the task list to the exam in the exam list | track the number of exam-related tasks | +| `* * *` | NUS student | add my exams to the exam list | add my upcoming exams to the exam list to track my revision progress. | +| `* * *` | NUS student | view the list of modules I have | see the modules I am taking and my study progress for the modules. | +| `* * ` | NUS student | tag the priority status of a task in the task list | prioritise the task that I would like to complete first | +| `* * ` | NUS student | tag the deadline of a task in the task list | track the date that the task should be completed | +| `* * ` | NUS student | edit the priority status tagged to a task in the task list | change the priority of the task I would like to complete first | +| `* * ` | NUS student | edit the deadline tagged to a task in the task list | change the deadline that I would like to complete the task | +| `* * ` | NUS student | delete the priority status tagged to a task in the task list | remove the priority status of tasks which have been added wrongly | +| `* * ` | NUS student | tag the priority status tagged to a task in the task list | remove deadlines which I no longer want to track. | +| `* * ` | NUS student | sort the tasks in the task list | organise the tasks in the task list. | +| `* *` | NUS student | find a task by task description through a command | quickly locate the task instead of having to go through the whole list of tasks just to find it. | +| `* *` | NUS student | find a module by module code through a command | quickly locate the module instead of having to go through the whole list of modules just to find it. | +| `* *` | NUS student | edit the exams in the exam list | change and correct the exam details easily if I input the wrong details. | +| `* *` | NUS student | indicate a task is not completed | continue working on the task. | +| `* *` | NUS student | edit the tasks in my task list | easily change and correct the details of my tasks. | +| `* *` | NUS student | view my progress for each module | focus on the modules which currently have little progress. | +| `* *` | NUS student | view my progress for each exam | focus on revising for the exams which currently have little progress. | + -*{More to be added}* ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is the `MODPRO` and the **Actor** is the `NUS student`, unless specified otherwise) -**Use case: Delete a person** + +**Use case: Add a task into task list** **MSS** +1. NUS student requests to add a task. +2. MODPRO shows the task added. +3. MODPRO updates the progress bar to include the added task.
+ Use case ends. -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +**Extensions** +* 1a. The command format is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The module code is invalid. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The given description is empty. + * 1c1. MODPRO shows an error message.
+ Use case ends. +* 1d. The module does not exist in the module list. + * 1d1. MODPRO shows an error message.
+ Use case ends. + +**Use case: List tasks in task list** - Use case ends. +**MSS** +1. NUS student requests to view all tasks in the stored task list +2. MODPRO displays the list of all tasks and display a message of "Listed all tasks". + + Use case ends. **Extensions** +* 1a. The provided command is in an invalid command format. + + * 1a1. MODPRO shows an error message.
+ Use case ends. + -* 2a. The list is empty. +**Use case: Indicate a task is completed** + +**MSS** +1. NUS student requests to mark a specific task +2. MODPRO ticks the specified task +3. MODPRO updates the progress bar for the module and exam (if it exists) of the task + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The task specified is already marked + * 1c1. MODPRO shows an error message
+ Use case ends. + +**Use case: Indicate a task is not completed** +**MSS** +1. NUS student requests to unmark a specific task +2. MODPRO unticks the specified task +3. MODPRO updates the progress bar for the module and exam (if it exists) of the task + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The task specified is already unmarked + * 1c1. MODPRO shows an error message
+ Use case ends. + +**Use case: Edit a task** + +**MSS** +1. NUS student requests to edit the module or description of a specific task +2. MODPRO updates the specified task with the new values provided + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. Neither the module nor the description is provided. + * 1c1. MODPRO shows an error message
+ Use case ends. +* 1d. The given module code is invalid + * 1d1. MODPRO shows an error message
+ Use case ends. +* 1e. The given description is invalid + * 1e1. MODPRO shows an error message
+ Use case ends. +* 1f. The given module does not exist in the module list + * 1f1. MODPRO shows an error message
+ Use case ends. +* 1g. The module and description of the specified task are not changed. + * 1g1. MODPRO shows an error message
+ Use case ends. +* 1h. The edited task is the same as another existing task in the task list + * 1h1. MODPRO shows an error message
+ Use case ends. +* 2a. The module of the specified task is changed and the specified task is linked to an exam + * 2a1. MODPRO unlinks the task from its exam and updates the progress bar for the exam + * 2a2. MODPRO updates the progress bar for both the current and previous module of the task
+ Use case ends. +* 2b. The module of the specified task is changed and the specified task is not linked to any exam + * 2b1. MODPRO updates the progress bar for both the current and previous module of the task
+ Use case ends. + +**Use Case: Add tags to a task** + +**MSS** +1. NUS student requests to add a tag to a task in task list +2. MODPRO adds the tag to the task in the task list + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The priority status provided is invalid + * 1c1. MODPRO shows an error message
+ Use case ends +* 1d. The deadline provided is invalid + * 1d1. MODPRO shows an error message
+ Use case ends +* 1e. The task already has a priority status + * 1e1. MODPRO shows an error message
+ Use case ends +* 1f. The task already has a deadline + * 1f1. MODPRO shows an error message
+ Use case ends + +**Use Case: Edit the tags of a task** + +**MSS** +1. NUS student requests to edit the tags of a task in task list +2. MODPRO edits the tag of the task in the task list + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The priority status provided is invalid + * 1c1. MODPRO shows an error message
+ Use case ends +* 1d. The deadline provided is invalid + * 1d1. MODPRO shows an error message
+ Use case ends +* 1e. The task does not have a priority status + * 1e1. MODPRO shows an error message
+ Use case ends +* 1f. The task does not have a deadline + * 1f1. MODPRO shows an error message
+ Use case ends +* 1g. The priority status is the same as the priority status of the task + * 1g1. MODPRO shows an error message
+ Use case ends +* 1h. The deadline is the same as the deadline of the task + * 1h1. MODPRO shows an error message
+ Use case ends + +**Use Case: Delete tags of a task** + +**MSS** +1. NUS student requests to delete the tags of a task in task list +2. MODPRO deletes the tag of the task in the task list + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The keywords provided is invalid + * 1c1. MODPRO shows an error message
+ Use case ends +* 1d. The task does not have a priority status + * 1d1. MODPRO shows an error message
+ Use case ends +* 1e. The task does not have a deadline + * 1e1. MODPRO shows an error message
+ Use case ends + + +**Use case: Delete a task from the task list** + +**MSS** +1. User requests to delete a specific task in the task list +2. MODPRO deletes the task + + Use case ends. + +**Extensions** +* 1a. The given index is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Filter the task list** -* 3a. The given index is invalid. +**MSS** +1. NUS student requests to filter the task list based on some conditions. +2. MODPRO shows the list of tasks that fulfil the given conditions.
+ Use case ends. + +**Extensions** +* 1a. The command format is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The module code is invalid. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The module does not exist in the task list. + * 1c1. MODPRO shows an error message.
+ Use case ends. +* 1d. The completion status provided is invalid. + * 1d1. MODPRO shows an error message.
+ Use case ends. +* 1e. The link status provided is invalid. + * 1e1. MODPRO shows an error message.
+ Use case ends. +* 1f. There are no filter conditions stated. + * 1f1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Clear the task list** + +**MSS** +1. NUS student requests to clear the task list. +2. MODPRO clears the task list. +3. MODPRO resets all exam and module progress bars.
+ Use case ends. + +**Extensions** +* 1a. The task list is already empty + * 1a1. MODPRO shows an error message.
+ Use case ends. + +**Use Case: Add a module to the module list** - * 3a1. AddressBook shows an error message. +**MSS** +1. NUS student requests to add a module to the module list +2. MODPRO adds the module to the module list + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given module code is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The given module name is invalid + * 1c1. MODPRO shows an error message
+ Use case ends +* 1d. The given module credit is invalid + * 1d1. MODPRO shows an error message
+ Use case ends +* 1e. The given module code already exists in the module list + * 1e1. MODPRO shows an error message
+ Use case ends +* 1f. The given module name already exists in the module list + * 1f1. MODPRO shows an error message
+ Use case ends +* 1g. The given module credit already exists in the module list + * 1g1. MODPRO shows an error message
+ Use case ends + +**Use case: Delete a module from the module list** - Use case resumes at step 2. +**MSS** +1. User requests to delete a specific module in the module list +2. MODPRO deletes the module + + Use case ends. + +**Extensions** +* 1a. The given index is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The module at the given index is tied to multiple tasks thus cannot be deleted + * 1a1. MODPRO shows an error message.
+ Use case ends. -*{More to be added}* +**Use case: Edit a module in the module list** + +**MSS** +1. User requests to edit a specific module in the module list +2. MODPRO edits the module + + Use case ends. + +**Extensions** +* 1a. The given index is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The module at the given index is tied to multiple tasks thus cannot be edited + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The given module code is invalid + * 1c1. MODPRO shows an error message.
+ Use case ends. + + +**Use case: Delete an exam from the exam list** + +**MSS** +1. NUS student requests to delete a specific exam in the exam list. +2. MODPRO deletes the exam. +3. MODPRO unlinks all tasks currently linked to the deleted exam.
+ Use case ends. + +**Extensions** +* 1a. The command format is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The given index is non-positive or larger than 2147483647. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The given index is larger than the number of exams in the exam list. + * 1c1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Unlink task from exam** + +**MSS** +1. NUS student requests to unlink a task from its exam. +2. MODPRO unlinks the task. +3. MODPRO updates the exam progress bar.
+ Use case ends. + +**Extensions** +* 1a. The command format is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The given index is non-positive or larger than 2147483647. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The given index is larger than the number of tasks in the task list. + * 1c1. MODPRO shows an error message.
+ Use case ends. +* 1d. The task is not linked to any exam. + * 1d1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Showing the tasks of an exam** + +**MSS** +1. NUS student requests to list all tasks of a specified exam. +2. MODPRO shows list of all tasks of the specified exam.
+ Use case ends. + +**Extensions** +* 1a. The command format is invalid. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The given index is non-positive or larger than 2147483647. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The given index is larger than the number of exams in the exam list. + * 1c1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Clear all lists** + +**MSS** +1. NUS student requests to clear all lists. +2. MODPRO clears task, exam and module lists.
+ Use case ends. + +**Extensions** +* 1a. All lists are already empty + * 1a1. MODPRO shows an error message.
+ Use case ends. + + +**Use Case: Sort the task list** + +**MSS** +1. NUS student requests to sort the task list +2. MODPRO sorts the task in the task list + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given criteria is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. + +**Use Case: Link the exam to a task** + +**MSS** +1. NUS student requests to link the exam in the exam list to a task in the task list +2. MODPRO links the task to the exam + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The given index for the task is invalid + * 1b1. MODPRO shows an error message
+ Use case ends. +* 1c. The given index for the exam is invalid + * 1c1. MODPRO shows an error message
+ Use case ends. +* 1d. The task is already linked + * 1d1. MODPRO shows an error message
+ Use case ends. +* 1e. The task and exam selected have a different module code + * 1e1. MODPRO shows an error message
+ Use case ends. + + + + +**Use case: List modules in module list** + +**MSS** +1. NUS student requests to view all modules in the stored module list. +2. MODPRO displays the list of all modules and display a message of "Listed all modules". + + Use case ends. + +**Extensions** +* 1a. The provided command is in an invalid command format. + * 1a1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Find tasks in the task list** + +**MSS** +1. NUS student requests to find tasks whose description matches the keyword inputted partially or fully. +2. MODPRO show the list of tasks whose description matches the keyword partially or fully. + + Use case ends. + +**Extensions** +* 1a. The keyword inputted is empty + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The provided command is in an invalid command format. + * 1b1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Find modules in the module list** + +**MSS** +1. NUS student requests to find modules whose module code matches the keyword inputted partially or fully. +2. MODPRO show the list of modules whose module code matches the keyword partially or fully. + + Use case ends. + +**Extensions** +* 1a. The keyword inputted is empty + * 1a1. MODPRO shows an error message
+ Use case ends. +* 1b. The provided command is in an invalid command format. + * 1b1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Add an exam into the exam list** + +**MSS** +1. NUS student requests to add an exam. +2. MODPRO adds the exam into the exam list. + + Use case ends. + +**Extensions** +* 1a. The description of the exam is empty + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The exam date provided is invalid. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. Exam module does not exist in MODPRO. + * 1c1. MODPRO shows an error message.
+ Use case ends. +* 1d. The exam to be added is the same as another existing exam in the exam list. + * 1d1. MODPRO shows an error message.
+ Use case ends. +* 1e. The module code provided is invalid. + * 1e1. MODPRO shows an error message.
+ Use case ends. +* 1f. The provided command is in an invalid command format. + * 1f1. MODPRO shows an error message.
+ Use case ends. +* 1g. Not all the fields(Exam description, Exam date, Module) are provided. + * 1g1. MODPRO shows an error message.
+ Use case ends. + +**Use case: Edit an exam in the exam list** + +**MSS** +1. NUS student requests to edit a specific exam in the exam list by specifying the index number of the exam to be edited. +2. MODPRO updates the specified exam with the new values provided. + + Use case ends. + +**Extensions** +* 1a. The given exam description is empty. + * 1a1. MODPRO shows an error message.
+ Use case ends. +* 1b. The given exam date is invalid. + * 1b1. MODPRO shows an error message.
+ Use case ends. +* 1c. The given exam module code is invalid. + * 1c1.MODPRO shows an error message.
+ Use case ends. +* 1d. The given exam module does not exist in MODPRO. + * 1d1. MODPRO shows an error message.
+ Use case ends. +* 1e. The given index is invalid. + * 1e1. MODPRO shows an error message.
+ Use case ends. +* 1f. The edited exam is the same as another existing exam in the exam list. + * 1f1. MODPRO shows an error message.
+ Use case ends. +* 1g. The module and description and date of the exam are not changed. + * 1g1. MODPRO shows an error message
+ Use case ends. +* 1h. The provided command is in an invalid command format. + * 1h1. MODPRO shows an error message.
+ Use case ends. +* 1i. No fields are provided to edit the exam. + * 1i1. MODPRO shows an error message.
+ Use case ends. +* 2a. The module of the specified exam is changed and the exam is linked to some tasks previously. + * 2a1. MODPRO unlinks these tasks from the exam, and the updates the progress bar of the exam.
+ Use case ends. ### Non-Functional Requirements -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. + + + + +2. Should be able to hold up to 1000 tasks without experiencing noticeable sluggishness in performance during typical usage + + 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. -*{More to be added}* ### Glossary * **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +* **GUI**: Graphical User Interface +* **UML** Unified Modeling Language -------------------------------------------------------------------------------------------------------------------- @@ -338,40 +1408,386 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Double-click the jar file Expected: Shows the GUI. The window size may not be optimum. 1. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ - -### Deleting a person - -1. Deleting a person while all persons are being shown - - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. - - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. - -1. _{ more test cases …​ }_ - -### Saving data - -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ -1. _{ more test cases …​ }_ +### Adding a module +1. Adding a module into the module list + 1. Test case: `m add c/cs2100 m/Computer Organisation mc/4`
+ Expected: Module with the name `Computer Organisation`, module credit `4` and +module code `cs2100` is added to the module list. + 2. Test case: `m add c/cs2100`
+ Expected: An error message will be displayed. There are missing compulsory fields for + module name and module credit. + 3. Other incorrect add module commands to try: `m add`, `m add c/1111 m/module mc/4`
+ Expected: An error message will be displayed. + +### Adding a tag to a task +1. Adding a tag to a task in the task list that is not tagged + 1. Prerequisites: The task selected must not be tagged with any tags. + 2. Test case: `t tagadd 1 p/HIGH`
+ Expected: The task located at the first index in the displayed task list is tagged with the priority status `HIGH`. + 3. Test case: `t tagadd 1 dl/25-11-2022`
+ Expected: The task located at the first index in the displayed task list is tagged with the deadline of `25-11-2022`. + 4. Test case: `t tagadd 1 p/random`
+ Expected: An error message will be displayed. An invalid priority status is chosen. + 5. Other incorrect add tag commands to try: `t tagadd`, `t tagadd 2 dl/39-13-2000`
+ Expected: An error message will be displayed. +2. Adding a tag to a task in the task list that is tagged with only a priority status + 1. Prerequisites: The task selected must be tagged with a priority status. + 2. Test case: `t tagadd 1 p/HIGH`
+ Expected: An error message will be displayed. The task located at the first index in the displayed task list is already tagged with a priority status. + 3. Test case: `t tagadd 1 dl/25-11-2022'
+ Expected: The task located at the first index in the displayed task list is tagged with the deadline of `25-11-2022`. + 4. Other incorrect add tag commands to try: `t tagadd`, `t tagadd 2 dl/39-13-2000`
+ Expected: An error message will be displayed. +3. Adding a tag to a task in the task list that it tagged with only a deadline + 1. Prerequisites: The task selected must be tagged with a deadline. + 2. Test case: `t tagadd 1 dl/25-11-2022`H`
+ Expected: An error message will be displayed. The task located at the first index in the displayed task list is already tagged with a deadline. + 3. Test case: `t tagadd 1 p/HIGH`
+ Expected: The task located at the first index in the displayed task list is tagged with the priority status `HIGH`. + 4. Other incorrect add tag commands to try: `t tagadd`, `t tagadd 2 dl/39-13-2000`
+ Expected: An error message will be displayed. + +### Editing a tag of a task +1. Editing the tag of a task in the task list that is not tagged at all + 1. Prerequisite is that the task selected is not tagged with either the priority status or deadline + 2. Test case: `t tagedit 1 p/HIGH`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a priority status. + 3. Test case: `t tagedit 1 dl/25-11-2022`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a deadline. + 4. Test case: `t tagedit 1 p/random`
+ Expected: An error message will be displayed. An invalid priority status is chosen. + 5. Other incorrect edit tag commands to try: `t tagedit`, `t tagedit 2 dl/39-13-2000`
+ Expected: An error message will be displayed. +2. Editing the tag of a task in the task list that is tagged with a priority status + 1. Prerequisites: The task selected is tagged with only the priority status `HIGH` + 2. Test case: `t tagedit 1 p/HIGH`
+ Expected: An error message will be displayed. The task selected is tagged with the same priority status. + 3. Test case: `t tagedit 1 p/LOW`
+ Expected: The task located at the first index of the displayed task list is tagged with the priority status `LOW` + 4. Other incorrect edit tag commands to try: `t tagedit`, `t tagedit 2 dl/39-13-2000`
+ Expected: An error message will be displayed. +3. Editing the tag of a task in the task list that it tagged with a deadline + 1. Prerequisites: The task selected is tagged with only the deadline `25-11-2022` + 2. Test case: `t tagedit 1 dl/25-11-2022`H`
+ Expected: An error message will be displayed. The task selected is tagged with the same deadline. + 3. Test case: `t tagedit 1 dl/26-11-2022`
+ Expected: The task located at the first index in the displayed task list is tagged with the new deadline `26-11-2022` + 4. Other incorrect edit tag commands to try: `t tagedit`, `t tagedit 2 dl/39-13-2000`
+ Expected: An error message will be displayed. + +### Deleting a tag of a task +1. Deleting the tag of a task in the task list that is not tagged at all + 1. Prerequisite is that the task selected is not tagged with either the priority status or deadline + 2. Test case: `t tagdel 1 t/priority`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a priority status. + 3. Test case: `t tagdel 1 t/deadline`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a deadline. + 4. Test case: `t tagdel 1 t/random`
+ Expected: An error message will be displayed. An invalid keyword is chosen. + 5. Other incorrect delete tag commands to try: `t tagdel`, `t tagdel -1 t/priority`
+ Expected: An error message will be displayed. +2. Deleting the tag of a task in the task list that is tagged with a priority status + 1. Prerequisites: The task selected is tagged with only a priority status + 2. Test case: `t tagdel t/priority`
+ Expected: The task located at the first index in the displayed task list will have its priority status removed. + 3. Test case: `t tagdel t/deadline`
+ Expected: An error message is displayed. The task does not have a deadline. + 4. Other incorrect delete tag commands to try: `t tagdel`, `t tagdel -1 t/priority`
+ Expected: An error message will be displayed. +3. Editing the tag of a task in the task list that it tagged with a deadline + 1. Prerequisites: The task selected is tagged with only a deadline + 2. Test case: `t tagdel t/priority`H`
+ Expected: An error message will be displayed. The task selected does not have a priority status + 3. Test case: `t tagdel t/deadline`
+ Expected: The task located at the first index in the displayed task list has its deadline removed. + 4. Other incorrect delete tag commands to try: `t tagdel`, `t tagdel -1 t/priority`
+ Expected: An error message will be displayed. + +### Sorting the task list +1. Sorting the unfiltered task list + 1. Test case: `t sort c/priority`
+ Expected: Sorts all tasks in the unfiltered task list by priority status. + 2. Other incorrect sort task commands to try: `t sort`, `t sort c/low`
+ Expected: An error message will be displayed. +2. Sorting the filtered task list + 1. Prerequisite: Perform a `t filter` operation on the task list + 2. Test case: `t sort c/priority`
+ Expected: Sorts all tasks in the filtered task list by priority status. + 3. Other incorrect sort task commands to try: `t sort`, `t sort c/low`
+ Expected: An error message will be displayed. + +### Linking the exam to a task +1. Linking the exam to an unlinked task + 1. Prerequisite: The task selected is unlinked and the task and exam have the same module code + 2. Test case: `e link e/1 t/1`
+ Expected: Links the first task in the displayed task list with the first exam in the displayed exam list. + 3. Other incorrect link exam commands to try: `e link`, `e link e/-1 t/9999999999999999999`
+ Expected: An error message will be displayed. +2. Linking the exam to a linked task + 1. Prerequisite: The task selected is linked and the task and exam selected have the same module code + 2. Test case: `e link e/1 t/1`
+ Expected: An error message is displayed. The first task in the displayed task list is already linked. + 3. Other incorrect link exam commands to try: `e link`, `e link e/-1 t/9999999999999999999`
+ Expected: An error message will be displayed. + +### Viewing the help window +1. Viewing the help window + 1. Test case: `help`
+ Expected: The help window will display on the screen + 2. Incorrect help command to try: `help123` + Expected: An error message will be displayed + + + +### Adding an exam + +1. Adding an exam to the exam list + 1. Prerequisite: the module `cs2030s` is present in the module list, and `cs2040s` is not present in module list. + 2. Test case: `e add m/cs2030s ex/Finals ed/30-12-2023`
+ Expected: The exam with the module field as `cs2030s`, exam description as `Finals`, exam date as `30-12-2023` is added to the exam list. + A message is displayed to show that the exam is added successfully. + 3. Test case: `e add m/cs2030s ex/Finals ed/30-13-2023`
+ Expected: Exam will not be added, and error message will be shown to say that the date provided is not in DD-MM-YYYY format. + 4. Test case: `e add m/cs2040s ex/Finals ed/30-12-2023`
+ Expected: Exam will not be added, and error message will be shown to say that the module does not exist. + 5. Test case: `e add m/cs2030s ex/ ed/30-12-2023`
+ Expected: Exam will not be added, and error message will be shown to say that the description of the exam should not be empty. + + +### Editing an exam +1. Editing an exam in the exam list. + 1. Prerequisite: the module `cs2030s` is present in the module list, and `cs2040s` is not present in module list and exam list only has 2 exams. + 2. Test case: `e edit 1 m/cs2030s ex/Finals ed/30-12-2023`
+ Expected: The first exam in the exam list is edited by changing the module field to `cs2030s`, exam description to `Finals`, exam date to `30-12-2023`. + A message is displayed to show that the exam is edited successfully. + 3. Test case: `e edit 0 m/cs2030s ex/Finals ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the index should be greater than 0 and less than 2147483648 for the index of an exam. + 4. Test case: `e edit 1 m/cs2040s ex/Finals ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the module does not exist. + 5. Test case: `e edit 1 ex/ ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the description of the exam should not be empty. + 6. Test case: `e edit 1 ed/30-13-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the date provided is not in DD-MM-YYYY format. + 7. Test case: `e edit 4 ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the index should be more than 0 but less than 3. + + +### Finding a task +1. Finding a task in the task list + 1. Prerequisite: The tasks with task descriptions as "WORK", "homework 1", "homewoRK 2", "past year paper" are inside the task list. + 2. Test case: `t find work`
+ Expected: The task list should display the 3 tasks with task description of "WORK", "homework 1", "homewoRK 2",with a message saying "3 tasks listed" + 3. Test case: `t find paper`
+ Expected: The task list should only display the task with the task description "past year paper", with a message saying "1 tasks listed" + 4. Test case: `t find assignment`
+ Expected: The task list should show no tasks displayed, with a message saying "0 tasks listed" + + +### Finding a module +1. Finding a module in the module list + 1. Prerequisite: The modules with module code as "CS2030s", "CS2040S", "CS3333" are inside the module list. + 2. Test case: `m find cs`
+ Expected: The module list should display the 3 modules with module code of "CS2030s", "CS2040S", "CS3333",with a message saying "3 modules listed" + 3. Test case: `m find 20`
+ Expected: The module list should display the 2 modules with the module code of "CS2030s", "CS2040S",with a message saying "2 modules listed". + 4. Test case: `m find 5555`
+ Expected: The module list should show no module displayed, with a message saying "0 modules listed" + + +### Listing modules +1. Listing modules in the stored module list + 1. Prerequisite: The modules with module code as "CS2030s", "CS2040S", "CS3333" are inside the module list, and a `m find 20` command is done. + 2. Test case: `m list`
+ Expected: The module list should now display the all 3 modules with module code of "CS2030s", "CS2040S", "CS3333",with a message saying "Listed all modules" + + + +### Listing tasks +1. Listing tasks in the stored tasks list + 1. Prerequisite: The tasks with task descriptions as "WORK", "homework 1", "homewoRK 2", "past year paper" are inside the task list, and a `t find paper` command is done. + 2. Test case: `t list`
+ Expected: The task list should now display the all 4 tasks with task descriptions of "WORK", "homework 1", "homewoRK 2", "past year paper",with a message saying "Listed all tasks" + +### Marking a task + +1. Marking a task while all tasks are being shown + * Prerequisites: + * List all tasks using the `t list` command. + * The task list displays multiple tasks. + * Test case: `t mark 1`
+ Expected: + * First task in the list is ticked. + * Details of the marked task shown in the feedback message. + * Progress bar for the module of the task is updated. + * Test case: `t mark 0`
+ Expected: + * No changes made to any tasks, exams or modules. + * Error details shown in the feedback message. + * Test case: `t mark INDEX` (where `INDEX` is the index of a task that is already marked)
+ Expected: Similar to the previous test case. + * Other invalid mark commands to try: `t mark `, `t mark asd`, `t mark INDEX` (where `INDEX` is larger than the list size)
+ Expected: Similar to the previous test case. + +2. Marking a task with only some tasks shown + * Prerequisites: + * Filter the tasks using the `t filter` command. + * The task list displays multiple tasks. + * Test case: `t mark 1`
+ Expected: + * First task in the list is ticked. + * Details of the marked task shown in the feedback message. + * Progress bar for the module of the task is updated. + * Test case: `t mark INDEX` (where `INDEX` is larger than the size of the displayed list but less than the size of the stored task list)
+ Expected: + * No changes made to any tasks, exams or modules. + * Error details shown in the feedback message. + +3. Marking a task linked to an exam + * Prerequisites: + * The list contains a task linked to an exam. + * The task shows the name of the exam. + * Test case: `t mark INDEX` (where `INDEX` is the index of the linked task)
+ Expected: + * The task specified is ticked. + * Details of the marked task shown in the feedback message. + * Progress bars for both the module of the task and the exam it is linked to, are updated. + + +### Editing a task + +1. Editing a task while all tasks are being shown + * Prerequisites: + * List all tasks using the `t list` command. + * The task list displays multiple tasks. + * There are no tasks with the description 'task 1'. + * Test case: `t edit 1 d/task 1` + Expected: + * The description of the first task in the list is changed to 'task 1'. + * Details of the edited task is shown in the feedback message. + * Test case: `t edit 0 d/task 1`
+ Expected: + * No changes made to any tasks, exams or modules. + * Error details shown in the feedback message. + * Other incorrect edit commands to try: `t edit d/task 1`, `t edit 1`, `t edit asd d/task 1`, `t edit INDEX d/task 1` (where `INDEX` is larger than the list size), `t edit 1 d/DESCRIPTION` (where `DESCRIPTION` is the current description of the first task in the list)
+ Expected: Similar to the previous test case. + +2. Editing a task with only some tasks shown + * Prerequisites: + * Filter the tasks using the `t filter` command. + * The task list displays multiple tasks but not all the tasks in the stored task list. + * There are no tasks in the stored task list with the description 'task 1'. + * Test case: `t edit 1 d/task 1`
+ Expected: + * The description of the first task in the list is changed to 'task 1'. + * Details of the marked task shown in the feedback message. + * Test case: `t edit INDEX d/task 1` (where `INDEX` is larger than the size of the displayed list but less than the size of the stored task list)
+ Expected: + * No changes made to any tasks, exams or modules. + * Error details shown in the feedback message. + +3. Editing a task with invalid parameters + * Prerequisites: + * There are no modules in the stored module list with the module code 'cs2030'. + * The task list displays multiple tasks. + * Test cases: `t edit 1 m/cs2030`, `t edit 1 m/c`, `t edit 1 d/ `
+ Expected: + * No changes made to any tasks, exams or modules. + * Error details shown in the feedback message. + +4. Editing a task to be the same as another task + * Prerequisites: + * There are 2 modules in the stored module list. + * The first 2 tasks in the list have the same module. + * Test cases: `t edit 1 d/DESCRIPTION` (where DESCRIPTION is the description of the second task)
+ Expected: + * No changes made to any tasks, exams or modules. + * Error details shown in the feedback message. + +5. Editing the module of a task linked to an exam + * Prerequisites: + * List all tasks using the `t list` command. + * There are 2 modules in the stored module list, 1 exam in the exam list and 1 task in the task list. + * The module of both the exam and the task is the first module in the module list. + * The task is linked to the exam. + * Test cases: `t edit 1 m/MODULE` (where `MODULE` is the module code of the second module in the list)
+ Expected: + * The module of the task in the list is changed to `MODULE`. + * A warning and the details of the edited task are shown in the feedback message. + * Progress bars for the 2 modules are updated. + +### Adding a task +1. Adding a task to the task list. + 1. Prerequisite: The module `cs2030s` is present in the module list, and `cs2040s` is not present in the module list. + 2. Test case: `t add m/cs2030s d/assignment`
+ Expected: A task with module `cs2030s` and description `assignment` is added to the task list. A message is displayed to show that task has been added successfully. + 3. Test case: `t add m/cs2040s d/assignment`
+ Expected: Task will not be added, and an error message will be shown to say that the module does not exist. + 4. Test case: `t add m/cs d/assignment`
+ Expected: Task will not be added, and an error message will be shown to say that the module code is invalid. + 5. Test case: `t add m/cs2040s d/`
+ Expected: Task will not be added, and an error message will be shown to say that the description should not be empty. + +### Filtering the task list +1. Filtering the task list by module, completion status and link status. + 1. Prerequisite: First task with module `cs2030s`, which is `complete` and `linked`. Second task with module `cs2030s`, which is `incomplete` and `unlinked`. + 2. Test case: `t filter m/cs2030s`
+ Expected: Task list displays both tasks. + 3. Test case: `t filter m/cs2040s`
+ Expected: Task list does not display both tasks. + 4. Test case: `t filter c/y`
+ Expected: Task list displays first task only. + 5. Test case: `t filter l/n`
+ Expected: Task list displays second task only. + 6. Test case: `t filter m/cs`
+ Expected: Task list does not change, and an error message will be shown to say that the module code is invalid. + 7. Test case: `t filter c/yes`
+ Expected: Task list does not change, and an error message will be shown to say that response to condition is invalid. + +### Clearing the task list +1. Clearing non-empty task list. + 1. Prerequisite: Task with module `cs2030s`, which is marked `complete`. + 2. Test case: `t clear`
+ Expected: Task list is cleared. `cs2030s` module progress bar resets. + +### Deleting an exam +1. Deleting an exam from the exam list. + 1. Prerequisite: One exam in the exam list. One task in the task list linked to the exam. + 2. Test case: `e del 1`
+ Expected: Exam is deleted, task becomes unlinked. + 3. Test case: `e del 2`
+ Expected: Both lists remain unchanged, and an error message will be shown to say that the exam list index is invalid. + +### Unlinking an exam +1. Unlink an exam from a task. + 1. Prerequisite: One exam in the exam list. First task in the task list linked to the exam. Second task is unlinked. + 2. Test case: `e unlink 1`
+ Expected: First task becomes unlinked. + 3. Test case: `e unlink 2`
+ Expected: Task list remains unchanged, and an error message will be shown to say that the task is already unlinked. + 4. Test case: `e unlink 3`
+ Expected: Both lists remain unchanged, and an error message will be shown to say that the task list index is invalid. + +### Showing tasks of an exam +1. Show all tasks linked to an exam. + 1. Prerequisite: Two exams in the exam list and two tasks in the task list. Link first task to first exam and second task to second exam. + 2. Test case: `e showt 1`
+ Expected: Task list displays first task only. + 3. Test case: `e showt 3`
+ Expected: Both lists remain unchanged, and an error message will be shown to say that the exam list index is invalid. + +### Clearing all lists +1. Clear all module, task and exam lists. + 1. Test case: `clearall`
+ Expected: Module, task and exam lists cleared. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..48fbc593c53 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,912 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +## Introduction -* Table of Contents -{:toc} +Welcome to the User Guide of MODPRO! + +MODPRO is a desktop application which helps NUS students track the progress of their modules. + +MODPRO helps you… +* organise your tasks by modules and exams +* track your progress for each module and exam with a progress bar +* easily navigate through your tasks by tagging them, and filtering and sorting your task list + +It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. If you struggle to type fast, we also provide a Graphical User interface (GUI) to assist you in using MODPRO. + +------------------ +
+ + +### The Graphical User Interface (GUI) + +To give you a quick overview of MODPRO, the following image shows you the main components of the GUI. + +| ![Ui](images/GUI.png) | +|:---------------------:| +| GUI of MODPRO | + +The table below summarises the function of each component. + +| **Component** | **Function** | +|---------------------------|--------------------------------------------------------------| +| Menu Bar | To view the help window and to exit the application | +| Command Input | To key in commands which will be executed | +| Feedback Message Display | To view the feedback given after the execution of a command | +| Task List | To view your tasks | +| Module List | To view your modules | +| Exam List | To view your exams | + + +### Icons and formatting used in the guide + + +This guide uses icons and formatting to differentiate between the different types of information so that it is comprehensible. The following table summarises the icons and formatting used, along with their meaning. + +| **Icon/Formatting** | **Meaning** | +|--------------------------|--------------------------------------------------------------| +| :information_source: | Extra information that is good to know | +| :exclamation: | Warnings regarding the use of specific commands and features | +| :bulb: | Tips on using MODPRO | +| `WORDS HIGHLIGHTED GREY` | Words that you can type into the Command Input | + + +### Purpose of the guide +This document is to assist you in using MODPRO smoothly and effectively to track your tasks and progress. + +* For first-time users, you can proceed to [Quick Start](#quick-start) for a guide on how to set up MODPRO and a short tutorial on the basic commands. Once you are familiar with the interface, you can start exploring our extensive list of features [here](#features) +* For the experienced users, you can learn more about these unique features that can help you manage your tasks better: [adding tags to your tasks](#adding-the-tags-to-a-task), [sorting your list](#sorting-the-task-list), [filtering your list](#filtering-the-task-list), [linking an exam to a task](#linking-an-exam) -------------------------------------------------------------------------------------------------------------------- +
+ + + +## Table of Contents +- [Quick Start](#quick-start) +- [Features](#features) + - [Modules-Related Features](#modules-related-features) + - [Adding a module](#adding-a-module) + - [Listing the modules](#listing-the-modules) + - [Finding a module](#finding-a-module) + - [Deleting a module](#deleting-a-module) + - [Editing a module](#editing-a-module) + - [Tasks-Related Features](#tasks-related-features) + - [Adding a task](#adding-a-task) + - [Deleting a task](#deleting-a-task) + - [Editing a task](#editing-a-task) + - [Marking a task](#marking-a-task) + - [Unmarking a task](#unmarking-a-task) + - [Listing the tasks](#listing-the-tasks) + - [Filtering the task list](#filtering-the-task-list) + - [Finding a task](#finding-a-task) + - [Sorting the task list](#sorting-the-task-list) + - [Adding the tags to a task](#adding-the-tags-to-a-task) + - [Editing the tags of a task](#editing-the-tags-of-a-task) + - [Deleting the tags of a task](#deleting-the-tags-of-a-task) + - [Clearing the task list](#clearing-the-task-list) + - [Exams-Related Features](#exams-related-features) + - [Adding an exam](#adding-an-exam) + - [Editing an exam](#editing-an-exam) + - [Deleting an exam](#deleting-an-exam) + - [Linking an exam](#linking-an-exam) + - [Unlinking an exam](#unlinking-an-exam) + - [Showing the tasks of an exam](#showing-the-tasks-of-an-exam) + - [Other Features](#other-features) + - [Clearing all the lists](#clearing-all-the-lists) + - [Opening the help window](#opening-the-help-window) + - [Exiting the program](#exiting-the-program) +- [Future Features](#1.1) +- [General](#general) + - [Saving data to the data file](#saving-data-to-the-data-file) + - [Editing the data file](#editing-the-data-file) +- [FAQ](#faq) +- [Summary Of Commands](#summary-of-commands) -## Quick start + +-------------------------------------------------------------------------------------------------------------------- + +## Quick start 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `modpro.jar` from [here](https://github.com/AY2223S1-CS2103T-F11-2/tp/releases/download/v1.4/modpro.jar). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for MODPRO. -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Double-click the file to start the app. The GUI similar to the image below should appear in a few seconds.
![Ui](images/Ui.png) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: - - * **`list`** : Lists all contacts. + + + - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +5. Type the command in the command box and press Enter to execute it.
+ e.g. typing help and pressing Enter will open the help window.
+ Here are some commands you can try: - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. + * `m add c/CS2100 m/Computer Organisation mc/4` : Adds the module called Computer Organisation with the module code CS2100 into the module list. + * `m list` : Lists all modules stored in the module list. + * `m delete 1` : Deletes the 1st module shown in the displayed module list. + * `exit` : Exits the app. - * **`clear`** : Deletes all contacts. + +6. Refer to the [Features](#features) below for details of each command. - * **`exit`** : Exits the app. +-------------------------------------------------------------------------------------------------------------------- +
-1. Refer to the [Features](#features) below for details of each command. --------------------------------------------------------------------------------------------------------------------- ## Features
+**:information_source: How to read the command format:**
+ +* Command words are case-insensitive.
+ e.g. `t add` is the same as `T add` for command words. -**:information_source: Notes about the command format:**
- -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +* Prefixes are case-sensitive.
+ e.g. `t add m/CS2030 d/Programming` is not the same as `t add M/CS2030 D/Programming` and the latter command will throw an error message. +* Words in UPPER_CASE are the parameters to be supplied by the user.
+ e.g. in `t add m/MODULE`, `MODULE` is a parameter which can be used as `t add m/CS2030`. + * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g `t/KEYWORD [SECOND_KEYWORD]` can be used as `t/priority deadline` or as `t/priority`. + +* Items in square brackets with * are optional, but at least one of them is required.
+ e.g `[m/MODULE]* [d/DESCRIPTION]*` can be used as `m/cs2030` or `m/cs2030 d/assignment` but not ` ` (none provided). + +* Parameters can be in any order.
+ e.g. if the command specifies `m/MODULE d/DESCRIPTION`, `d/DESCRIPTION m/MODULE` is also acceptable. + +* If a parameter is expected only once in the command and it is specified multiple times, only the last occurrence of the parameter will be taken.
+ e.g. if you specify `m/cs2030 m/cs2040`, only `m/cs2040` will be taken. + +* Extraneous parameters for commands that do not take in parameters (such as help, exit and clearall) will be ignored.
+ e.g. if the command specifies `help 123`, it will be interpreted as `help`. +
-* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +-------------------------- +
-* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +## Modules-related Features + +### Adding a module +Adds a module into the stored module list. + +Format: `m add c/MODULE_CODE m/MODULE_NAME mc/MODULE_CREDIT` + +Parameters: +* `MODULE_CODE` refers to the module code of the module being added. +* `MODULE_NAME` refers to the name of the module. +* `MODULE_CREDIT` refers to the number of module credits that the module has. +Restrictions: +* `MODULE_CODE` + * `MODULE_CODE` should be at least 6 characters long. + * The first two characters of `MODULE_CODE` should be alphabetical and the remaining characters should be alphanumeric. +* `MODULE_NAME` should not be empty. +* `MODULE_CREDIT` should be an integer between 0 and 45 inclusive. + +
+ +:information_source: **Note:** `MODULE_CODE` is case-insensitive.
-### Viewing help : `help` +Examples: -Shows a message explaning how to access the help page. +`m add c/cs2100 m/computer organisation mc/4` adds a module with the module code 'cs2100', the module name 'computer organisation' and the module credit '4'. -![help message](images/helpMessage.png) +`m add c/cs2105 m/networking mc/3` adds a module with the module code 'cs2105', the module name 'networking' and the module credit '3'. -Format: `help` +| ![AddModuleCommand](images/AddModuleCommandDemo.png) | +|:-----------------------------------------------------------------------:| +| Demonstration of Command: `m add c/cs2100 m/computer organisation mc/4` | -### Adding a person: `add` +### Listing the modules +Lists all modules in the stored module list. -Adds a person to the address book. +Format: `m list` -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +### Finding a module +Finds modules in the stored module list whose module code matches the `KEYWORD` partially or fully. -
:bulb: **Tip:** -A person can have any number of tags (including 0) +Format: `m find KEYWORD` + +Parameter: +* `KEYWORD` refers to the keyword inputted by the user. + +
+ +:information_source: **Note:** `KEYWORD` is case-insensitive.
+------------------ +
+ Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` -### Listing all persons : `list` +`m find CS` finds modules whose module code contains the `KEYWORD` 'CS' such as 'CS2030S', 'CS2040S'. -Shows a list of all persons in the address book. +`m find 30` finds modules whose module code contains the `KEYWORD` '30' such as 'CS2030S'. -Format: `list` +### Deleting a module +Deletes the specified module from the stored module list. -### Editing a person : `edit` +Format: `m del INDEX` -Edits an existing person in the address book. +Parameter: +* `INDEX` refers to the index number (shown in the displayed module list) of the module to be deleted. + +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of modules in the displayed module list. + +Examples: -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +`m del 1` deletes the first module in the displayed module list. -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +`m del 3` deletes the third module in the displayed module list. + +
+:exclamation: **Warning:** All tasks and exams related to the module will be deleted after the specified module is deleted. +
+ +------------------ +
+ + +### Editing a module +Edits the specified module by updating the existing values to the input values. + +Format: `m edit INDEX [c/MODULE_CODE]* [m/MODULE_NAME]* [mc/MODULE_CREDIT]*` + +Parameters: +* `INDEX` refers to the index number (shown in the displayed module list) of the module to be edited. +* `MODULE_CODE` refers to the module code that will replace the existing module code of the module specified. +* `MODULE_NAME` refers to the module name that will replace the existing module name of the module specified. +* `MODULE_CREDIT` refers to the module credit that will replace the existing module credit of the module specified. + +
+:information_source: **Note:** `MODULE_CODE` is case-insensitive. +
+ +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of modules in the displayed module list. +* `MODULE_CODE` + * `MODULE_CODE` should be at least 6 characters long. + * The first two characters of `MODULE_CODE` should be alphabetical and the remaining characters should be alphanumeric. + * `MODULE_CODE` should not be the module code of an existing module in the stored module list. +* `MODULE_NAME` should not be empty. +* `MODULE_CREDIT` + * `MODULE_CREDIT` should not be empty. + * `MODULE_CREDIT` should be an integer between 0 and 45 inclusive. +* The input values should not be the same as existing values. +* The edited module should not be the same as any existing module in the stored module list. + +
+:exclamation: **Warning:** If the module code of the module is edited, and the module is related to some tasks or exams, the module of these tasks and exams will be changed to this edited module. +
Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -### Locating persons by name: `find` +`m edit 1 m/Programming Methodology I mc/4` changes the module name of the first module in the displayed module list to 'Programming Methodology I' and the module credit to '4'. + +`m edit 2 c/cs2040 m/Data Structures and Algorithms mc/4` changes the module name of the second module in the displayed module list to ‘Data Structures and Algorithms’, the module code to ‘cs2040’ and the module credit to ‘4’. + +## Tasks-related Features + +### Adding a task +Adds a task into the stored task list. + +Format: `t add m/MODULE d/DESCRIPTION` + +Parameters: +* `MODULE` refers to the module code of the module which the task belongs to. +* `DESCRIPTION` refers to the task description to be shown. + +
+ +:information_source: **Note:** `MODULE` is case-insensitive. +
+ +Restrictions: +* `MODULE` + * `MODULE` should be at least 6 characters long. + * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric. + * `MODULE` should be the module code of an existing module in the stored module list. +* `DESCRIPTION` should not be empty. + +Example: +`t add m/CS2105 d/Assignment 1` adds a task with the module as 'CS2105' and description as 'Assignment 1' into the stored task list. -Finds persons whose names contain any of the given keywords. +| ![AddTaskCommand](images/AddTaskCommandDemo.png) | +|:---------------------------------------------------------:| +| Demonstration of Command: `t add m/CS2105 d/Assignment 1` | -Format: `find KEYWORD [MORE_KEYWORDS]` +### Deleting a task +Deletes the specified task from the stored task list. -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +Format: `t del INDEX` + +Parameter: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to be deleted. + +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of tasks in the displayed task list. Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +`t del 1` deletes the first task in the displayed task list. + +`t del 3` deletes the third task in the displayed task list. + +
+:exclamation: **Warning:** When the task is deleted, if it has a link to an exam, the task will be unlinked from the exam. +
+ +### Editing a task +Edits the specified task by updating the existing values to the input values. + +Format: `t edit INDEX [m/MODULE]* [d/DESCRIPTION]*` + +Parameters: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to be edited. +* `MODULE` refers to the module code of the module that will replace the existing module of the task specified. +* `DESCRIPTION` refers to the description that will replace the existing description of the task specified. -Deletes the specified person from the address book. +
-Format: `delete INDEX` +:information_source: **Note:** `MODULE` is case-insensitive. +
-* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of tasks in the displayed task list. +* `MODULE` + * `MODULE` should be at least 6 characters long. + * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric. + * `MODULE` should be the module code of an existing module in the stored module list. +* `DESCRIPTION` should not be empty. +* The input values should not be the same as existing values. +* The edited task should not be the same as any existing task in the stored task list. + +
+ +:exclamation: **Warning:** If a task is linked to an exam, and its module is changed, the task will be unlinked from the exam. +
Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. -### Clearing all entries : `clear` +`t edit 1 d/Assignment 2` changes the description of the first task in the displayed task list to 'Assignment 2'. -Clears all entries from the address book. +`t edit 2 m/CS2040 d/tutorial 2` changes the module and description of the second task in the displayed task list to 'CS2040' and 'tutorial 2' respectively. -Format: `clear` +### Marking a task +Indicates the specified task is completed. -### Exiting the program : `exit` +Format: `t mark INDEX` -Exits the program. +Parameter: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to be marked. -Format: `exit` +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of tasks in the displayed task list. +* The task specified should not be already marked. + +Examples: -### Saving the data +`t mark 1` indicates the first task in the displayed task list is completed. -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +`t mark 3` indicates the third task in the displayed task list is completed. -### Editing the data file +
+ +:bulb: **Tip:** You can sort and filter tasks based on their completion status. The percentage of completed tasks are also shown for each exam and module. +
+ +------------------ +
+ +| ![MarkTaskCommand](images/MarkTask.png) | +|:-----------------------------------------:| +| Demonstration of Command: `t mark 1` | + +### Unmarking a task +Indicates the specified task is not completed. + +Format: `t unmark INDEX` + +Parameter: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to be unmarked. + +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of tasks in the displayed task list. +* The task specified should not be already unmarked. + +Examples: + +`t unmark 1` indicates the first task in the displayed task list is not completed. + +`t unmark 3` indicates the third task in the displayed task list is not completed. + + +### Listing the tasks +Lists all tasks in the stored task list. + +Format: `t list` + + +### Filtering the task list +Filters the displayed task list to show only tasks that fulfil the module code, completion status, and/or link status conditions. + +Format: `t filter [m/MODULE/]* [c/COMPLETED]* [l/LINKED]*` + +Parameters: +* `MODULE` refers to the module code of the module to be filtered out. +* `COMPLETED` should be `y` to filter tasks that are complete or `n` to filter tasks that are incomplete. +* `LINKED` should be `y` to filter tasks that are linked to an exam or `n` to filter tasks that are not linked to any exam. + +
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +:information_source: **Note:** `MODULE` is case-insensitive. +
-
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. +Restrictions: +* `MODULE` + * `MODULE` should be at least 6 characters long. + * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric. + * `MODULE` should be the module code of an existing module in the stored module list. +* `COMPLETED` should be `y` or `n`. +* `LINKED` should be `y` or `n`. + +Examples: + +`t filter l/n` filters out all tasks that are currently not linked to any exam. + +`t filter m/cs2030 c/y` filters out all completed tasks that are under the module 'cs2030'. + +| ![FilterTasksCommand](images/FilterTasksCommandDemo.png) | +|:----------------------------------------------------------:| +| Demonstration of Command: `t filter l/n` | + +### Finding a task +Finds tasks in the stored task list whose task description matches the `KEYWORD` partially or fully. + +Format: `t find KEYWORD` + +Parameter: +* `KEYWORD` refers to the keyword inputted by the user. + +
+ +:information_source: **Note:** `KEYWORD` is case-insensitive.
-### Archiving data files `[coming in v2.0]` +Examples: -_Details coming soon ..._ +`t find work` finds tasks that contain the `KEYWORD` 'work' such as 'homework', 'work to do'. --------------------------------------------------------------------------------------------------------------------- +`t find do paper` finds tasks that contain the `KEYWORD` 'do paper', such as 'do paper one', 'do paper two'. -## FAQ +| ![FindCommand](images/FindCommandImage.png) | +|:---------------------------------------------:| +| Demonstration of Command: `t find work` | + +### Sorting the task list +Sorts tasks in the task list based on the criteria specified. + +Format: `t sort c/CRITERIA` + +Parameter: +* `CRITERIA` refers to the criteria that is used for sorting the task list. + +Restrictions: +* `CRITERIA` can be either `priority`, `deadline`, `module` or `description`. +* When sorting by priority, tasks marked with priority status `HIGH` will appear at the top of the displayed task list, followed by tasks with priority status`MEDIUM`, and lastly tasks with priority status `LOW`. +* When sorting by deadline, tasks with the earlier deadlines will appear at the top while tasks with no deadlines will appear at the bottom of the displayed task list. +* When sorting by module, all tasks will be sorted by the module code in alphanumeric order. +* When sorting by description, all tasks will be sorted by the task description in alphanumeric order. + +
+ +:information_source: **Note:** `CRITERIA` is case-insensitive. +
+ +Examples: + +`t sort c/description` sorts all the tasks in the task list by task description. + +`t sort c/priority` sorts all the tasks in the task list by priority status. + +`t sort c/deadline` sorts all the tasks in the task list by deadline. + +`t sort c/module` sorts all the tasks in the task list by module code. + +| ![SortTaskCommand](images/SortTaskCommandDemo.png) | +|:---------------------------------------------------:| +| Demonstration of Command: `t sort c/description` | + +### Adding the tags to a task +Tags the specified task with a priority status and/or the deadline to complete the task. -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +Format: `t tagadd INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*` + +Parameters: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to be tagged. +* `PRIORITY_STATUS` refers to the priority status which is tagged to the task in the displayed task list. +* `DEADLINE` refers to the deadline which is tagged to the task in the displayed task list. + +--------------------------------------------------------- +
+ +
+ +:information_source: **Note:** `PRIORITY_STATUS` is case-insensitive. +
+ +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of tasks in the displayed task list. +* `PRIORITY STATUS` should be one of these three priorities: `HIGH`, `MEDIUM`, `LOW`. +* `DEADLINE` should be in the format DD-MM-YYYY and not earlier than the current date. + +Examples: + +`t tagadd 1 p/HIGH` tags the first task in the displayed task list with the priority status of 'high'. + +`t tagadd 1 dl/31-12-2022` tags the first task in the displayed task list with the deadline of '31-12-2022'. + +`t tagadd 2 p/low dl/31-12-2022` tags the second task in the displayed task list with a priority status of +'low' and a deadline of '31-12-2022'. + +| ![AddTagCommand](images/AddTagCommandDemo.png) | +|:-----------------------------------------------:| +| Demonstration of Command: `t tagadd 1 p/HIGH` | + + +### Editing the tags of a task +Edits the tags associated with the specified task. + +Format: `t tagedit INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*` + +Parameters: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to edit the tags. +* `PRIORITY_STATUS` refers to the priority status which is tagged to the task in the displayed task list. +* `DEADLINE` refers to the deadline which is tagged to the task in the displayed task list. + +
+ +:information_source: **Note:** `PRIORITY_STATUS` is case-insensitive. +
+ +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of tasks in the displayed task list. +* `PRIORITY STATUS` + * `PRIORITY STATUS` should be one of these three priorities: `HIGH`, `MEDIUM`, `LOW`. + * `PRIORITY_STATUS` of the task should not be the same as the current priority status of the task. +* `DEADLINE` + * `DEADLINE` should be in the format DD-MM-YYYY and not earlier than the current date. + * `DEADLINE` should not be the same as the current deadline of the task. +* To edit the `PRIORITY_STATUS` of the task, the task should already have a priority status tagged to it. +* To edit the `DEADLINE` of the task, the task should already have a deadline tagged to it. + +Examples: + +`t tagadd 1 p/HIGH` followed by `t tagedit 1 p/LOW` updates priority status of the task +from 'high' to 'low'. + +`t tagadd 1 dl/31-12-2022` followed by `t tagedit 1 dl/31-11-2022` updates the deadline of the task +from '31-12-2022' to '31-11-2022'. + +### Deleting the tags of a task +Deletes the tags associated with the specified task. + +Format: `t tagdel INDEX t/KEYWORD [SECOND_KEYWORD]` + +Parameters: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to remove the tags. +* `KEYWORD` refers to the first keyword which indicates the type of tag that can be removed. +* `SECOND_KEYWORD` refers to second keyword which indicates the type of tag that can be removed. + +
+ +:information_source: **Note:** `KEYWORD` and `SECOND_KEYWORD` are case-insensitive. +
+ +Restrictions: +* `INDEX` should be an integer greater than 0 and less than 2147483648. +* `INDEX` should not be greater than the number of tasks in the displayed task list. +* The list of keywords which can be used for `KEYWORD` and `SECOND_KEYWORD` are `priority` and `deadline`. +* If duplicate keywords are used, the duplicate keyword will be ignored. + +Examples: + +`t tagadd 1 p/HIGH` followed by `t tagdel 1 t/priority` deletes the priority status +of the first task in the displayed task list. + +`t tagadd 1 dl/24-11-2022` followed by `t tagdel 1 t/deadline` deletes the deadline +of the first task in the displayed task list. + +`t tagadd 2 p/LOW dl/31-12-2022` followed by `t tagdel 2 t/priority deadline` deletes +the priority status and deadline of the second task in the displayed task list. + +### Clearing the task list +Clears all tasks currently in the stored task list. + +Format: `t clear` -------------------------------------------------------------------------------------------------------------------- -## Command summary - -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +## Exams-related Features + +### Adding an exam +Adds an exam into the stored exam list. + +Format: `e add m/MODULE ex/EXAM_DESCRIPTION ed/EXAM_DATE` + +Parameters: +* `MODULE` refers to the module code of the module of the exam to be added. +* `EXAM_DESCRIPTION` refers to the description of the exam to be added. +* `EXAM_DATE` refers to the date of the exam to be added. + +
+ +:information_source: **Note:** `MODULE` is case-insensitive. +
+ +Restrictions: +* `MODULE` + * `MODULE` should be at least 6 characters long. + * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric. + * `MODULE` should be the module code of an existing module in the stored module list. +* `EXAM_DESCRIPTION` should not be empty. +* `EXAM_DATE` should be in the format DD-MM-YYYY and not earlier than the current date. +* The exam to be added should not be the same as any existing exam in the stored exam list. + +Example: + +`e add m/CS2100 ex/midterms ed/20-08-2023` adds the exam with the exam module as 'CS2100', +exam description as 'midterms', exam date as '20-08-2023' into the stored exam list. + +| ![AddExamCommand](images/AddExamImage.png) | +|:--------------------------------------------------------------------:| +| Demonstration of Command: `e add m/CS2100 ex/midterms ed/20-08-2023` | + +### Editing an exam +Edits the specified exam by updating the existing values to the input values. + +Format: `e edit INDEX [m/MODULE]* [ex/EXAM_DESCRIPTION]* [ed/EXAM_DATE]*` + +Parameters: +* `INDEX` refers to the index number (shown in the displayed exam list) of the exam to be edited. +* `MODULE` refers to the module code of the module that will replace the existing module of the exam specified. +* `EXAM_DESCRIPTION` refers to the exam description that will replace the existing exam description of the exam specified. +* `EXAM_DATE` refers to the exam date that will replace the existing exam date of the exam specified. + +
+ +:information_source: **Note:** `MODULE` is case-insensitive. +
+ +Restrictions: +* `INDEX` + * `INDEX` should be an integer greater than 0 and less than 2147483648. + * `INDEX` should not be greater than the number of exams in the displayed exam list. +* `MODULE` + * `MODULE` should be at least 6 characters long. + * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric. + * `MODULE` should be the module code of an existing module in the stored module list. +* `EXAM_DESCRIPTION` should not be empty. +* `EXAM_DATE` should be in the format DD-MM-YYYY and not earlier than the current date. +* The input values should not be the same as existing values. +* The edited exam should not be the same as any existing exam in the stored exam list. + +
+ +:exclamation: **Warning:** If the exam is linked to some tasks, and the module of the exam is changed, the tasks will be unlinked from the exam. +
+ +Examples: + +`e edit 1 ex/finals ed/20-12-2022` changes the exam description of the first exam in the displayed exam list to ‘finals’ and the exam date to ‘20-12-2022’. + +`e edit 2 m/CS2030S ex/midterms ed/22-12-2022` changes the exam description of the second exam in the displayed exam list to ‘midterms’, the exam module to ‘CS2030S’ and the exam date to ‘22-12-2022’. + + +### Deleting an exam +Deletes the specified exam from the stored exam list. + +Format: `e del INDEX` + +Parameter: +* `INDEX` refers to the index number (shown in the displayed exam list) of the exam to be deleted. + +Restrictions: +* `INDEX` should be an integer greater than 0 and less than 2147483648. +* `INDEX` should not be greater than the number of exams in the displayed exam list. + +Example: + +`e del 1` deletes the first exam in the displayed exam list. +
+ +:exclamation: **Warning:** All tasks currently linked to the exam will be unlinked after the exam is deleted. +
+ +### Linking an exam +Links the specified task to the specified exam. + +Format `e link e/EXAM_INDEX t/TASK_INDEX` + +Parameters: +* `EXAM_INDEX` refers to the index number (shown in the displayed exam list) of the exam to be linked. +* `TASK_INDEX` refers to the index number (shown in the displayed task list) of the task to be linked. + +Restrictions: +* `EXAM_INDEX` and `TASK_INDEX` should be an integer greater than 0 and less than 2147483648. +* `EXAM_INDEX` should not be greater than the number of exams in the displayed exam list. +* `TASK_INDEX` should not be greater than the number of tasks in the displayed task list. + +Examples: + +`e link e/1 t/2` links the second task in the displayed task list to the first exam in the displayed exam list. + +`e link e/2 t/3` links the third task in the displayed task list to the second exam in the displayed exam list. + +| ![LinkExamCommand](images/LinkExamCommandDemo.png) | +|:--------------------------------------------------:| +| Demonstration of Command: `e link e/1 t/2` | + + +### Unlinking an exam +Unlinks the exam from the specified task. + +Format: `e unlink INDEX` + +Parameter: +* `INDEX` refers to the index number (shown in the displayed task list) of the task to be unlinked. + +Restrictions: +* `INDEX` should be an integer greater than 0 and less than 2147483648. +* `INDEX` should not be greater than the number of tasks in the displayed task list. + +Example: + +`e unlink 1` unlinks the first task in the displayed task list from its current exam. + +### Showing the tasks of an exam +Shows all tasks linked to the specified exam. + +Format: `e showt INDEX` + +Parameter: +* `INDEX` refers to the index number (shown in the displayed exam list) of the exam. + +Restrictions: +* `INDEX` should be an integer greater than 0 and less than 2147483648. +* `INDEX` should not be greater than the number of exams in the displayed exam list. + +Example: + +`e showt 1` shows a list of all tasks linked to the first exam in the displayed exam list. + +| ![ListExamTasksCommand](images/ListExamTasksCommandDemo.png) | +|:------------------------------------------------------------:| +| Demonstration of Command: `e showt 1` | + +## Other Features + +### Clearing all the lists +Clears all tasks, exams and modules currently in the respective stored lists. + +Format: `clearall` + +### Opening the help window +Opens the help window which displays the list of commands. + +Format: `help` + +### Exiting the program +Exits the program. + +Format: `exit` + +-------------------------------------------------------------- +## Future Features to be added (Coming Soon!!) + +To optimize your tracking process, MODPRO will let you: +* Edit the case sensitivity of module code in tasks, modules and exams. +* Tick checkboxes of tasks using the mouse. +* Add time and venue for exams. +* Undo commands. + +To provide you with a better user experience, MODPRO will: +* Improve the handling of duplicate prefixes in commands, by recognizing and rejecting them. + +-------------------------------------------------------------------------- +
+ +## General + + + + +### Saving data to the data file +* All MODPRO data will be manually saved to the hard disk after the execution of each command. +There is no need to manually save the data. + +### Editing the data file +* MODPRO data is saved as a JSON file at `[JAR FILE LOCATION]/data/modpro.json`. Advanced users are allowed +to modify the data at the JSON file. + +
+:exclamation: **Warning:** +If changes made to the modpro.json makes the format invalid or invalid data is used, +MODPRO will discard all data stored and start with an empty data file. +
+ + + +-------------------------------------------------------------------------- +## FAQ + +1. What is the difference between the stored list and a displayed list? (e.g. stored task list vs displayed task list) + * The stored task list is the list containing all the tasks you created, whereas the displayed task list is the one shown on your screen. + * The stored task list and displayed task list could be different after the filter or find command. + +----------------------------------------------------------------------------------------------------------------------------------------------------- + +## Summary of Commands + +| Command | Format and Examples | +|---------------|----------------------------------------------------------------------------------------------------------------------------------| +| **Module** | | +| **m add** | **Format**: `m add c/MODULE_CODE m/MODULE_NAME mc/MODULE_CREDIT`
**Example**: `m add c/cs2103t m/software engineering mc/4` | +| **m del** | **Format**: `m del INDEX`
**Example**: `m del 1` | +| **m edit** | **Format**: `m edit INDEX [c/MODULE_CODE]* [m/MODULE_NAME]* [mc/MODULE_CREDIT]*`
**Example**: `m edit 1 c/cs2040 mc/4` | +| **m list** | **Format**: `m list`
**Example**: `m list` | +| **m find** | **Format**: `m find KEYWORD`
**Example**: `m find cs` | +| **Task** | | +| **t add** | **Format**: `t add m/MODULE d/DESCRIPTION`
**Example**: `t add m/CS2105 d/Assignment 1` | +| **t del** | **Format**: `t del INDEX`
**Example**: `t del 1` | +| **t edit** | **Format**: `t edit INDEX [m/MODULE]* [d/DESCRIPTION]*`
**Example**: `t edit 1 d/Assignment 2` | +| **t mark** | **Format**: `t mark INDEX`
**Example**: `t mark 1` | +| **t unmark** | **Format**: `t unmark INDEX`
**Example**: `t unmark 1` | +| **t list** | **Format**: `t list`
**Example**: `t list` | +| **t sort** | **Format**: `t sort c/CRITERIA`
**Example**: `t sort c/priority` | +| **t filter** | **Format**: `t filter [m/MODULE]* [c/COMPLETED]* [l/LINKED]*`
**Example**: `t filter m/cs2030 c/y` | +| **t find** | **Format**: `t find KEYWORD`
**Example**: `t find watch lecture rec` | +| **t tagadd** | **Format**: `t tagadd INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*`
**Example**: `t tagadd 1 p/high dl/29-12-2022` | +| **t tagdel** | **Format**: `t tagdel INDEX t/KEYWORD [SECOND_KEYWORD]`
**Example**:`t tagdel 1 t/priority` | +| **t tagedit** | **Format**: `t tagedit INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*`
**Example**: `t tagedit 1 p/medium` | +| **t clear** | **Format**: `t clear`
**Example**: `t clear` | +| **Exam** | | +| **e add** | **Format**: `e add m/MODULE ex/EXAM_DESCRIPTION ed/EXAM_DATE`
**Example**: `e add m/cs2013t ex/practical ed/29-10-2022` | +| **e edit** | **Format**: `e edit INDEX [m/MODULE]* [ex/EXAM_DESCRIPTION]* [ed/EXAM_DATE]*`
**Example**: `e edit 1 m/cs2040` | +| **e del** | **Format**: `e del INDEX`
**Example**: `e del 1` | +| **e link** | **Format**: `e link e/EXAM_INDEX t/TASK_INDEX`
**Example**: `e link e/1 t/2` | +| **e unlink** | **Format**: `e unlink INDEX`
**Example**: `e unlink 1` | +| **e showt** | **Format**: `e showt INDEX`
**Example**: `e showt 1` | +| **Others** | | +| **clearall** | **Format**: `clearall`
**Example**: `clearall` | +| **help** | **Format**: `help`
**Example**: `help` | +| **exit** | **Format**: `exit`
**Example**: `exit` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..74d19a4e233 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "MODPRO" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103T-F11-2/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..9a0e41f47a6 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "MODPRO"; font-size: 32px; } } diff --git a/docs/diagrams/AddExamActivityDiagram.puml b/docs/diagrams/AddExamActivityDiagram.puml new file mode 100644 index 00000000000..8acf40f45ab --- /dev/null +++ b/docs/diagrams/AddExamActivityDiagram.puml @@ -0,0 +1,28 @@ +@startuml +start +:User executes command; +:Parse Command; +if () then ([Fields to be edited are valid and command is valid]) + if () then([The exam module exists in MODPRO]) + + if() then([The exam already exists in MODPRO]) + :Display error message + saying that exam already exists in MODPRO.; + else([else]) + :Add exam to exam list; + :Display message to show that + the exam is added successfully to the exam list.; + endif + else([else]) + :Display error message; + endif +else ([else]) + :Display error message regarding + invalid fields or invalid command; + +endif + +stop + +@enduml + diff --git a/docs/diagrams/AddExamCommandSequenceDiagram.puml b/docs/diagrams/AddExamCommandSequenceDiagram.puml new file mode 100644 index 00000000000..c5ed103466d --- /dev/null +++ b/docs/diagrams/AddExamCommandSequenceDiagram.puml @@ -0,0 +1,85 @@ +]]@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddExamCommandParser" as AddExamCommandParser LOGIC_COLOR +participant "ex:AddExamCommand" as AddExamCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("e add m/cs2030s ex/midterm ed/20-12-2022") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("e add...") +activate AddressBookParser + +create AddExamCommandParser +AddressBookParser -> AddExamCommandParser +activate AddExamCommandParser + +AddExamCommandParser --> AddressBookParser +deactivate AddExamCommandParser + +AddressBookParser -> AddExamCommandParser: parse(m/cs2...) +activate AddExamCommandParser + +create AddExamCommand +AddExamCommandParser -> AddExamCommand +activate AddExamCommand + +AddExamCommand --> AddExamCommandParser : ex +deactivate AddExamCommand + +AddExamCommandParser --> AddressBookParser : ex +deactivate AddExamCommandParser +AddExamCommandParser [hidden]--> AddressBookParser +destroy AddExamCommandParser + +AddressBookParser --> LogicManager : ex +deactivate AddressBookParser + +LogicManager -> AddExamCommand : execute(model) +activate AddExamCommand + +AddExamCommand -> Model: hasModule(exam) +activate Model +Model --> AddExamCommand +deactivate Model + + + +AddExamCommand -> Model: hasExam(exam) +activate Model +Model --> AddExamCommand +deactivate Model + +AddExamCommand -> Model: addExam(exam) +activate Model + +Model --> AddExamCommand +deactivate Model + + + +create CommandResult +AddExamCommand -> CommandResult: CommandResult(MESSAGE_SUCCESS) + +activate CommandResult +CommandResult --> AddExamCommand +deactivate CommandResult + +AddExamCommand --> LogicManager: result +deactivate AddExamCommand + + + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..dd2a6304dfc 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,13 +7,13 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "t delete 3" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("t delete 3") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteTask(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/DeleteTaskActivityDiagram.puml b/docs/diagrams/DeleteTaskActivityDiagram.puml new file mode 100644 index 00000000000..5e15238a420 --- /dev/null +++ b/docs/diagrams/DeleteTaskActivityDiagram.puml @@ -0,0 +1,20 @@ +@startuml +start +:User executes the delete exam command; +if () then ([invalid command format]) + :Display invalid command format error message; +else ([else]) + if () then ([invalid task index]) + :Display invalid task index shown error message; + + else ([else]) + : delete task; + if () then ([deleted task is linked]) + :Display message that task has been deleted successfully with link dropped; + else ([else]) + :Display message that task has been deleted successfully; + endif + endif +endif +stop +@enduml diff --git a/docs/diagrams/DeleteTaskSequenceDiagram.puml b/docs/diagrams/DeleteTaskSequenceDiagram.puml new file mode 100644 index 00000000000..918ae466fa8 --- /dev/null +++ b/docs/diagrams/DeleteTaskSequenceDiagram.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteTaskCommandParser" as DeleteTaskCommandParser LOGIC_COLOR +participant ":DeleteTaskCommand" as DeleteTaskCommand LOGIC_COLOR +participant ":MarkCommand" as MarkCommand LOGIC_COLOR +participant "taskToDelete:Task" as TargetTask LOGIC_COLOR +participant "markedTask:Task" as MarkedTask LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("t del 1") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("mark 1") +activate AddressBookParser + +create DeleteTaskCommandParser +AddressBookParser -> DeleteTaskCommandParser +activate DeleteTaskCommandParser + +DeleteTaskCommandParser --> AddressBookParser +deactivate DeleteTaskCommandParser + +AddressBookParser -> DeleteTaskCommandParser : parse("1") +activate DeleteTaskCommandParser + +create DeleteTaskCommand +DeleteTaskCommandParser -> DeleteTaskCommand +activate DeleteTaskCommand + +DeleteTaskCommand --> DeleteTaskCommandParser +deactivate DeleteTaskCommand + +DeleteTaskCommandParser --> AddressBookParser +deactivate DeleteTaskCommandParser + +DeleteTaskCommandParser -[hidden]-> AddressBookParser +destroy DeleteTaskCommandParser + +AddressBookParser --> LogicManager +deactivate AddressBookParser + +LogicManager -> DeleteTaskCommand: execute() +activate DeleteTaskCommand + +DeleteTaskCommand -> Model : getFilteredTaskList().get(targetIndex.getZeroBased()) +activate Model + +Model --> DeleteTaskCommand : taskToDelete +deactivate Model + +DeleteTaskCommand -> Model : deleteTask(taskToDelete) +activate Model + +Model --> DeleteTaskCommand +deactivate Model + +create CommandResult +DeleteTaskCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteTaskCommand +deactivate CommandResult + +DeleteTaskCommand --> LogicManager: result +deactivate DeleteTaskCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/EditTaskActivityDiagram.puml b/docs/diagrams/EditTaskActivityDiagram.puml new file mode 100644 index 00000000000..db43e58a211 --- /dev/null +++ b/docs/diagrams/EditTaskActivityDiagram.puml @@ -0,0 +1,39 @@ +@startuml +start +:User executes the edit task command; + +switch () +case ( [invalid command] ) + :Display error message + regarding invalid command; +case ( [task is not edited] ) + :Display error message + stating the fields provided + are the same as the current + values; +case ( [duplicate task] ) + :Display error message + stating the edited task + is the same as another + existing task in the task + list; +case ( [module does not exist] ) + :Display error message + stating the module + provided does not exist + in the module list; +case ( [else] ) + if () then ( [task is linked and its module is changed] ) + :Unlink the task from its exam; + :Display message stating that + the task is successfully edited + and unlinked; + else ( [else] ) + :Display message stating that + the task is successfully edited; + endif + + +endswitch +stop +@enduml diff --git a/docs/diagrams/EditTaskReferenceDiagram.puml b/docs/diagrams/EditTaskReferenceDiagram.puml new file mode 100644 index 00000000000..6cf4218f40c --- /dev/null +++ b/docs/diagrams/EditTaskReferenceDiagram.puml @@ -0,0 +1,55 @@ +@startuml +!include style.puml +mainframe **sd** get taskToEdit and create editedTask + +box Logic LOGIC_COLOR_T1 +participant "c:EditTaskCommand" as EditTaskCommand LOGIC_COLOR + +end box + +box Model MODEL_COLOR_T1 +participant "taskToEdit:Task" as TargetTask MODEL_COLOR +participant "unlinkedEditedTask:Task" as UnlinkedEditedTask MODEL_COLOR +participant "linkedEditedTask:Task" as LinkedEditedTask MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + + +EditTaskCommand -> Model : get task at the specified index +activate Model + +Model --> EditTaskCommand : taskToEdit +deactivate Model + +EditTaskCommand -> TargetTask : edit() +activate TargetTask + +alt "module is changed" + +create UnlinkedEditedTask +TargetTask -> UnlinkedEditedTask +note right +unlinkedEditedTask has no +linked exam, while +linkedEditedTask keeps +any linked exam of taskToEdit +end note +activate UnlinkedEditedTask + +UnlinkedEditedTask --> TargetTask +deactivate UnlinkedEditedTask + +else "module not changed" +create LinkedEditedTask +TargetTask -> LinkedEditedTask +activate LinkedEditedTask + +LinkedEditedTask --> TargetTask +deactivate LinkedEditedTask +end + +TargetTask --> EditTaskCommand : editedTask +deactivate TargetTask +autonumber + +@enduml diff --git a/docs/diagrams/EditTaskSequenceDiagram.puml b/docs/diagrams/EditTaskSequenceDiagram.puml new file mode 100644 index 00000000000..78c52222a73 --- /dev/null +++ b/docs/diagrams/EditTaskSequenceDiagram.puml @@ -0,0 +1,86 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditTaskCommandParser" as EditTaskCommandParser LOGIC_COLOR +participant ":EditTaskDescriptor" as EditTaskDescriptor LOGIC_COLOR +participant "c:EditTaskCommand" as EditTaskCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("t edit d/task 1") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("t edit 2 d/task 1") +activate AddressBookParser + +create EditTaskCommandParser +AddressBookParser -> EditTaskCommandParser +activate EditTaskCommandParser + +EditTaskCommandParser --> AddressBookParser +deactivate EditTaskCommandParser + +AddressBookParser -> EditTaskCommandParser : parse("2 d/task 1") +activate EditTaskCommandParser + +create EditTaskDescriptor +EditTaskCommandParser -> EditTaskDescriptor +activate EditTaskDescriptor + +EditTaskDescriptor --> EditTaskCommandParser +deactivate EditTaskDescriptor + +create EditTaskCommand +EditTaskCommandParser -> EditTaskCommand +activate EditTaskCommand + +EditTaskCommand --> EditTaskCommandParser +deactivate EditTaskCommand + +EditTaskCommandParser --> AddressBookParser : c +deactivate EditTaskCommandParser + +EditTaskCommandParser -[hidden]-> AddressBookParser +destroy EditTaskCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> EditTaskCommand: execute() +activate EditTaskCommand + +ref over EditTaskCommand, Model +get taskToEdit and create editedTask +endref + +EditTaskCommand -> Model : replaceTask(taskToEdit, editedTask, false) +activate Model + +Model --> EditTaskCommand +deactivate Model + +create CommandResult +EditTaskCommand -> CommandResult +activate CommandResult + +CommandResult --> EditTaskCommand +deactivate CommandResult + +EditTaskCommand --> LogicManager : result +deactivate EditTaskCommand + +EditTaskCommand -[hidden]-> LogicManager +destroy EditTaskCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/FilterActivityDiagram.puml b/docs/diagrams/FilterActivityDiagram.puml new file mode 100644 index 00000000000..21de4c010f9 --- /dev/null +++ b/docs/diagrams/FilterActivityDiagram.puml @@ -0,0 +1,26 @@ +@startuml + +start +:User executes filter command; +if () then ([command is invalid]) + :Display invalid command + format error message and + filter message usage; +else ([else]) + if () then ([input fields are invalid]) + if () then ([module does not exist]) + :Display module not + found error message; + else ([else]) + :Display response + constraints error + message; + endif + else ([else]) + :Update filtered task list; + :Display filtered task list; + endif +endif +stop + +@enduml diff --git a/docs/diagrams/FilterSequenceDiagram.puml b/docs/diagrams/FilterSequenceDiagram.puml new file mode 100644 index 00000000000..74db52bf491 --- /dev/null +++ b/docs/diagrams/FilterSequenceDiagram.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FilterTasksCommandParser" as FilterTasksCommandParser LOGIC_COLOR +participant "predicate:FilterPredicate" as FilterPredicate LOGIC_COLOR +participant "f:FilterTasksCommand" as FilterTasksCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("t filter m/CS2103T c/y l/n") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("t filter m/CS2103T c/y l/n") +activate AddressBookParser + +create FilterTasksCommandParser +AddressBookParser -> FilterTasksCommandParser +activate FilterTasksCommandParser + +FilterTasksCommandParser --> AddressBookParser +deactivate FilterTasksCommandParser + +AddressBookParser -> FilterTasksCommandParser : parse("m/CS2103T c/y l/n") +activate FilterTasksCommandParser + +create FilterPredicate +FilterTasksCommandParser -> FilterPredicate +activate FilterPredicate + +FilterPredicate --> FilterTasksCommandParser +deactivate FilterPredicate + +create FilterTasksCommand +FilterTasksCommandParser -> FilterTasksCommand +activate FilterTasksCommand + +FilterTasksCommand --> FilterTasksCommandParser : f +deactivate FilterTasksCommand + +FilterTasksCommandParser --> AddressBookParser : f +deactivate FilterTasksCommandParser + +FilterTasksCommandParser [hidden]--> AddressBookParser +destroy FilterTasksCommandParser + +AddressBookParser --> LogicManager : f +deactivate AddressBookParser + +LogicManager -> FilterTasksCommand : execute() +activate FilterTasksCommand + +FilterTasksCommand -> Model : updateFilteredTaskList(predicate) +activate Model + +Model --> FilterTasksCommand +deactivate Model + +create CommandResult +FilterTasksCommand -> CommandResult : CommandResult(MESSAGE_SUCCESS) +activate CommandResult + +CommandResult --> FilterTasksCommand +deactivate CommandResult + +FilterTasksCommand --> LogicManager : result +deactivate FilterTasksCommand + +FilterTasksCommand -[hidden]-> LogicManager +destroy FilterTasksCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindTasksActivityDiagram.puml b/docs/diagrams/FindTasksActivityDiagram.puml new file mode 100644 index 00000000000..ae99180c1fe --- /dev/null +++ b/docs/diagrams/FindTasksActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start +:User execute keyword; +:Parse Command; +if () then ([keyword is not empty]) + :Update filtered task list; + :Displays the tasks which matches the keyword + with a message that states + the number of tasks that are listed. ; + +else ([else]) + :Display error message stating invalid format; + +endif + +stop + +@enduml diff --git a/docs/diagrams/FindTasksCommandSequenceDiagram.puml b/docs/diagrams/FindTasksCommandSequenceDiagram.puml new file mode 100644 index 00000000000..b2392cf89fe --- /dev/null +++ b/docs/diagrams/FindTasksCommandSequenceDiagram.puml @@ -0,0 +1,78 @@ +]]@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindTasksCommandParser" as FindTasksCommandParser LOGIC_COLOR +participant "ex:FindTasksCommand" as FindTasksCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant "pred: DescriptionContainsKeywordsPredicate" as DescriptionContainsKeywordsPredicate LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("t find task") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("t find task") +activate AddressBookParser + +create FindTasksCommandParser +AddressBookParser -> FindTasksCommandParser +activate FindTasksCommandParser + +FindTasksCommandParser --> AddressBookParser +deactivate FindTasksCommandParser + +AddressBookParser -> FindTasksCommandParser: parse(task) +activate FindTasksCommandParser + +create DescriptionContainsKeywordsPredicate +FindTasksCommandParser -> DescriptionContainsKeywordsPredicate +activate DescriptionContainsKeywordsPredicate + +DescriptionContainsKeywordsPredicate --> FindTasksCommandParser +deactivate DescriptionContainsKeywordsPredicate + +create FindTasksCommand +FindTasksCommandParser -> FindTasksCommand: FindTasksCommand(pred) +activate FindTasksCommand + +FindTasksCommand --> FindTasksCommandParser : ex +deactivate FindTasksCommand + + +FindTasksCommandParser --> AddressBookParser : ex +deactivate FindTasksCommandParser +FindTasksCommandParser [hidden]--> AddressBookParser +destroy FindTasksCommandParser + +AddressBookParser --> LogicManager : ex +deactivate AddressBookParser + +LogicManager -> FindTasksCommand : execute(model) +activate FindTasksCommand + +FindTasksCommand -> Model: updateFilteredTaskList(predicate) +activate Model +Model --> FindTasksCommand +deactivate Model + +create CommandResult +FindTasksCommand -> CommandResult: + +activate CommandResult +CommandResult --> FindTasksCommand +deactivate CommandResult + +FindTasksCommand --> LogicManager: result +deactivate FindTasksCommand + + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/LinkExamCommandActivityDiagram.puml b/docs/diagrams/LinkExamCommandActivityDiagram.puml new file mode 100644 index 00000000000..e37aab09312 --- /dev/null +++ b/docs/diagrams/LinkExamCommandActivityDiagram.puml @@ -0,0 +1,30 @@ +@startuml +start +:User executes LinkExamCommand; +if () then ([invalid command format]) + :Display invalid command format error message; +else ([else]) + if () then ([invalid task index]) + :Display invalid task index shown error message; + + else ([else]) + if () then ([invalid exam index]) + :Display invalid exam index shown error; + else ([else]) + if () then ([task is already linked]) + :Display task is task is already linked error; + else ([else]) + :Link task to the exam; + :Display message that task has been linked successfully; + endif + endif + endif +endif + + + + + +stop + +@enduml diff --git a/docs/diagrams/LinkExamCommandSequenceDiagram.puml b/docs/diagrams/LinkExamCommandSequenceDiagram.puml new file mode 100644 index 00000000000..5878103f5de --- /dev/null +++ b/docs/diagrams/LinkExamCommandSequenceDiagram.puml @@ -0,0 +1,117 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":LinkExamCommandParser" as LinkExamCommandParser LOGIC_COLOR +participant "le:LinkExamCommand" as LinkExamCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "task:Task" as Task MODEL_COLOR +participant "linkedTask:Task" as LinkedTask MODEL_COLOR +participant "exam:Exam" as Exam MODEL_COLOR +participant "Module" as Module MODEL_COLOR +end box + +[-> LogicManager : execute("e link e/1 t/1") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("e link e/1 t/1") +activate AddressBookParser + +create LinkExamCommandParser +AddressBookParser -> LinkExamCommandParser +activate LinkExamCommandParser + +LinkExamCommandParser --> AddressBookParser +deactivate LinkExamCommandParser + +AddressBookParser -> LinkExamCommandParser: parse("e/1 t/1") +activate LinkExamCommandParser + +create LinkExamCommand +LinkExamCommandParser -> LinkExamCommand: +activate LinkExamCommand + +LinkExamCommand --> LinkExamCommandParser : le +deactivate LinkExamCommand + +LinkExamCommandParser --> AddressBookParser : le +deactivate LinkExamCommandParser +LinkExamCommandParser [hidden]--> AddressBookParser +destroy LinkExamCommandParser + +AddressBookParser --> LogicManager : le +deactivate AddressBookParser + +LogicManager -> LinkExamCommand : execute(model) +activate LinkExamCommand + +LinkExamCommand -> Model: getFilteredTaskList() +activate Model + +Model --> LinkExamCommand +deactivate Model + +LinkExamCommand -> Model: getFilteredExamList() +activate Model + +Model --> LinkExamCommand +deactivate Model + +LinkExamCommand -> Task: isLinked() +activate Task + +Task --> LinkExamCommand +deactivate Task + +LinkExamCommand -> Task: getModule() +activate Task + +Task --> LinkExamCommand +deactivate Task + +LinkExamCommand -> Exam: getModule() +activate Exam + +Exam --> LinkExamCommand +deactivate Exam + +LinkExamCommand -> Module: isSameModule(task.module, exam.module) +activate Module + +Module --> LinkExamCommand +deactivate Module + +LinkExamCommand -> LinkedTask: linkTask(exam) +activate LinkedTask + +LinkedTask --> LinkExamCommand +deactivate LinkedTask + +LinkExamCommand -> Model: replaceTask(task, linkedTask, true) +activate Model + +Model --> LinkExamCommand +deactivate Model + +create CommandResult +LinkExamCommand -> CommandResult: CommandResult(EXAM_LINKED_SUCCESS) +activate CommandResult + +CommandResult --> LinkExamCommand +deactivate CommandResult + +LinkExamCommand --> LogicManager +deactivate LinkExamCommand +LinkExamCommand [hidden]--> LogicManager +destroy LinkExamCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/MarkTaskActivityDiagram.puml b/docs/diagrams/MarkTaskActivityDiagram.puml new file mode 100644 index 00000000000..97f1188f097 --- /dev/null +++ b/docs/diagrams/MarkTaskActivityDiagram.puml @@ -0,0 +1,23 @@ +@startuml +start +:User executes the mark command; +switch () +case ( [no valid number provided to indicate the index]) + :Display error message + regarding invalid command format; +case ( [invalid index]) + :Display error message + regarding invalid index; +case ( [task already marked] ) + :Display error message + stating task is already marked; +case ( [else] ) + :Display message stating that + the task is successfully marked; + :Tick the task and update the + progress bar for the module + of the task; +endswitch + +stop +@enduml diff --git a/docs/diagrams/MarkTaskReferenceDiagram.puml b/docs/diagrams/MarkTaskReferenceDiagram.puml new file mode 100644 index 00000000000..0c5c825ff7f --- /dev/null +++ b/docs/diagrams/MarkTaskReferenceDiagram.puml @@ -0,0 +1,37 @@ +@startuml +!include style.puml +mainframe **sd** get taskToMark and create markedTask + +box Logic LOGIC_COLOR_T1 +participant "c:MarkCommand" as MarkCommand LOGIC_COLOR + +end box + +box Model MODEL_COLOR_T1 +participant "taskToMark:Task" as TargetTask MODEL_COLOR +participant "markedTask:Task" as MarkedTask MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + + +MarkCommand -> Model : get task at the specified index +activate Model + +Model --> MarkCommand : taskToMark +deactivate Model + +MarkCommand -> TargetTask : mark() +activate TargetTask + +create MarkedTask +TargetTask -> MarkedTask +activate MarkedTask + +MarkedTask --> TargetTask +deactivate MarkedTask + +TargetTask --> MarkCommand : markedTask +deactivate TargetTask +autonumber + +@enduml diff --git a/docs/diagrams/MarkTaskSequenceDiagram.puml b/docs/diagrams/MarkTaskSequenceDiagram.puml new file mode 100644 index 00000000000..3c3946e8eee --- /dev/null +++ b/docs/diagrams/MarkTaskSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":MarkCommandParser" as MarkCommandParser LOGIC_COLOR +participant "c:MarkCommand" as MarkCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("t mark 1") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("t mark 1") +activate AddressBookParser + +create MarkCommandParser +AddressBookParser -> MarkCommandParser +activate MarkCommandParser + +MarkCommandParser --> AddressBookParser +deactivate MarkCommandParser + +AddressBookParser -> MarkCommandParser : parse("1") +activate MarkCommandParser + +create MarkCommand +MarkCommandParser -> MarkCommand +activate MarkCommand + +MarkCommand --> MarkCommandParser +deactivate MarkCommand + +MarkCommandParser --> AddressBookParser : c +deactivate MarkCommandParser + +MarkCommandParser -[hidden]-> AddressBookParser +destroy MarkCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> MarkCommand: execute() +activate MarkCommand + +ref over MarkCommand, Model +get taskToMark and create markedTask +endref + +MarkCommand -> Model : replaceTask(taskToMark, markedTask, true) +activate Model + +Model --> MarkCommand +deactivate Model + +create CommandResult +MarkCommand -> CommandResult +activate CommandResult + +CommandResult --> MarkCommand +deactivate CommandResult + +MarkCommand --> LogicManager : result +deactivate MarkCommand + +MarkCommand -[hidden]-> LogicManager +destroy MarkCommand + +[<--LogicManager +deactivate LogicManager + + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..d58b47e4741 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -12,14 +12,21 @@ Class AddressBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag - +Class DistinctTaskList +Class DistinctExamList +Class DistinctModuleList +Class Task +Class Module +Class Exam +Class ModuleCode +Class ModuleCredit +Class ModuleName +Class TaskDescription +Class TaskStatus +Class PriorityTag +Class DeadlineTag +Class ExamDate +Class ExamDescription } Class HiddenOutside #FFFFFF @@ -34,17 +41,51 @@ ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +AddressBook *--> "1" DistinctTaskList +AddressBook *--> "1" DistinctModuleList +AddressBook *--> "1" DistinctExamList +DistinctTaskList --> "~* all" Task +DistinctModuleList -> "~* all" Module +DistinctExamList ---> "~* all" Exam + +ModelManager -->"~* filtered" Task +ModelManager ---------->"~* filtered" Exam +ModelManager ------>"~* filtered" Module + +Module *--> "1" ModuleCode +Module *--> "0..1" ModuleName +Module *--> "0..1" ModuleCredit + +Task --> "1" Module +Task *---> "1" TaskDescription +Task *-----> "1" TaskStatus +Task --> "0..1" Exam +Task *--> "0..1" PriorityTag +Task *--> "0..1" DeadlineTag + +Exam ----> "1" Module +Exam *--> "1" ExamDate +Exam --> "1" ExamDescription + +DistinctTaskList -[hidden]up- AddressBook +DistinctTaskList -[hidden]up- ModelManager +DistinctModuleList -[hidden]up- AddressBook +DistinctModuleList -[hidden]up- ModelManager +DistinctExamList -[hidden]up- AddressBook +DistinctExamList -[hidden]up- ModelManager + +DistinctTaskList -[hidden]left- DistinctModuleList +DistinctModuleList -[hidden]left- DistinctExamList + +Task -[hidden]up- DistinctTaskList +Module -[hidden]up- DistinctModuleList +Exam -[hidden]up- DistinctExamList + + +Module -[hidden]right- Task + +DistinctExamList -[hidden]left- DistinctTaskList +DistinctTaskList -[hidden]left- DistinctModuleList -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email -ModelManager -->"~* filtered" Person @enduml diff --git a/docs/diagrams/SortTaskCommandActivityDiargam.puml b/docs/diagrams/SortTaskCommandActivityDiargam.puml new file mode 100644 index 00000000000..012faeae895 --- /dev/null +++ b/docs/diagrams/SortTaskCommandActivityDiargam.puml @@ -0,0 +1,21 @@ +@startuml +start +:User executes the sort task command; +if () then ([invalid command format]) + :Display invalid command format message; +else ([else]) + if () then ([invalid criteria chosen]) + :Display invalid criteria chosen error message; + else ([else]) + if () then ([Task list is empty]) + :Display there are no tasks to sort error message; + else ([else]) + :Sort the task in the task list; + :Display the task sorted successfully message; + endif + endif +endif + +stop + +@enduml diff --git a/docs/diagrams/SortTaskCommandSequenceDiagram.puml b/docs/diagrams/SortTaskCommandSequenceDiagram.puml new file mode 100644 index 00000000000..0bad84ddd91 --- /dev/null +++ b/docs/diagrams/SortTaskCommandSequenceDiagram.puml @@ -0,0 +1,77 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":SortTaskCommandParser" as SortTaskCommandParser LOGIC_COLOR +participant "st:SortTaskCommand" as SortTaskCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("t sort c/priority") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand("t sort c/priority") +activate AddressBookParser + +create SortTaskCommandParser +AddressBookParser -> SortTaskCommandParser +activate SortTaskCommandParser + +SortTaskCommandParser --> AddressBookParser +deactivate SortTaskCommandParser + +AddressBookParser -> SortTaskCommandParser: parse("c/priority") +activate SortTaskCommandParser + +create SortTaskCommand +SortTaskCommandParser -> SortTaskCommand +activate SortTaskCommand + +SortTaskCommand --> SortTaskCommandParser : st +deactivate SortTaskCommand + +SortTaskCommandParser --> AddressBookParser : st +deactivate SortTaskCommandParser +SortTaskCommandParser [hidden]--> AddressBookParser +destroy SortTaskCommandParser + +AddressBookParser --> LogicManager : st +deactivate AddressBookParser + +LogicManager -> SortTaskCommand : execute(model) +activate SortTaskCommand + +SortTaskCommand -> Model: sortTaskList(criteria) +activate Model + +Model --> SortTaskCommand +deactivate Model + +SortTaskCommand -> Model: getFilteredTaskList() +activate Model + +Model --> SortTaskCommand +deactivate Model + +create CommandResult +SortTaskCommand -> CommandResult: CommandResult(TASK_SORTED_SUCCESSFULLY) +activate CommandResult + +CommandResult --> SortTaskCommand +deactivate CommandResult + +SortTaskCommand --> LogicManager +deactivate SortTaskCommand +SortTaskCommand [hidden]--> LogicManager +destroy SortTaskCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..3aa1b331cca 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,8 +18,9 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedTask +Class JsonAdaptedModule +Class JsonAdaptedExam } } @@ -37,7 +38,9 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedModule +JsonSerializableAddressBook --> "*" JsonAdaptedExam +JsonSerializableAddressBook --> "*" JsonAdaptedTask + @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..97c30856db0 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,13 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard + +Class ModuleListPanel +Class ModuleCard +Class TaskListPanel +Class TaskCard +Class ExamListPanel +Class ExamCard Class StatusBarFooter Class CommandBox } @@ -32,26 +37,38 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" ModuleListPanel +MainWindow *-down-> "1" TaskListPanel +MainWindow *-down-> "1" ExamListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard +TaskListPanel -down-> "*" TaskCard +ModuleListPanel -down-> "*" ModuleCard +ExamListPanel -down-> "*" ExamCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +ModuleListPanel --|> UiPart +ModuleCard --|> UiPart +TaskListPanel --|> UiPart +TaskCard --|> UiPart +ExamListPanel --|> UiPart +ExamCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +TaskCard ..> Model +ModuleCard ..> Model +ExamCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +ModuleListPanel -[hidden]left- HelpWindow +TaskListPanel -[hidden]left- ModuleListPanel +ExamListPanel -[hidden]left- TaskListPanel HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UnlinkExamActivityDiagram.puml b/docs/diagrams/UnlinkExamActivityDiagram.puml new file mode 100644 index 00000000000..06cbe31d19f --- /dev/null +++ b/docs/diagrams/UnlinkExamActivityDiagram.puml @@ -0,0 +1,35 @@ +@startuml + +start +:User executes unlink exam command; +if () then ([command is invalid]) + :Display invalid command + format error message and + unlink exam message usage; +else ([else]) + if () then ([index is invalid]) + :Display error message + with valid integer range; + else ([else]) + if () then ([index is outside range of task list]) + :Display error message + with valid indexes for + task list; + else ([else]) + if () then ([task is already unlinked]) + :Display error message + that task is already unlinked; + else ([else]) + :Create a copy of the task + at the specified index, + but unlinked; + :Replace the task at + the specified index with + the unlinked task; + endif + endif + endif +endif +stop + +@enduml diff --git a/docs/diagrams/UnlinkExamSequenceDiagram.puml b/docs/diagrams/UnlinkExamSequenceDiagram.puml new file mode 100644 index 00000000000..1ca83f3cc1a --- /dev/null +++ b/docs/diagrams/UnlinkExamSequenceDiagram.puml @@ -0,0 +1,94 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":UnlinkExamCommandParser" as UnlinkExamCommandParser LOGIC_COLOR +participant "u:UnlinkExamCommand" as UnlinkExamCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "taskToUnlink:Task" as TaskToUnlink MODEL_COLOR +participant "unlinkedTask:Task" as UnlinkedTask MODEL_COLOR +end box + +[-> LogicManager : execute("e unlink 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("e unlink 1") +activate AddressBookParser + +create UnlinkExamCommandParser +AddressBookParser -> UnlinkExamCommandParser +activate UnlinkExamCommandParser + +UnlinkExamCommandParser --> AddressBookParser +deactivate UnlinkExamCommandParser + +AddressBookParser -> UnlinkExamCommandParser : parse("1") +activate UnlinkExamCommandParser + +create UnlinkExamCommand +UnlinkExamCommandParser -> UnlinkExamCommand +activate UnlinkExamCommand + +UnlinkExamCommand --> UnlinkExamCommandParser +deactivate UnlinkExamCommand + +UnlinkExamCommandParser --> AddressBookParser : u +deactivate UnlinkExamCommandParser + +UnlinkExamCommandParser -[hidden]-> AddressBookParser +destroy UnlinkExamCommandParser + +AddressBookParser --> LogicManager : u +deactivate AddressBookParser + +LogicManager -> UnlinkExamCommand : execute() +activate UnlinkExamCommand + +UnlinkExamCommand -> Model : getFilteredTaskList() +activate Model + +Model --> UnlinkExamCommand +deactivate Model + +UnlinkExamCommand -> TaskToUnlink : unlinkTask() +activate TaskToUnlink + +create UnlinkedTask +TaskToUnlink -> UnlinkedTask +activate UnlinkedTask + +UnlinkedTask --> TaskToUnlink +deactivate UnlinkedTask + +TaskToUnlink --> UnlinkExamCommand : unlinkedTask +deactivate TaskToUnlink + +UnlinkExamCommand -> Model : replaceTask(taskToUnlink, unlinkedTask, true) +activate Model + +Model --> UnlinkExamCommand +deactivate Model + +create CommandResult +UnlinkExamCommand -> CommandResult : CommandResult(EXAM_UNLINKED_SUCCESS) +activate CommandResult + +CommandResult --> UnlinkExamCommand +deactivate CommandResult + +UnlinkExamCommand --> LogicManager : result +deactivate UnlinkExamCommand + +UnlinkExamCommand -[hidden]-> LogicManager +destroy UnlinkExamCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/images/AddExamActivityDiagram.png b/docs/images/AddExamActivityDiagram.png new file mode 100644 index 00000000000..a06dc393f4a Binary files /dev/null and b/docs/images/AddExamActivityDiagram.png differ diff --git a/docs/images/AddExamCommandSequenceDiagram.png b/docs/images/AddExamCommandSequenceDiagram.png new file mode 100644 index 00000000000..15c23a5a2e6 Binary files /dev/null and b/docs/images/AddExamCommandSequenceDiagram.png differ diff --git a/docs/images/AddExamImage.png b/docs/images/AddExamImage.png new file mode 100644 index 00000000000..4eef7ba49fd Binary files /dev/null and b/docs/images/AddExamImage.png differ diff --git a/docs/images/AddModuleCommandDemo.png b/docs/images/AddModuleCommandDemo.png new file mode 100644 index 00000000000..3db4222116e Binary files /dev/null and b/docs/images/AddModuleCommandDemo.png differ diff --git a/docs/images/AddTagCommandDemo.png b/docs/images/AddTagCommandDemo.png new file mode 100644 index 00000000000..c53826a8de7 Binary files /dev/null and b/docs/images/AddTagCommandDemo.png differ diff --git a/docs/images/AddTaskCommandDemo.png b/docs/images/AddTaskCommandDemo.png new file mode 100644 index 00000000000..99c5e8a5187 Binary files /dev/null and b/docs/images/AddTaskCommandDemo.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..d652f6cd3e4 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/DeleteTaskActivityDiagram.png b/docs/images/DeleteTaskActivityDiagram.png new file mode 100644 index 00000000000..e037c52308f Binary files /dev/null and b/docs/images/DeleteTaskActivityDiagram.png differ diff --git a/docs/images/DeleteTaskSequenceDiagram.png b/docs/images/DeleteTaskSequenceDiagram.png new file mode 100644 index 00000000000..60ef0c3c288 Binary files /dev/null and b/docs/images/DeleteTaskSequenceDiagram.png differ diff --git a/docs/images/EditTaskActivityDiagram.png b/docs/images/EditTaskActivityDiagram.png new file mode 100644 index 00000000000..435029767cf Binary files /dev/null and b/docs/images/EditTaskActivityDiagram.png differ diff --git a/docs/images/EditTaskReferenceDiagram.png b/docs/images/EditTaskReferenceDiagram.png new file mode 100644 index 00000000000..8f858e22f87 Binary files /dev/null and b/docs/images/EditTaskReferenceDiagram.png differ diff --git a/docs/images/EditTaskSequenceDiagram.png b/docs/images/EditTaskSequenceDiagram.png new file mode 100644 index 00000000000..fa5fb83fb2b Binary files /dev/null and b/docs/images/EditTaskSequenceDiagram.png differ diff --git a/docs/images/FilterActivityDiagram.png b/docs/images/FilterActivityDiagram.png new file mode 100644 index 00000000000..712c07b5dab Binary files /dev/null and b/docs/images/FilterActivityDiagram.png differ diff --git a/docs/images/FilterSequenceDiagram.png b/docs/images/FilterSequenceDiagram.png new file mode 100644 index 00000000000..a9ed7cce1b7 Binary files /dev/null and b/docs/images/FilterSequenceDiagram.png differ diff --git a/docs/images/FilterTasksCommandDemo.png b/docs/images/FilterTasksCommandDemo.png new file mode 100644 index 00000000000..6c601ad6d03 Binary files /dev/null and b/docs/images/FilterTasksCommandDemo.png differ diff --git a/docs/images/FindCommandImage.png b/docs/images/FindCommandImage.png new file mode 100644 index 00000000000..da0cbf16496 Binary files /dev/null and b/docs/images/FindCommandImage.png differ diff --git a/docs/images/FindTasksActivityDiagram.png b/docs/images/FindTasksActivityDiagram.png new file mode 100644 index 00000000000..a1bbbd0fbed Binary files /dev/null and b/docs/images/FindTasksActivityDiagram.png differ diff --git a/docs/images/FindTasksCommandSequenceDiagram.png b/docs/images/FindTasksCommandSequenceDiagram.png new file mode 100644 index 00000000000..67eb7597991 Binary files /dev/null and b/docs/images/FindTasksCommandSequenceDiagram.png differ diff --git a/docs/images/GUI.png b/docs/images/GUI.png new file mode 100644 index 00000000000..1240bc2db0f Binary files /dev/null and b/docs/images/GUI.png differ diff --git a/docs/images/LinkExamCommandActivityDiagram.png b/docs/images/LinkExamCommandActivityDiagram.png new file mode 100644 index 00000000000..5a7f0d2657f Binary files /dev/null and b/docs/images/LinkExamCommandActivityDiagram.png differ diff --git a/docs/images/LinkExamCommandDemo.png b/docs/images/LinkExamCommandDemo.png new file mode 100644 index 00000000000..0958f2a0ccc Binary files /dev/null and b/docs/images/LinkExamCommandDemo.png differ diff --git a/docs/images/LinkExamCommandSequenceDiagram.png b/docs/images/LinkExamCommandSequenceDiagram.png new file mode 100644 index 00000000000..d49259f366e Binary files /dev/null and b/docs/images/LinkExamCommandSequenceDiagram.png differ diff --git a/docs/images/ListExamTasksCommandDemo.png b/docs/images/ListExamTasksCommandDemo.png new file mode 100644 index 00000000000..ad61373c73f Binary files /dev/null and b/docs/images/ListExamTasksCommandDemo.png differ diff --git a/docs/images/MarkTask.png b/docs/images/MarkTask.png new file mode 100644 index 00000000000..16660a9861a Binary files /dev/null and b/docs/images/MarkTask.png differ diff --git a/docs/images/MarkTaskActivityDiagram.png b/docs/images/MarkTaskActivityDiagram.png new file mode 100644 index 00000000000..11979c64338 Binary files /dev/null and b/docs/images/MarkTaskActivityDiagram.png differ diff --git a/docs/images/MarkTaskReferenceDiagram.png b/docs/images/MarkTaskReferenceDiagram.png new file mode 100644 index 00000000000..7c7c383d81f Binary files /dev/null and b/docs/images/MarkTaskReferenceDiagram.png differ diff --git a/docs/images/MarkTaskSequenceDiagram-0.png b/docs/images/MarkTaskSequenceDiagram-0.png new file mode 100644 index 00000000000..21ff34c309e Binary files /dev/null and b/docs/images/MarkTaskSequenceDiagram-0.png differ diff --git a/docs/images/MarkTaskSequenceDiagram.png b/docs/images/MarkTaskSequenceDiagram.png new file mode 100644 index 00000000000..21ff34c309e Binary files /dev/null and b/docs/images/MarkTaskSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..58d190f0dfb 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/SortTaskCommandActivityDiagram.png b/docs/images/SortTaskCommandActivityDiagram.png new file mode 100644 index 00000000000..d00151165f8 Binary files /dev/null and b/docs/images/SortTaskCommandActivityDiagram.png differ diff --git a/docs/images/SortTaskCommandDemo.png b/docs/images/SortTaskCommandDemo.png new file mode 100644 index 00000000000..b7c6ba14b53 Binary files /dev/null and b/docs/images/SortTaskCommandDemo.png differ diff --git a/docs/images/SortTaskCommandSequenceDiagram.png b/docs/images/SortTaskCommandSequenceDiagram.png new file mode 100644 index 00000000000..b0dc37b5e3d Binary files /dev/null and b/docs/images/SortTaskCommandSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..b22ec931ea5 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..9650a19dd7a 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 785e04dbab4..c5b448beadc 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UnlinkExamActivityDiagram.png b/docs/images/UnlinkExamActivityDiagram.png new file mode 100644 index 00000000000..d9946fb0af9 Binary files /dev/null and b/docs/images/UnlinkExamActivityDiagram.png differ diff --git a/docs/images/UnlinkExamSequenceDiagram.png b/docs/images/UnlinkExamSequenceDiagram.png new file mode 100644 index 00000000000..6fd670bdc4e Binary files /dev/null and b/docs/images/UnlinkExamSequenceDiagram.png differ diff --git a/docs/images/dlimyy.png b/docs/images/dlimyy.png new file mode 100644 index 00000000000..1582afd7dc4 Binary files /dev/null and b/docs/images/dlimyy.png differ diff --git a/docs/images/phualiting.png b/docs/images/phualiting.png new file mode 100644 index 00000000000..5f1fe367105 Binary files /dev/null and b/docs/images/phualiting.png differ diff --git a/docs/images/rachelchua.png b/docs/images/rachelchua.png new file mode 100644 index 00000000000..b63fcc02ac7 Binary files /dev/null and b/docs/images/rachelchua.png differ diff --git a/docs/images/sampy147.png b/docs/images/sampy147.png new file mode 100644 index 00000000000..ca4d434322d Binary files /dev/null and b/docs/images/sampy147.png differ diff --git a/docs/images/tlx02.png b/docs/images/tlx02.png new file mode 100644 index 00000000000..bbfd771e159 Binary files /dev/null and b/docs/images/tlx02.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..c6e3a2d2f47 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,8 +3,7 @@ layout: page title: AddressBook Level-3 --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-F11-2/tp/branch/master/graph/badge.svg?token=IA3TT58GTK)](https://codecov.io/gh/AY2223S1-CS2103T-F11-2/tp) ![Ui](images/Ui.png) diff --git a/docs/team/dlimyy.md b/docs/team/dlimyy.md new file mode 100644 index 00000000000..2d813213317 --- /dev/null +++ b/docs/team/dlimyy.md @@ -0,0 +1,59 @@ +--- +layout: page +title: Douglas Lim's Project Portfolio Page +--- + +### Project: MODPRO + +MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Implemented `AddTagCommand` + * What it does: Adds the tag to a task in the task list. + * Justification: This feature allows users to add priority status or deadline to their task so + that they can track the priority and deadline to complete the task. + * Highlights: The implementation for `AddTagCommand` was slightly challenging as there were several cases + such the task already having the tag that needs to be considered. Moreover, given that the various tags added + interact with the `Task` classes when being added, some changes in `Task` methods have to be made. + + +* **New Feature**: Implemented `LinkExamCommand` which allows users to link a task in the task list to an exam in the +exam list. + +* **New Feature**: Partially implemented `AddModuleCommand` which allows users to add modules to the module list. + +* **New Feature**: Implemented the `EditTagCommand` which allows users to edit the tags linked to the task. + +* **New Feature**: Implemented the `DeleteTagCommand` which allows users to delete the tags linked to the task. + +* **New Feature**: Implemented the `SortTaskCommand` which allows users to sort the tasks stored in the task list. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=dlimyy) + + +* **Project management**: + * Managed releases `v1.3.0` and `v1.3.1` on GitHub + +* **Documentation**: + * User Guide: + * Added documentation for the features `AddModuleCommand`, `AddTagCommand`, `EditTagCommand`, `DeleteTagCommand`, + `LinkExamCommand` and `SortTaskCommand`. + * Updated the `Quick Start` portion of the UG to reflect change in application to `MODPRO`. + * Developer Guide: + * Added implementation details of `SortTaskCommand` and `LinkExamCommand` + * Added numerous use cases and user stories + * Updated class diagram for `Model` and `Storage` components + +* **Contribution to Team Tasks** + * Set up the GitHub Repo for `MODPRO` + * Added CodeCov coverage for our GitHub repo + * Enabled assertions in the build.gradle file for our team project + * Contributed to the design of UI image of our product + +* **Community**: + * Reviewed numerous PR + * Reported a total of 13 bugs for the group working on [MyInsuRec](https://github.com/AY2223S1-CS2103T-W16-4/tp) + (Examples of bugs include [1](https://github.com/dlimyy/ped/issues/13), [2](https://github.com/dlimyy/ped/issues/12), + [3](https://github.com/dlimyy/ped/issues/10)) + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/phualiting.md b/docs/team/phualiting.md new file mode 100644 index 00000000000..c7289b3922f --- /dev/null +++ b/docs/team/phualiting.md @@ -0,0 +1,60 @@ +--- +layout: page +title: Phua Li Ting's Project Portfolio Page +--- + +### Project: MODPRO + +MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to add tasks. + * What it does: Allows users to add a task to the task list + * Justification: Allows users to track their progress of tasks + * Credits: Referenced AB3 implementation for adding a person + +* **New Feature**: Added the ability to filter tasks by module, completion status and link status. + * What it does: Allows users to see tasks that fulfil the one or more filter constraints + * Justification: Easier for users to look for a specific task + +* **New Feature**: Added the ability to clear all tasks. + * What it does: Allows users to clear the whole task list + * Justification: Easier for users to start from a fresh task list without manually deleting tasks one at a time + +* **New Feature**: Added the ability to delete exams. + * What it does: Allows users to delete an exam, which unlinks all tasks currently linked to it + * Justification: Allow users to remove exams they no longer wish to track + * Credit: Referenced AB3 implementation for deleting a person + +* **New Feature**: Added the ability to unlink exams. + * What it does: Allows users to unlink an exam from a task + * Justification: Allows users to unlink an exam that has been wrongly linked + +* **New Feature**: Added the ability to view tasks of an exam. + * What it does: Allows users to see all tasks that are linked to a specific exam + * Justification: Easier for users to look at tasks of one exam + +* **New Feature**: Added the ability to clear all task, module and exam lists. + * What it does: Allows users to clear the entire address book + * Justification: Easier for users to start from a fresh address book + * Credit: Referenced AB3 implementation of clear command + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=phualiting&breakdown=true) + + +* **Documentation**: + * User Guide: + * Added documentation for all new features stated above + * Developer Guide: + * Added implementation details and diagrams for the filter tasks feature and unlink exam feature + * Added use cases, user stories and manual testing for all new features stated above + +* **Community**: + * PRs reviewed + * Reported bugs for other teams' User Guide and Developer Guide in class + * Reported bugs and suggestion for another team's product and User Guide + +* **Team Based** + * Contributed to UI design of the app diff --git a/docs/team/rachelchua.md b/docs/team/rachelchua.md new file mode 100644 index 00000000000..729adfb8192 --- /dev/null +++ b/docs/team/rachelchua.md @@ -0,0 +1,62 @@ +--- +layout: page +title: Rachel Chua's Project Portfolio Page +--- + +### Project: MODPRO + +MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: To be added soon + * Added an exam class and its following fields classes to store the exams in an exam list. + * What it does: Implemented an exam column so that user can see their exam in one column. + * Justification: Allows students to track their revision progress for the exam so that they can stay on track for their revision despite their busy schedule. + * Added the AddExamCommand class together with other exam related fields classes. + * What it does: Add an exam to the exam list with the following fields of Exam Description, Exam Date, and Module + * Justification: Allows you to add an exam in exam list to track your exams. + * Credits: Referenced AB3 implementation for add person. + * Added the EditExamCommand class + * What it does: Edit an exam in the exam list + * Justification: If the user type a wrong exam into the exam list, he or she can easily change it using our edit exam command + * Credits: Referenced AB3 implementation for edit person. + * Added the FindTasksCommand class + * What it does: Find the tasks that contain the keyword inputted partially or fully by the user + * Justification: User can now easily find the tasks by keyword inputted from the long list of tasks to do as it is difficult to find a task manually by scrolling. + * Credits: Referenced AB3 implementation for find person. + * Added the ListTasksCommand + * What it does: List all the tasks in the task list + * Justification: Allows user to go back to the main list of tasks after keying commands such as find tasks or filter tasks command. + * Credits: Referenced AB3 implementation for list persons. + * Added the FindModulesCommand class + * What it does: Find the modules that contain the keyword inputted partially or fully by the user + * Justification: User can now easily find the modules. + * Credits: Referenced AB3 implementation for find person. + * Added the ListModulesCommand + * What it does: List all the modules in the task list + * Justification: Allows user to go back to the main list of modules after keying commands such as find module command. + * Credits: Referenced AB3 implementation for list persons. +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=rachelchua) + +* **Documentation**: + * User Guide: + Wrote documentation for + * FindTasks command, + * FindModules command, + * ListTasks command, + * ListModules command, + * AddExamCommand, + * EditExamCommand + * Developer Guide: + * Implementation details for AddExamCommand + * Implementation details for FindTasksCommand + * Manual Testing for commands that I implemented. + * Use Cases, User Stories for commands that I implemented. + +* **Community**: + * PRs reviewed. + * Reported bugs and suggestions for other teams in the class when reviewing their DG, and UG, as well as in PE dry run. +* **Team Based**: + * Contributed to the UI design of the app. + diff --git a/docs/team/sampy147.md b/docs/team/sampy147.md new file mode 100644 index 00000000000..1af3bc35044 --- /dev/null +++ b/docs/team/sampy147.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Samuel Pang's Project Portfolio Page +--- + +### Project: MODPRO + +MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to delete a task / module + * What it does: allows the user to delete a task / module from the task / module list. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. It gives the user the freedom to delete any task / module that the user deems unnecessary. + * Highlights: This enhancement required the development of non-trivial new methods across different classes. It required an in depth understanding of user requirements, as certain design decisions such as deleting exams and tasks related to a module when the module was deleted were based of a user-centric approach. + * Credits: Referenced AB3 implementation for delete command. + + +* **New Feature**: Added the ability to edit a module + * What it does: allows the user to edit a module in the module list. + * Justification: This feature improves the product greatly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. It gives the user the freedom to edit any module that the user deems unnecessary. + * Highlights: This enhancement affects existing methods across different classes and required the creation of methods that required extensive understanding of the existing code base. It required complex implementation using multiple classes, as certain design decisions such as editing exams and tasks related to a module when the module was edited were non-trivial. + * Credits: Referenced AB3 implementation for edit command. + + +* **New Feature**: Augmented the ability to add a module using module name, module credit fields on top of the existing module code field. + * What it does: allows the user to add a module using these fields. + * Justification: This feature improves the product greatly because a user can have a more detailed view of the modules. It provides added personalisation for the user. + * Highlights: The addition of these fields affects existing methods across different classes and required extensive understanding of the existing code base, especially since existing methods had to now account for these fields. + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=sampy147&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + + +* **Documentation**: + * User Guide: + * Added documentation for the features `deleteTask`, `deleteModule` and `editModule` (links: [#43](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/43), [#201](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/201)). + * Developer Guide: + * Added implementation details of the `deleteTask` feature, and other details for `deleteModule` and `editModule` features (links: [#44](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/44), [#106](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/106)). + + +* **Community**: + * Numerous PRs reviewed (with non-trivial review comments). + * Clarified questions in forum regarding feature bugs ([example](https://github.com/nus-cs2103-AY2223S1/forum/issues/407)). + * Reported bugs and suggestions for other teams (examples: [1](https://github.com/Sampy147/ped/issues/1), [2](https://github.com/Sampy147/ped/issues/2), [3](https://github.com/Sampy147/ped/issues/9), [4](https://github.com/Sampy147/ped/issues/10)). + * Served as the point of contact between the Professor, TA as well as the team especially when dealing with queries. diff --git a/docs/team/tlx02.md b/docs/team/tlx02.md new file mode 100644 index 00000000000..4bd6aeae05e --- /dev/null +++ b/docs/team/tlx02.md @@ -0,0 +1,45 @@ +--- +layout: page +title: Tan Li Xin's Project Portfolio Page +--- + +### Project: MODPRO + +MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: + * Added the progress bar for modules + * What it does: Calculates the percentage of tasks linked to the module, that are completed and shows the percentage on a progress bar. The percentage bar will update itself with each command from the user input. + * Justification: This feature improves the product significantly because it is a key feature of our product as a module tracker. + * Highlights: This enhancement is closely linked to existing commands since the percentage is affected by changes such as the editing and marking of tasks. The progress bar has to update itself to reflect these changes. Hence, it required analysis to find the most appropriate design structure and the implementation was difficult as it had to integrate well with other commands. + * Added the progress bar for exams + * What it does: Calculates the percentage of tasks linked to the exam, that are completed and shows the percentage on a progress bar. The percentage bar will update itself with each command from the user input. + * Justifications: This feature allows students to track their revision progress for their exams conveniently. + * Added the MarkTaskCommand + * What it does: Marks a task as completed. This is reflected on the GUI by adding a tick to the checkbox. + * Justifications: This features allows students to focus on tasks they have not completed. It is also key to the implementation of the progress bar. + * Added the UnmarkTaskCommand + * What it does: Unmarks (labels as not completed) a task. This is reflected on the GUI by removing the tick from the checkbox. + * Added the EditTaskCommand + * What it does: Edits the details of a task with the new values provided by the user. + * Justification: This feature allows students to correct their mistakes and make changes to the module and description of their tasks. + * Credits: Referenced AB3 implementation for editing persons. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=tlx02&breakdown=true) + +* **Documentation**: + * User Guide: + * Added documentation for the Mark Task, Unmark Task, and Edit Task features + * Added the command summary + * Developer Guide: + * Added implementation details of the Mark Task and Edit Task features + * Added manual testing for Mark Task and Edit Task features + * Added user stories and use cases for the features I implemented + * **Community**: + * Reviewed PRs + * Reported bugs and suggestions for other teams in the class during the PE dry run ([1](https://github.com/tlx02/ped/issues/3), [2](https://github.com/tlx02/ped/issues/6), [3](https://github.com/tlx02/ped/issues/7)) + * **Team based**: + * Contributed to the UI design of the app + diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..7356b512aad 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -21,7 +21,6 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonAddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; @@ -78,10 +77,14 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { ReadOnlyAddressBook initialData; try { addressBookOptional = storage.readAddressBook(); + //@@author dlimyy-reused + //Reused with minor modifications from + // https://github.com/se-edu/addressbook-level3/blob/master/src/main/java/seedu/address/MainApp.java if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with an empty AddressBook"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = addressBookOptional.orElseGet(AddressBook::new); + //@@author } catch (DataConversionException e) { logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); initialData = new AddressBook(); diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..e4b9673cbb5 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -6,8 +6,33 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_FEATURE_TYPE_FORMAT = "Invalid %s command format!"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_INVALID_TASK_INDEX_TOO_LARGE = + "Please provide a task index greater than 0 and less than %d"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_MODULES_LISTED_OVERVIEW = "%1$d modules listed!"; + public static final String MESSAGE_INVALID_MODULE_INDEX = + "Please provide a positive unsigned integer less than 2147483648 for the index of a module."; + public static final String MESSAGE_INVALID_MODULE_DELETION_AS_TIED_WITH_TASK = "The module" + + " cannot be deleted as it is tied with an existing task"; + public static final String MESSAGE_INVALID_MODULE_EDIT_AS_TIED_WITH_TASK = "The module" + + " cannot be edited as it is tied with an existing task"; + public static final String MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE = + "Please provide a module index greater than 0 and less than %d"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task list"; + public static final String MESSAGE_INVALID_TASK_INDEX = + "Please provide an integer greater than 0 and less than 2147483648 for the index of a task."; + public static final String MESSAGE_MODULE_NOT_FOUND = "This module does not exist"; + public static final String MESSAGE_DUPLICATE_MODULE = "This module already exists in the module list"; + public static final String MESSAGE_DUPLICATE_EXAM = "This exam already exists in the exam list"; + public static final String MESSAGE_INVALID_EXAM_DISPLAYED_INDEX = + "The exam index provided is invalid"; + public static final String MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE = + "Please provide an exam index greater than 0 and less than %d"; + public static final String MESSAGE_INVALID_EXAM_INDEX = + "Please provide an integer greater than 0 and less than 2147483648 for the index of an exam."; } diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java index eafe4dfd681..2c5f69df335 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java @@ -26,6 +26,16 @@ public static void requireAllNonNull(Collection items) { items.forEach(Objects::requireNonNull); } + /** + * Throws NullPointerException if all of {@code items} is null. + */ + public static void requireAnyNonNull(Object... items) { + requireNonNull(items); + if (!isAnyNonNull(items)) { + throw new NullPointerException(); + } + } + /** * Returns true if {@code items} contain any elements that are non-null. */ diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..24a2307149b 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -37,6 +37,18 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { return Arrays.stream(wordsInPreppedSentence) .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if {@code sentence} contains the {@code partialWord}. + * @param sentence cannot be null + * @param partialWord cannot be null, cannot be empty + * @return true if {@code sentence} contains the {@code partialWord}, otherwise false + */ + public static boolean containsPartialWord(String sentence, String partialWord) { + requireNonNull(sentence); + requireNonNull(partialWord); + String preppedWord = partialWord.trim(); + return sentence.contains(preppedWord); + } /** * Returns a detailed message of the t, including the stack trace. diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..420c8365740 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,9 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; /** * API of the Logic component @@ -30,8 +32,14 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of modules */ + ObservableList getFilteredModuleList(); + + /** Returns an unmodifiable view of the filtered list of tasks */ + ObservableList getFilteredTaskList(); + + /** Returns an unmodifiable view of the filtered list of exams */ + ObservableList getFilteredExamList(); /** * Returns the user prefs' address book file path. @@ -47,4 +55,5 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..6778db863bc 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -14,7 +14,9 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; import seedu.address.storage.Storage; /** @@ -60,8 +62,17 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredModuleList() { + return model.getFilteredModuleList(); + } + + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + @Override + public ObservableList getFilteredExamList() { + return model.getFilteredExamList(); } @Override @@ -78,4 +89,5 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddExamCommand.java b/src/main/java/seedu/address/logic/commands/AddExamCommand.java new file mode 100644 index 00000000000..d479a7b35d1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddExamCommand.java @@ -0,0 +1,64 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EXAM; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; + +/** + * Adds an exam to the exam list. + */ +public class AddExamCommand extends Command { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + ": Adds an exam to the exam list. " + + "\n" + + "Parameters: " + + PREFIX_MODULE + "MODULE " + + PREFIX_EXAM_DESCRIPTION + "EXAMDESCRIPTION " + + PREFIX_EXAM_DATE + "EXAMDATE " + + "\n" + + "Example: e " + COMMAND_WORD + " " + + PREFIX_MODULE + "CS2103T " + + PREFIX_EXAM_DESCRIPTION + "Midterms " + + PREFIX_EXAM_DATE + "20-12-2022"; + + public static final String MESSAGE_SUCCESS = "New exam added: %1$s"; + + private final Exam toAdd; + + /** + * Creates a AddExamCommand to add the specified {@code Exam} + */ + public AddExamCommand(Exam exam) { + requireNonNull(exam); + toAdd = exam; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(toAdd.getModule())) { + throw new CommandException(MESSAGE_MODULE_NOT_FOUND); + } else if (model.hasExam(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_EXAM); + } + model.addExam(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddExamCommand // instanceof handles nulls + && toAdd.equals(((AddExamCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddModuleCommand.java b/src/main/java/seedu/address/logic/commands/AddModuleCommand.java new file mode 100644 index 00000000000..de65bfdfaef --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddModuleCommand.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.Module; + +/** + * AddModuleCommand class represents an AddModuleCommand which adds the module. + */ +public class AddModuleCommand extends Command { + public static final String COMMAND_WORD = "add"; + public static final String MESSAGE_USAGE = "m " + COMMAND_WORD + ": adds a module to the module list.\n" + + "Parameters: " + + PREFIX_MOD_CODE + "MODULE_CODE " + + PREFIX_MOD_NAME + "MODULE_NAME " + + PREFIX_MOD_CREDIT + "MODULE_CREDIT\n" + + "Example: " + "m " + COMMAND_WORD + " " + PREFIX_MOD_CODE + "CS2100 " + + PREFIX_MOD_NAME + "Computer organisation " + PREFIX_MOD_CREDIT + "4"; + public static final String MODULE_ADDED_SUCCESS = "Module has been added successfully!"; + + public static final String DUPLICATE_MODULE_DETECTED = "This module already exists! " + + "Try to input a different module code along with your initial module name and module credit fields."; + private final Module moduleAdded; + + /** + * Constructor of the AddModuleCommand class which + * helps to add a module. + * + * @param moduleAdded + */ + public AddModuleCommand(Module moduleAdded) { + requireNonNull(moduleAdded); + this.moduleAdded = moduleAdded; + + } + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasModule(moduleAdded)) { + throw new CommandException(DUPLICATE_MODULE_DETECTED); + } + model.addModule(moduleAdded); + model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + return new CommandResult(MODULE_ADDED_SUCCESS); + } + + @Override + public boolean equals(Object otherAddCommand) { + return otherAddCommand == this + || (otherAddCommand instanceof AddModuleCommand + && moduleAdded.equals(((AddModuleCommand) otherAddCommand).moduleAdded)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java new file mode 100644 index 00000000000..35f5231be24 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElse; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.tag.exceptions.BothTagsCannotBeNullException; +import seedu.address.model.task.Task; + +/** + * AddTagCommand represents a command which adds a tag to the existing task. + */ +public class AddTagCommand extends Command { + public static final String COMMAND_WORD = "tagadd"; + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": tags a task in the task list.\n" + + "Parameters: INDEX " + "[" + PREFIX_PRIORITY_STATUS + "PRIORITY_STATUS]* " + + "[" + PREFIX_DEADLINE + "DEADLINE]*\n" + + "Example: " + "t " + COMMAND_WORD + " 1" + + " " + PREFIX_PRIORITY_STATUS + "HIGH"; + + public static final String TAG_ADDED_SUCCESS = "The tag(s) has/have been added successfully"; + + public static final String PRIORITY_TAG_ALREADY_EXIST = "The priority tag already exists"; + public static final String DEADLINE_TAG_ALREADY_EXIST = "The deadline tag already exists"; + + private final PriorityTag priorityTag; + private final DeadlineTag deadlineTag; + private final Index index; + + /** + * Constructor of the AddTagCommand. Sets the Priority tag, Deadline tag and + * the index to add the tag. + * + * @param priorityTag The priority tag to be added. + * @param deadlineTag The deadline tag to be added. + * @param index The index of the tag. + */ + public AddTagCommand(PriorityTag priorityTag, DeadlineTag deadlineTag, Index index) { + requireNonNull(index); + if (priorityTag == null && deadlineTag == null) { + throw new BothTagsCannotBeNullException(); + } + this.priorityTag = priorityTag; + this.deadlineTag = deadlineTag; + this.index = index; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Task taggedTask = null; + List taskList = model.getFilteredTaskList(); + if (index.getZeroBased() >= taskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Task currentTask = taskList.get(index.getZeroBased()); + if (priorityTag != null) { + if (currentTask.hasPriorityTag()) { + throw new CommandException(PRIORITY_TAG_ALREADY_EXIST); + } + taggedTask = currentTask.setPriorityTag(priorityTag); + } + + if (deadlineTag != null) { + if (currentTask.hasDeadlineTag()) { + throw new CommandException(DEADLINE_TAG_ALREADY_EXIST); + } + taggedTask = requireNonNullElse(taggedTask, currentTask).setDeadlineTag(deadlineTag); + } + model.replaceTask(currentTask, taggedTask, true); + return new CommandResult(TAG_ADDED_SUCCESS); + } + + @Override + public boolean equals(Object otherAddTagCommand) { + return otherAddTagCommand == this + || (otherAddTagCommand instanceof AddTagCommand + && Objects.equals(priorityTag, ((AddTagCommand) otherAddTagCommand).priorityTag) + && Objects.equals(deadlineTag, ((AddTagCommand) otherAddTagCommand).deadlineTag) + && index.equals(((AddTagCommand) otherAddTagCommand).index)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTaskCommand.java b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java new file mode 100644 index 00000000000..93101945b2b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_TASK; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + + +/** + * Adds a task to the task list. + */ +public class AddTaskCommand extends Command { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": Adds a task to the task list.\n" + + "Parameters: " + + PREFIX_MODULE + "MODULE " + + PREFIX_DESCRIPTION + "DESCRIPTION\n" + + "Example: t " + COMMAND_WORD + " " + + PREFIX_MODULE + "CS2103T " + + PREFIX_DESCRIPTION + "lecture quiz"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + + private final Task toAdd; + + /** + * Creates a AddTaskCommand to add the specified {@code Task} + */ + public AddTaskCommand(Task toAdd) { + requireNonNull(toAdd); + this.toAdd = toAdd; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(toAdd.getModule())) { + throw new CommandException(MESSAGE_MODULE_NOT_FOUND); + } else if (model.hasTask(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.addTask(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTaskCommand // instanceof handles nulls + && toAdd.equals(((AddTaskCommand) other).toAdd)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearAllCommand.java b/src/main/java/seedu/address/logic/commands/ClearAllCommand.java new file mode 100644 index 00000000000..77f6b2d9941 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClearAllCommand.java @@ -0,0 +1,36 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; + +/** + * Clears task, exam and module lists. + */ +public class ClearAllCommand extends Command { + + public static final String COMMAND_WORD = "clearall"; + public static final String MESSAGE_SUCCESS = "Task, exam and module lists have been cleared!"; + public static final String MESSAGE_ALL_LISTS_ALREADY_EMPTY = "Task, exam and module lists are already empty!"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList taskList = model.getFilteredTaskList(); + ObservableList moduleList = model.getFilteredModuleList(); + ObservableList examList = model.getFilteredExamList(); + if (taskList.isEmpty() && moduleList.isEmpty() && examList.isEmpty()) { + throw new CommandException(MESSAGE_ALL_LISTS_ALREADY_EMPTY); + } + + model.setAddressBook(new AddressBook()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearTasksCommand.java b/src/main/java/seedu/address/logic/commands/ClearTasksCommand.java new file mode 100644 index 00000000000..bc5ffc3be89 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClearTasksCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; + +/** + * Clears the task list + */ +public class ClearTasksCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Task list has been cleared!"; + public static final String MESSAGE_TASK_LIST_ALREADY_EMPTY = "The task list is already empty!"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList taskList = model.getFilteredTaskList(); + if (taskList.isEmpty()) { + throw new CommandException(MESSAGE_TASK_LIST_ALREADY_EMPTY); + } + + ObservableList moduleList = model.getFilteredModuleList(); + ObservableList examList = model.getFilteredExamList(); + + AddressBook newAddressBook = new AddressBook(); + newAddressBook.setModules(moduleList); + newAddressBook.setExams(examList); + newAddressBook.resetAllTaskCount(); + + model.setAddressBook(newAddressBook); + + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteExamCommand.java b/src/main/java/seedu/address/logic/commands/DeleteExamCommand.java new file mode 100644 index 00000000000..0e2a1960d7c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteExamCommand.java @@ -0,0 +1,65 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; + +/** + * Deletes an exam identified using it's displayed index from the address book. All tasks currently linked to that exam + * will become unlinked. + */ +public class DeleteExamCommand extends Command { + + public static final String COMMAND_WORD = "del"; + + public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + + ": Deletes the exam identified by the index number used in the displayed exam list.\n" + + "Parameters: INDEX\n" + + "Example: e " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_EXAM_SUCCESS = "Deleted Exam: %1$s"; + + public static final String MESSAGE_NO_EXAM_IN_LIST = + "There is no exam in the exam list so delete exam operation cannot be done!"; + + private final Index targetIndex; + + public DeleteExamCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredExamList(); + + if (lastShownList.size() == 0) { + throw new CommandException(MESSAGE_NO_EXAM_IN_LIST); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + Exam examToDelete = lastShownList.get(targetIndex.getZeroBased()); + + model.unlinkTasksFromExam(examToDelete); + model.deleteExam(examToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_EXAM_SUCCESS, examToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteExamCommand // instanceof handles nulls + && targetIndex.equals(((DeleteExamCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteModuleCommand.java b/src/main/java/seedu/address/logic/commands/DeleteModuleCommand.java new file mode 100644 index 00000000000..d7f718de7c5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteModuleCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.Module; + +/** + * Deletes a module identified using it's displayed index from the module list. + */ +public class DeleteModuleCommand extends Command { + + public static final String COMMAND_WORD = "del"; + + public static final String MESSAGE_USAGE = "m " + COMMAND_WORD + + ": Deletes the module identified by the index number used in the displayed module list.\n" + + "Parameters: INDEX\n" + + "Example: m " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_MODULE_SUCCESS = "Deleted Module: %1$s"; + public static final String MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE = + "\nWarning! All the tasks and exams related to this module have been deleted."; + + private final Index targetIndex; + + public DeleteModuleCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredModuleList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + Module moduleToDelete = lastShownList.get(targetIndex.getZeroBased()); + + boolean areTasksOrExamsDeleted = false; + + if (model.hasTaskWithModule(moduleToDelete)) { + model.deleteTasksWithModule(moduleToDelete); + areTasksOrExamsDeleted = true; + } + if (model.hasExamWithModule(moduleToDelete)) { + model.deleteExamsWithModule(moduleToDelete); + areTasksOrExamsDeleted = true; + } + + model.deleteModule(moduleToDelete); + + if (areTasksOrExamsDeleted) { + return new CommandResult(String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete) + + MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE); + } + return new CommandResult(String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteModuleCommand // instanceof handles nulls + && targetIndex.equals(((DeleteModuleCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java new file mode 100644 index 00000000000..58dd01566ed --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java @@ -0,0 +1,81 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * DeleteTagCommand removes the tag linked to the task in the task list. + */ +public class DeleteTagCommand extends Command { + public static final String COMMAND_WORD = "tagdel"; + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": deletes tags linked to the task.\n" + + "Parameters: " + "INDEX " + PREFIX_TAG + "KEYWORD [SECOND_KEYWORD]\n" + + "Example: " + "t " + COMMAND_WORD + " 1 " + "t/" + "priority"; + public static final String NO_PRIORITY_TAG_TO_DELETE = + "There is no priority tag to delete from the task."; + public static final String NO_DEADLINE_TAG_TO_DELETE = + "There is no deadline tag to delete form the task."; + public static final String TAG_DELETED_SUCCESSFULLY = + "The tag(s) has/have been deleted from the task."; + + private final Index index; + private final Set keywords; + + /** + * The constructor of the DeleteTagCommand class. Sets the index and + * the keywords of the tags to be removed. + * + * @param index The index of the task + * @param keywords The keywords which identify the tags to be removed. + */ + public DeleteTagCommand(Index index, Set keywords) { + requireAllNonNull(index, keywords); + this.index = index; + this.keywords = keywords; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List taskList = model.getFilteredTaskList(); + if (index.getZeroBased() >= taskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Task currentTask = taskList.get(index.getZeroBased()); + Task changedTask = currentTask; + boolean hasPriorityKeyword = keywords.contains("priority"); + boolean hasDeadlineKeyword = keywords.contains("deadline"); + if (hasPriorityKeyword) { + if (!currentTask.hasPriorityTag()) { + throw new CommandException(NO_PRIORITY_TAG_TO_DELETE); + } + changedTask = changedTask.deletePriorityTag(); + } + if (hasDeadlineKeyword) { + if (!currentTask.hasDeadlineTag()) { + throw new CommandException(NO_DEADLINE_TAG_TO_DELETE); + } + changedTask = changedTask.deleteDeadlineTag(); + } + model.replaceTask(currentTask, changedTask, true); + return new CommandResult(TAG_DELETED_SUCCESSFULLY); + } + + @Override + public boolean equals(Object otherDeleteTagCommand) { + return otherDeleteTagCommand == this + || (otherDeleteTagCommand instanceof DeleteTagCommand + && keywords.equals(((DeleteTagCommand) otherDeleteTagCommand).keywords) + && index.equals(((DeleteTagCommand) otherDeleteTagCommand).index)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java new file mode 100644 index 00000000000..5ca44253036 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * Deletes a task identified using it's displayed index from the task list. + */ +public class DeleteTaskCommand extends Command { + + public static final String COMMAND_WORD = "del"; + + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + + ": Deletes the task identified by the index number used in the displayed task list.\n" + + "Parameters: INDEX\n" + + "Example: t " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s"; + public static final String MESSAGE_EXAM_LINK_DROPPED = + "\nWarning! The link between this task and its exam is dropped."; + + private final Index targetIndex; + + public DeleteTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + Task taskToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTask(taskToDelete); + + if (taskToDelete.isLinked()) { + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete) + + MESSAGE_EXAM_LINK_DROPPED); + } + + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteTaskCommand // instanceof handles nulls + && targetIndex.equals(((DeleteTaskCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditExamCommand.java b/src/main/java/seedu/address/logic/commands/EditExamCommand.java new file mode 100644 index 00000000000..0c0713af6bd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditExamCommand.java @@ -0,0 +1,192 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EXAM; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EXAMS; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.Module; + +/** + * Edits the exam with the specified index number in the displayed exam list. + */ +public class EditExamCommand extends Command { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + ": Edits the details of the exam identified " + + "by the index number used in the displayed exam list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX " + + "[" + PREFIX_MODULE + "MODULE]* " + + "[" + PREFIX_EXAM_DESCRIPTION + "EXAM DESCRIPTION]* " + + "[" + PREFIX_EXAM_DATE + "EXAM DATE]* " + "\n" + + "Example: e " + COMMAND_WORD + " 1 " + + PREFIX_MODULE + "cs2030s " + + PREFIX_EXAM_DESCRIPTION + "finals " + + PREFIX_EXAM_DATE + "20-12-2022"; + + public static final String MESSAGE_EDIT_EXAM_SUCCESS = "Successfully Edited Exam: %1$s"; + public static final String MESSAGE_EXAM_NOT_EDITED = "Please provide a module or exam description or exam date " + + "different from the exam's current module and description and exam date";; + public static final String MESSAGE_NO_FIELDS_PROVIDED = + "Please provide at least one of the fields to edit: m/MODULE, ex/EXAMDESCRIPTION, ed/EXAMDATE"; + private final Index index; + private final EditExamDescriptor editExamDescriptor; + + /** + * @param index of the exam in the filtered exam list to edit + * @param editExamDescriptor details to edit the exam with + */ + public EditExamCommand(Index index, EditExamDescriptor editExamDescriptor) { + requireAllNonNull(index, editExamDescriptor); + this.index = index; + this.editExamDescriptor = editExamDescriptor; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredExamList(); + + if (lastShownList.size() == 0) { + throw new CommandException("There is no exam in the exam list so edit operation cannot be done"); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + if (editExamDescriptor.getModule().isPresent() && !model.hasModule(editExamDescriptor.module)) { + throw new CommandException(MESSAGE_MODULE_NOT_FOUND); + } + + Exam examToEdit = lastShownList.get(index.getZeroBased()); + Exam editedExam = examToEdit.edit(editExamDescriptor); + + if (examToEdit.isSameExam(editedExam)) { + throw new CommandException(MESSAGE_EXAM_NOT_EDITED); + } + + boolean isEditedExamOfSameModule = examToEdit.getModule().isSameModule(editedExam.getModule()); + + try { + model.replaceExam(examToEdit, editedExam, false); + if (!isEditedExamOfSameModule && model.isExamLinkedToTask(examToEdit)) { + model.unlinkTasksFromExam(examToEdit); + return new CommandResult(String.format(MESSAGE_EDIT_EXAM_SUCCESS, editedExam) + "\n" + + "Warning! All the tasks previously linked to this exam are now unlinked."); + } else { + model.updateExamFieldForTask(examToEdit, editedExam); + } + } catch (DuplicateExamException e) { + throw new CommandException(MESSAGE_DUPLICATE_EXAM); + } + + model.updateFilteredExamList(PREDICATE_SHOW_ALL_EXAMS); + return new CommandResult(String.format(MESSAGE_EDIT_EXAM_SUCCESS, editedExam)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditExamCommand)) { + return false; + } + + // state check + EditExamCommand e = (EditExamCommand) other; + return index.equals(e.index) + && editExamDescriptor.equals(e.editExamDescriptor); + } + + /** + * Stores the details to edit the exam with. Each non-empty field value will replace the + * corresponding field value of the exam. + */ + public static class EditExamDescriptor { + private Module module; + private ExamDescription description; + private ExamDate examDate; + + public EditExamDescriptor() {} + + /** + * Copy constructor. + */ + public EditExamDescriptor(EditExamDescriptor toCopy) { + setExamDate(toCopy.examDate); + setDescription(toCopy.description); + setModule(toCopy.module); + } + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(module, description, examDate); + } + + public void setModule(Module module) { + this.module = module; + } + + public Optional getModule() { + return Optional.ofNullable(module); + } + + public void setDescription(ExamDescription description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setExamDate(ExamDate examDate) { + this.examDate = examDate; + } + + public Optional getExamDate() { + return Optional.ofNullable(examDate); + } + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditExamDescriptor)) { + return false; + } + + // state check + EditExamDescriptor e = (EditExamDescriptor) other; + return getModule().equals(e.getModule()) && getDescription().equals(e.getDescription()) + && getExamDate().equals(e.getExamDate()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditModuleCommand.java b/src/main/java/seedu/address/logic/commands/EditModuleCommand.java new file mode 100644 index 00000000000..7fd4d95a843 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditModuleCommand.java @@ -0,0 +1,205 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; +import seedu.address.model.module.exceptions.DuplicateModuleException; + +/** + * Edits the task with the specified index number in the displayed task list. + */ +public class EditModuleCommand extends Command { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = "m " + COMMAND_WORD + + ": Edits the module code, module name and module credit of the module identified " + + "by the index number used in the displayed module list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX " + + "[" + PREFIX_MOD_CODE + "MODULE CODE]* " + + "[" + PREFIX_MOD_NAME + "MODULE NAME]* " + + "[" + PREFIX_MOD_CREDIT + "MODULE CREDIT]*\n" + + "Example: m " + COMMAND_WORD + " 1 " + + PREFIX_MOD_CODE + "cs2040 " + + PREFIX_MOD_NAME + "Data Structures and Algorithms " + + PREFIX_MOD_CREDIT + "4"; + + public static final String MESSAGE_EDIT_MODULE_SUCCESS = "Edited Module: %1$s, Edited Name: %2$s, " + + "Edited Credit: %3$s"; + public static final String MESSAGE_MODULE_NOT_EDITED = "The provided fields are the same as the current module"; + public static final String MESSAGE_NO_FIELDS_PROVIDED = + String.format("Please provide at least one of the fields to edit: %1$sMODULE CODE, " + + "%2$sMODULE NAME, %3$sMODULE CREDIT", PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT); + public static final String MESSAGE_TASKS_EXAMS_RELATED_MODIFIED = "\n" + + "Warning! All the tasks and exams related to the initial module " + + "now have this edited Module as their new module."; + + private final Index index; + private final EditModuleDescriptor editModuleDescriptor; + + /** + * @param index of the task in the filtered module list to edit + * @param editModuleDescriptor details to edit the module with + */ + public EditModuleCommand(Index index, EditModuleDescriptor editModuleDescriptor) { + requireAllNonNull(index, editModuleDescriptor); + + this.index = index; + this.editModuleDescriptor = editModuleDescriptor; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredModuleList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + Module moduleToEdit = lastShownList.get(index.getZeroBased()); + Module editedModule = moduleToEdit.edit(editModuleDescriptor); + + if (moduleToEdit.hasAllSameFields(editedModule)) { + throw new CommandException(MESSAGE_MODULE_NOT_EDITED); + } + + try { + model.replaceModule(moduleToEdit, editedModule); + + boolean isModuleFieldUpdated = false; + if (!moduleToEdit.isSameModule(editedModule)) { + if (model.hasTaskWithModule(moduleToEdit)) { + model.updateModuleFieldForTask(moduleToEdit, editedModule); + isModuleFieldUpdated = true; + } + if (model.hasExamWithModule(moduleToEdit)) { + model.updateModuleFieldForExam(moduleToEdit, editedModule); + isModuleFieldUpdated = true; + } + } + + if (isModuleFieldUpdated) { + return new CommandResult(String.format(MESSAGE_EDIT_MODULE_SUCCESS, editedModule, + editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED); + } + + } catch (DuplicateModuleException e) { + throw new CommandException(Messages.MESSAGE_DUPLICATE_MODULE); + } + + model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + return new CommandResult(String.format(MESSAGE_EDIT_MODULE_SUCCESS, editedModule, + editedModule.getModuleName(), editedModule.getModuleCredit())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditModuleCommand)) { + return false; + } + + // state check + EditModuleCommand e = (EditModuleCommand) other; + return index.equals(e.index) + && editModuleDescriptor.equals(e.editModuleDescriptor); + } + + /** + * Stores the module code, module name and module credit to edit the target module with. + * Each non-empty field value will replace the corresponding field value of the target module. + */ + public static class EditModuleDescriptor { + private ModuleCode moduleCode; + private ModuleName moduleName; + private ModuleCredit moduleCredit; + + public EditModuleDescriptor() {} + + /** + * Copy constructor. + */ + public EditModuleDescriptor(EditModuleCommand.EditModuleDescriptor toCopy) { + setModuleCode(toCopy.moduleCode); + setModuleName(toCopy.moduleName); + setModuleCredit(toCopy.moduleCredit); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(moduleCode, moduleName, moduleCredit); + } + + public void setModuleCode(ModuleCode moduleCode) { + this.moduleCode = moduleCode; + } + + public void setModuleName(ModuleName moduleName) { + this.moduleName = moduleName; + } + + public void setModuleCredit(ModuleCredit moduleCredit) { + this.moduleCredit = moduleCredit; + } + + public Optional getModuleCode() { + return Optional.ofNullable(moduleCode); + } + + public Optional getModuleName() { + return Optional.ofNullable(moduleName); + } + + public Optional getModuleCredit() { + return Optional.ofNullable(moduleCredit); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditModuleDescriptor)) { + return false; + } + + // state check + EditModuleDescriptor e = (EditModuleDescriptor) other; + + return getModuleCode().equals(e.getModuleCode()) + && getModuleName().equals(e.getModuleName()) + && getModuleCredit().equals(e.getModuleCredit()); + } + } +} + diff --git a/src/main/java/seedu/address/logic/commands/EditTagCommand.java b/src/main/java/seedu/address/logic/commands/EditTagCommand.java new file mode 100644 index 00000000000..8934fa27691 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditTagCommand.java @@ -0,0 +1,113 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.tag.exceptions.BothTagsCannotBeNullException; +import seedu.address.model.task.Task; + +/** + * EditTagCommand represents the edit tag command which edits the priority status of priority + * tag or the deadline of the deadline tag. + */ +public class EditTagCommand extends Command { + public static final String COMMAND_WORD = "tagedit"; + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": edits the tags of a task " + + "in the task list.\n" + + "Parameters: INDEX " + "[" + PREFIX_PRIORITY_STATUS + "PRIORITY_STATUS]* " + + "[" + PREFIX_DEADLINE + "DEADLINE]*\n" + + "Example: " + "t " + COMMAND_WORD + " 1 " + PREFIX_PRIORITY_STATUS + "HIGH"; + public static final String PRIORITY_TAG_DOES_NOT_EXIST = "The task does not have a priority tag."; + public static final String DEADLINE_TAG_DOES_NOT_EXIST = "The task does not have a deadline tag."; + public static final String PRIORITY_TAG_UNCHANGED = "The priority status provided is " + + "the same as the current priority status for the task."; + public static final String DEADLINE_TAG_UNCHANGED = "The deadline provided is" + + " the same as the current deadline for the task."; + public static final String TAG_EDITED_SUCCESSFULLY = "The tag(s) has/have been edited successfully."; + + private final Index index; + private final PriorityTag priorityTag; + private final DeadlineTag deadlineTag; + + /** + * The constructor of the EditTagCommand. Sets the index, the priority tag + * and the deadline tag. + * + * @param index The index of the task in the task list. + * @param priorityTag The priority status of the priority tag. + * @param deadlineTag The deadline of the deadline tag,. + */ + public EditTagCommand(Index index, PriorityTag priorityTag, + DeadlineTag deadlineTag) { + requireNonNull(index); + if (priorityTag == null && deadlineTag == null) { + throw new BothTagsCannotBeNullException(); + } + this.index = index; + this.deadlineTag = deadlineTag; + this.priorityTag = priorityTag; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List tasks = model.getFilteredTaskList(); + if (index.getZeroBased() >= tasks.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Task currTask = tasks.get(index.getZeroBased()); + checkTags(currTask); + Task taggedTask = currTask; + if (priorityTag != null) { + taggedTask = taggedTask.replacePriorityTag(priorityTag); + } + if (deadlineTag != null) { + taggedTask = taggedTask.replaceDeadlineTag(deadlineTag); + } + model.replaceTask(currTask, taggedTask, true); + return new CommandResult(TAG_EDITED_SUCCESSFULLY); + } + + private void checkTags(Task task) throws CommandException { + boolean hasPriorityTag = task.hasPriorityTag(); + boolean hasDeadlineTag = task.hasDeadlineTag(); + PriorityTag currTaskPriorityTag = task.getPriorityTag(); + DeadlineTag currTaskDeadlineTag = task.getDeadlineTag(); + if (!hasPriorityTag && priorityTag != null) { + throw new CommandException(PRIORITY_TAG_DOES_NOT_EXIST); + } + if (!hasDeadlineTag && deadlineTag != null) { + throw new CommandException(DEADLINE_TAG_DOES_NOT_EXIST); + } + if (currTaskPriorityTag != null + && priorityTag != null + && currTaskPriorityTag.compareTo(priorityTag) == 0) { + throw new CommandException(PRIORITY_TAG_UNCHANGED); + } + if (currTaskDeadlineTag != null + && deadlineTag != null + && currTaskDeadlineTag.compareTo(deadlineTag) == 0) { + throw new CommandException(DEADLINE_TAG_UNCHANGED); + } + } + + @Override + public boolean equals(Object otherEditTagCommand) { + return otherEditTagCommand == this + || (otherEditTagCommand instanceof EditTagCommand + && Objects.equals(priorityTag, ((EditTagCommand) otherEditTagCommand).priorityTag) + && Objects.equals(deadlineTag, ((EditTagCommand) otherEditTagCommand).deadlineTag) + && index.equals(((EditTagCommand) otherEditTagCommand).index)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditTaskCommand.java b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java new file mode 100644 index 00000000000..29c39172e13 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java @@ -0,0 +1,186 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.exceptions.DuplicateTaskException; + +/** + * Edits the task with the specified index number in the displayed task list. + */ +public class EditTaskCommand extends Command { + + public static final String COMMAND_WORD = "edit"; + public static final String FULL_COMMAND_WORD = "t edit"; + + public static final String MESSAGE_USAGE = FULL_COMMAND_WORD + ": Edits the details of the task at the specified " + + "INDEX in the displayed task list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX " + + "[" + PREFIX_MODULE + "MODULE]* " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]*\n" + + "Example: " + FULL_COMMAND_WORD + " 1 " + + PREFIX_MODULE + "cs2040 " + + PREFIX_DESCRIPTION + "task 3"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Successfully Edited Task: %1$s"; + public static final String MESSAGE_NO_FIELDS_PROVIDED = + "Please provide at least one of the fields to edit: m/MODULE, d/DESCRIPTION"; + public static final String MESSAGE_SAME_FIELDS_PROVIDED = + "Please provide a m/MODULE or d/DESCRIPTION different from the task's current module and description"; + public static final String MESSAGE_EXAM_UNLINKED = "Warning: The task has been unlinked from its exam.\n"; + public static final String MESSAGE_DUPLICATE_TASK = + "The edited task is the same as another task in the task list"; + public static final String MESSAGE_NO_TASK_IN_THE_LIST = + "There is no task in the task list so edit operation cannot be done!"; + + private final Index index; + private final EditTaskDescriptor editTaskDescriptor; + + /** + * @param index of the task in the filtered task list to edit + * @param editTaskDescriptor details to edit the task with + */ + public EditTaskCommand(Index index, EditTaskDescriptor editTaskDescriptor) { + requireAllNonNull(index, editTaskDescriptor); + + this.index = index; + this.editTaskDescriptor = editTaskDescriptor; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() == 0) { + throw new CommandException(MESSAGE_NO_TASK_IN_THE_LIST); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + if (editTaskDescriptor.getModule().isPresent() && !model.hasModule(editTaskDescriptor.module)) { + throw new CommandException(MESSAGE_MODULE_NOT_FOUND); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + Task editedTask = taskToEdit.edit(editTaskDescriptor); + + if (taskToEdit.isSameTask(editedTask)) { + throw new CommandException(MESSAGE_SAME_FIELDS_PROVIDED); + } + + try { + model.replaceTask(taskToEdit, editedTask, false); + } catch (DuplicateTaskException e) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + if (!taskToEdit.getModule().isSameModule(editedTask.getModule()) + && taskToEdit.isLinked()) { + return new CommandResult( + MESSAGE_EXAM_UNLINKED + String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + } + return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskCommand)) { + return false; + } + + // state check + EditTaskCommand e = (EditTaskCommand) other; + return index.equals(e.index) + && editTaskDescriptor.equals(e.editTaskDescriptor); + } + + /** + * Stores the details to edit the task with. Each non-empty field value will replace the + * corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private Module module; + private TaskDescription description; + + public EditTaskDescriptor() {} + + /** + * Copy constructor. + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setModule(toCopy.module); + setDescription(toCopy.description); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(module, description); + } + + public void setModule(Module module) { + requireNonNull(module); + this.module = module; + } + + public Optional getModule() { + return Optional.ofNullable(module); + } + + public void setDescription(TaskDescription description) { + requireNonNull(description); + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskDescriptor)) { + return false; + } + + // state check + EditTaskDescriptor e = (EditTaskDescriptor) other; + + return getModule().equals(e.getModule()) + && getDescription().equals(e.getDescription()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterTasksCommand.java b/src/main/java/seedu/address/logic/commands/FilterTasksCommand.java new file mode 100644 index 00000000000..20fb38115b3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterTasksCommand.java @@ -0,0 +1,69 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_COMPLETE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_LINKED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.FilterPredicate; + +/** + * Filters the task list by module, completion status and link status. + */ +public class FilterTasksCommand extends Command { + + public static final String COMMAND_WORD = "filter"; + + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + + ": Filters the task list by module and/or completion status and/or link status.\n" + + "Parameters: " + + "[" + PREFIX_MODULE + "MODULE]* " + + "[" + PREFIX_IS_COMPLETE + "COMPLETED(y or n)]* " + + "[" + PREFIX_IS_LINKED + "LINKED(y or n)]*\n" + + "Example: t " + COMMAND_WORD + " " + + PREFIX_MODULE + "CS2103T " + + PREFIX_IS_COMPLETE + "y " + + PREFIX_IS_LINKED + "n"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks with following constraints:%1$s"; + + public static final String MESSAGE_NO_RESULTS = "No tasks found with following constraints:%1$s"; + + private final FilterPredicate predicate; + + /** + * Constructor of the FilterTaskCommand class which filters the task list by the given constraints. + * + * @param predicate conditions to check for each task + */ + public FilterTasksCommand(FilterPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (predicate.hasModuleToCheck() && !model.hasModule(predicate.getModuleToCheck())) { + throw new CommandException(MESSAGE_MODULE_NOT_FOUND); + } + + model.updateFilteredTaskList(predicate); + + if (model.getFilteredTaskList().isEmpty()) { + return new CommandResult(String.format(MESSAGE_NO_RESULTS, predicate)); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, predicate)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterTasksCommand // instanceof handles nulls + && predicate.equals(((FilterTasksCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindModulesCommand.java b/src/main/java/seedu/address/logic/commands/FindModulesCommand.java new file mode 100644 index 00000000000..a2dd08dca4d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindModulesCommand.java @@ -0,0 +1,48 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; + + +/** + * Finds and lists all modules in the module list whose module code contains any of the argument keywords. + * Keyword matching is case insensitive. + */ + +public class FindModulesCommand extends Command { + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = "m " + COMMAND_WORD + ": Finds all modules whose module code" + + " partially or fully contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD \n" + + "Example: m " + COMMAND_WORD + " cs2030s"; + + private final ModuleCodeContainsKeywordsPredicate predicate; + + public FindModulesCommand(ModuleCodeContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + model.updateFilteredModuleList(predicate); + + return new CommandResult( + String.format(Messages.MESSAGE_MODULES_LISTED_OVERVIEW, model.getFilteredModuleList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindModulesCommand // instanceof handles nulls + && predicate.equals(((FindModulesCommand) other).predicate)); // state check + } +} + + diff --git a/src/main/java/seedu/address/logic/commands/FindTasksCommand.java b/src/main/java/seedu/address/logic/commands/FindTasksCommand.java new file mode 100644 index 00000000000..a17a7fcff38 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindTasksCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; + +/** + * Finds and lists all tasks in the task list whose description contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindTasksCommand extends Command { + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": Finds all tasks whose task description " + + "contain partially or fully any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD \n" + + "Example: t " + COMMAND_WORD + " watch lecture rec"; + + private final DescriptionContainsKeywordsPredicate predicate; + + public FindTasksCommand(DescriptionContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, model.getFilteredTaskList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTasksCommand // instanceof handles nulls + && predicate.equals(((FindTasksCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/LinkExamCommand.java b/src/main/java/seedu/address/logic/commands/LinkExamCommand.java new file mode 100644 index 00000000000..dc2acb48495 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LinkExamCommand.java @@ -0,0 +1,78 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; +import seedu.address.model.task.Task; + +/** + * LinkExamCommand represents a command which links the exam to the task. + */ +public class LinkExamCommand extends Command { + public static final String COMMAND_WORD = "link"; + public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + ": links the exam to the task.\n" + + "Parameters: " + PREFIX_EXAM_INDEX + "EXAM_INDEX" + " " + + PREFIX_TASK_INDEX + "TASK_INDEX\n" + + "Example: " + "e " + COMMAND_WORD + " t/1 e/1"; + public static final String EXAM_LINKED_SUCCESS = "The exam has been successfully linked to the task!"; + public static final String TASK_ALREADY_LINKED = "This task is already linked to an exam"; + public static final String DIFFERENT_MODULE_CODE = "This task has a different module code from the exam."; + private final Index examIndex; + private final Index taskIndex; + + /** + * The constructor of the LinkExamCommand class. Sets + * the index in the exam list and the index in the task list. + * + * @param examIndex The index in the exam list. + * @param taskIndex The index in the task list. + */ + public LinkExamCommand(Index examIndex, Index taskIndex) { + requireAllNonNull(examIndex, taskIndex); + this.examIndex = examIndex; + this.taskIndex = taskIndex; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List tasks = model.getFilteredTaskList(); + List exams = model.getFilteredExamList(); + if (taskIndex.getZeroBased() >= tasks.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + if (examIndex.getZeroBased() >= exams.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EXAM_DISPLAYED_INDEX); + } + Task task = tasks.get(taskIndex.getZeroBased()); + if (task.isLinked()) { + throw new CommandException(TASK_ALREADY_LINKED); + } + Exam exam = exams.get(examIndex.getZeroBased()); + if (!task.getModule().isSameModule(exam.getModule())) { + throw new CommandException(DIFFERENT_MODULE_CODE); + } + Task linkedTask = task.linkTask(exam); + model.replaceTask(task, linkedTask, true); + return new CommandResult(EXAM_LINKED_SUCCESS); + } + + @Override + + public boolean equals(Object otherLinkExamCommand) { + return otherLinkExamCommand == this + || (otherLinkExamCommand instanceof LinkExamCommand + && taskIndex.equals(((LinkExamCommand) otherLinkExamCommand).taskIndex)) + && examIndex.equals(((LinkExamCommand) otherLinkExamCommand).examIndex); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListExamTasksCommand.java b/src/main/java/seedu/address/logic/commands/ListExamTasksCommand.java new file mode 100644 index 00000000000..ffd4b56fb1f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListExamTasksCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.exam.Exam; +import seedu.address.model.task.TaskLinkedToExamPredicate; + +/** + * Shoes all tasks linked to a specific exam. + */ +public class ListExamTasksCommand extends Command { + + public static final String COMMAND_WORD = "showt"; + + public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + + ": Show all tasks linked to a specific exam identified by the index number used in the exam list.\n" + + "Parameter: INDEX\n" + + "Example: e " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks from exam: %1$s"; + + public static final String MESSAGE_NO_RESULTS = "No tasks found from exam: %1$s"; + + public static final String MESSAGE_NO_EXAM_IN_LIST = + "There is no exam in the exam list so showt operation cannot be done!"; + + private final Index examIndex; + + /** + * Constructor of the ListExamTasksCommand class which shows all tasks linked to a specific exam. + * + * @param examIndex Index of exam to be checked if tasks are linked with. + */ + public ListExamTasksCommand(Index examIndex) { + this.examIndex = examIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownExamList = model.getFilteredExamList(); + + if (lastShownExamList.size() == 0) { + throw new CommandException(MESSAGE_NO_EXAM_IN_LIST); + } + + if (examIndex.getZeroBased() >= lastShownExamList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, lastShownExamList.size() + 1)); + } + + Exam examToCheck = lastShownExamList.get(examIndex.getZeroBased()); + + TaskLinkedToExamPredicate predicate = new TaskLinkedToExamPredicate(examToCheck); + + model.updateFilteredTaskList(predicate); + + if (model.getFilteredTaskList().isEmpty()) { + return new CommandResult(String.format(MESSAGE_NO_RESULTS, examToCheck)); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, examToCheck)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListExamTasksCommand // instanceof handles nulls + && examIndex.equals(((ListExamTasksCommand) other).examIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListModulesCommand.java b/src/main/java/seedu/address/logic/commands/ListModulesCommand.java new file mode 100644 index 00000000000..dc034d95b10 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListModulesCommand.java @@ -0,0 +1,29 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES; + +import seedu.address.model.Model; + +/** + * Lists all modules in the module list to the user. + */ +public class ListModulesCommand extends Command { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all modules"; + + public static final String EMPTY_LIST = "No modules in list"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + if (model.getFilteredModuleList().size() == 0) { + return new CommandResult(EMPTY_LIST); + } + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListTasksCommand.java b/src/main/java/seedu/address/logic/commands/ListTasksCommand.java new file mode 100644 index 00000000000..27545db88fe --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTasksCommand.java @@ -0,0 +1,29 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import seedu.address.model.Model; + +/** + * Lists all tasks in the task list to the user. + */ +public class ListTasksCommand extends Command { + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + + public static final String EMPTY_LIST = "No tasks in list"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + if (model.getFilteredTaskList().size() == 0) { + return new CommandResult(EMPTY_LIST); + } + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/MarkCommand.java b/src/main/java/seedu/address/logic/commands/MarkCommand.java new file mode 100644 index 00000000000..3416bb258b7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MarkCommand.java @@ -0,0 +1,68 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * Mark a task identified using it's displayed index from the task list as complete. + */ +public class MarkCommand extends Command { + + public static final String COMMAND_WORD = "mark"; + public static final String FULL_COMMAND_WORD = "t mark"; + + public static final String MESSAGE_USAGE = FULL_COMMAND_WORD + + ": Indicates the task at the specified INDEX in the displayed task list is not completed.\n" + + "Parameters: INDEX\n" + + "Example: " + FULL_COMMAND_WORD + " 1"; + + public static final String MESSAGE_MARK_TASK_SUCCESS = "Successfully Marked Task: %1$s"; + public static final String MESSAGE_TASK_ALREADY_MARKED = "The task is already marked!"; + public static final String MESSAGE_NO_TASK_IN_THE_LIST = + "There is no task in the task list so mark operation cannot be done!"; + + private final Index targetIndex; + + public MarkCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() == 0) { + throw new CommandException(MESSAGE_NO_TASK_IN_THE_LIST); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + Task taskToMark = lastShownList.get(targetIndex.getZeroBased()); + if (taskToMark.isComplete()) { + throw new CommandException(MESSAGE_TASK_ALREADY_MARKED); + } + Task markedTask = taskToMark.mark(); + + model.replaceTask(taskToMark, markedTask, true); + + return new CommandResult(String.format(MESSAGE_MARK_TASK_SUCCESS, taskToMark)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MarkCommand // instanceof handles nulls + && targetIndex.equals(((MarkCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortTaskCommand.java b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java new file mode 100644 index 00000000000..a64215642c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java @@ -0,0 +1,51 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CRITERIA; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Criteria; +import seedu.address.model.Model; + +/** + * SortTaskCommand represents a command which sorts the tasks in the task list. + */ +public class SortTaskCommand extends Command { + public static final String COMMAND_WORD = "sort"; + public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": sorts the task list.\n" + + "Parameters: " + PREFIX_CRITERIA + "CRITERIA\n" + + "Example: " + "t " + COMMAND_WORD + " " + PREFIX_CRITERIA + "priority"; + public static final String TASK_SORTED_SUCCESSFULLY = + "Task list has been successfully sorted"; + public static final String NO_TASK_TO_SORT = + "There are no tasks in the task list to sort!"; + private final Criteria criteria; + + /** + * The constructor of the SortTaskCommand. Sets the criteria + * that is used for sorting. + * + * @param criteria The criteria used for sorting. + */ + public SortTaskCommand(Criteria criteria) { + requireNonNull(criteria); + this.criteria = criteria; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.sortTaskList(criteria); + if (model.getFilteredTaskList().size() == 0) { + throw new CommandException(NO_TASK_TO_SORT); + } + return new CommandResult(TASK_SORTED_SUCCESSFULLY); + } + + @Override + public boolean equals(Object otherSortTaskCommand) { + return otherSortTaskCommand == this + || (otherSortTaskCommand instanceof SortTaskCommand + && criteria.equals(((SortTaskCommand) otherSortTaskCommand).criteria)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnlinkExamCommand.java b/src/main/java/seedu/address/logic/commands/UnlinkExamCommand.java new file mode 100644 index 00000000000..3cb87584e91 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnlinkExamCommand.java @@ -0,0 +1,68 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * UnlinkExamCommand represents a command which unlinks the exam from the task. + */ +public class UnlinkExamCommand extends Command { + public static final String COMMAND_WORD = "unlink"; + public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + + ": Unlinks the task identified by the index number used in the displayed task list.\n" + + "Parameters: INDEX\n" + + "Example: e " + COMMAND_WORD + " 1"; + public static final String MESSAGE_EXAM_UNLINKED_SUCCESS = "The exam has been successfully unlinked from the task!"; + public static final String MESSAGE_TASK_ALREADY_UNLINKED = "The task is already not linked to any exam."; + public static final String MESSAGE_NO_TASK_IN_LIST = + "There is no task in the task list so unlink operation cannot be done!"; + private final Index taskIndex; + + /** + * The constructor of the UnlinkExamCommand class. + * + * @param taskIndex The index of the task to be unlinked. + */ + public UnlinkExamCommand(Index taskIndex) { + requireNonNull(taskIndex); + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List tasks = model.getFilteredTaskList(); + + if (tasks.size() == 0) { + throw new CommandException(MESSAGE_NO_TASK_IN_LIST); + } + + if (taskIndex.getZeroBased() >= tasks.size()) { + throw new CommandException(String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, tasks.size() + 1)); + } + + Task toUnlink = tasks.get(taskIndex.getZeroBased()); + + if (!toUnlink.isLinked()) { + throw new CommandException(MESSAGE_TASK_ALREADY_UNLINKED); + } + + Task unlinkedTask = toUnlink.unlinkTask(); + model.replaceTask(toUnlink, unlinkedTask, true); + return new CommandResult(MESSAGE_EXAM_UNLINKED_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnlinkExamCommand // instanceof handles nulls + && taskIndex.equals(((UnlinkExamCommand) other).taskIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnmarkCommand.java b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java new file mode 100644 index 00000000000..83ba7626998 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java @@ -0,0 +1,68 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * Unmark (label as incomplete) a task identified using it's displayed index from the task list. + */ +public class UnmarkCommand extends Command { + + public static final String COMMAND_WORD = "unmark"; + public static final String FULL_COMMAND_WORD = "t unmark"; + + public static final String MESSAGE_USAGE = FULL_COMMAND_WORD + + ": Indicates the task at the specified INDEX in the displayed task list is completed.\n" + + "Parameters: INDEX\n" + + "Example: " + FULL_COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNMARK_TASK_SUCCESS = "Successfully Unmarked Task: %1$s"; + public static final String MESSAGE_TASK_ALREADY_UNMARKED = "The task is already unmarked!"; + public static final String MESSAGE_NO_TASK_IN_THE_LIST = + "There is no task in the task list so unmark operation cannot be done!"; + + private final Index targetIndex; + + public UnmarkCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() == 0) { + throw new CommandException(MESSAGE_NO_TASK_IN_THE_LIST); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1)); + } + + Task taskToUnmark = lastShownList.get(targetIndex.getZeroBased()); + if (!taskToUnmark.isComplete()) { + throw new CommandException(MESSAGE_TASK_ALREADY_UNMARKED); + } + Task unmarkedTask = taskToUnmark.unmark(); + + model.replaceTask(taskToUnmark, unmarkedTask, true); + + return new CommandResult(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnmarkCommand // instanceof handles nulls + && targetIndex.equals(((UnmarkCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddExamCommandParser.java b/src/main/java/seedu/address/logic/parser/AddExamCommandParser.java new file mode 100644 index 00000000000..3703d4d6c56 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddExamCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddExamCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.Module; + +/** + * Parses arguments to create a new AddExamCommand Object. + */ +public class AddExamCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddExamCommand + * and returns an AddExamCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddExamCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_EXAM_DESCRIPTION, PREFIX_EXAM_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE, PREFIX_EXAM_DESCRIPTION, PREFIX_EXAM_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddExamCommand.MESSAGE_USAGE)); + } + + Module module = new Module(ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get())); + ExamDescription description = ParserUtil.parseExamDescription( + argMultimap.getValue(PREFIX_EXAM_DESCRIPTION).get()); + ExamDate examDate = ParserUtil.parseExamDate(argMultimap.getValue(PREFIX_EXAM_DATE).get()); + Exam exam = new Exam(module, description, examDate); + + return new AddExamCommand(exam); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/AddModuleCommandParser.java b/src/main/java/seedu/address/logic/parser/AddModuleCommandParser.java new file mode 100644 index 00000000000..7bdb7e5b13a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddModuleCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddModuleCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; + +/** + * Parses arguments to create a new AddModuleCommand Object. + */ +public class AddModuleCommandParser implements Parser { + @Override + public Command parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT); + + if (!arePrefixesPresent(argMultimap, PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddModuleCommand.MESSAGE_USAGE)); + } + + ModuleCode modCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MOD_CODE) + .orElseThrow(NullPointerException::new)); + + ModuleName modName = ParserUtil.parseModuleName(argMultimap.getValue(PREFIX_MOD_NAME) + .orElseThrow(NullPointerException::new)); + + ModuleCredit modCredit = ParserUtil.parseModuleCredit(argMultimap.getValue(PREFIX_MOD_CREDIT) + .orElseThrow(NullPointerException::new)); + + Module module = new Module(modCode, modName, modCredit); + return new AddModuleCommand(module); + } + + //@@author dlimyy-reused + //Reused from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + //@@author + +} diff --git a/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java new file mode 100644 index 00000000000..f3ac75d4510 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java @@ -0,0 +1,56 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; + +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; + +/** + * AddTagCommandParser parses the arguments and supplies these arguments + * to the AddTagCommand. + */ +public class AddTagCommandParser implements Parser { + private final Logger logger = LogsCenter.getLogger(AddTagCommand.class); + @Override + public AddTagCommand parse(String args) throws ParseException { + logger.info("Parsing arguments for AddTagCommand"); + requireNonNull(args); + String[] indexWithTags = args.strip().split("\\s", 2); + if (indexWithTags.length != 2 || !indexWithTags[0].matches("(-|\\+)?\\d+(\\.\\d+)?")) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + AddTagCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(indexWithTags[0]); + String tags = " " + indexWithTags[1]; + ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(tags, PREFIX_PRIORITY_STATUS, PREFIX_DEADLINE); + if (!areAnyPrefixesPresent(argMultiMap, PREFIX_PRIORITY_STATUS, PREFIX_DEADLINE) + || !argMultiMap.getPreamble().isEmpty()) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + AddTagCommand.MESSAGE_USAGE)); + } + String priorityStatus = argMultiMap.getValue(PREFIX_PRIORITY_STATUS).orElse(null); + String deadline = argMultiMap.getValue(PREFIX_DEADLINE).orElse(null); + PriorityTag priorityTag = priorityStatus != null ? ParserUtil + .parsePriorityTag(priorityStatus) : null; + DeadlineTag deadlineTag = deadline != null ? ParserUtil + .parseDeadlineTag(deadline) : null; + return new AddTagCommand(priorityTag, deadlineTag, index); + } + + //@@author dlimyy-reused + //Reused from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) + private static boolean areAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + //@@author +} diff --git a/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java new file mode 100644 index 00000000000..5234708e959 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; + +/** + * Parses arguments to create a new AddTaskCommand Object. + */ +public class AddTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddTaskCommand + * and returns an AddTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_DESCRIPTION); + + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE, PREFIX_DESCRIPTION) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + + ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get()); + Module module = new Module(moduleCode); + TaskDescription description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + + return new AddTaskCommand(new Task(module, description)); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..9e57ebf39bd 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -1,22 +1,44 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FEATURE_TYPE_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; import java.util.regex.Matcher; import java.util.regex.Pattern; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.AddExamCommand; +import seedu.address.logic.commands.AddModuleCommand; +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.logic.commands.ClearAllCommand; +import seedu.address.logic.commands.ClearTasksCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.DeleteExamCommand; +import seedu.address.logic.commands.DeleteModuleCommand; +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.commands.DeleteTaskCommand; +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.logic.commands.EditTagCommand; +import seedu.address.logic.commands.EditTaskCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FilterTasksCommand; +import seedu.address.logic.commands.FindModulesCommand; +import seedu.address.logic.commands.FindTasksCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LinkExamCommand; +import seedu.address.logic.commands.ListExamTasksCommand; +import seedu.address.logic.commands.ListModulesCommand; +import seedu.address.logic.commands.ListTasksCommand; +import seedu.address.logic.commands.MarkCommand; +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.logic.commands.UnlinkExamCommand; +import seedu.address.logic.commands.UnmarkCommand; import seedu.address.logic.parser.exceptions.ParseException; + + /** * Parses user input. */ @@ -25,8 +47,11 @@ public class AddressBookParser { /** * Used for initial separation of command word and args. */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - + public static final String EXAM_FEATURE_TYPE = "e"; + public static final String MODULE_FEATURE_TYPE = "m"; + public static final String TASK_FEATURE_TYPE = "t"; + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)" + + "(?\\s*\\S*)(?.*)"); /** * Parses user input into command for execution. * @@ -40,34 +65,80 @@ public Command parseCommand(String userInput) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); } - final String commandWord = matcher.group("commandWord"); + final String featureType = matcher.group("featureType"); + final String commandWord = matcher.group("commandWord").strip(); final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); + switch (featureType.toLowerCase()) { + case TASK_FEATURE_TYPE: + switch (commandWord.toLowerCase()) { + case AddTaskCommand.COMMAND_WORD: + return new AddTaskCommandParser().parse(arguments); + case DeleteTaskCommand.COMMAND_WORD: + return new DeleteTaskCommandParser().parse(arguments); + case EditTaskCommand.COMMAND_WORD: + return new EditTaskCommandParser().parse(arguments); + case MarkCommand.COMMAND_WORD: + return new MarkCommandParser().parse(arguments); + case UnmarkCommand.COMMAND_WORD: + return new UnmarkCommandParser().parse(arguments); + case ListTasksCommand.COMMAND_WORD: + return new ListTasksCommand(); + case SortTaskCommand.COMMAND_WORD: + return new SortTaskCommandParser().parse(arguments); + case FilterTasksCommand.COMMAND_WORD: + return new FilterTasksCommandParser().parse(arguments); + case FindTasksCommand.COMMAND_WORD: + return new FindTaskCommandParser().parse(arguments); + case AddTagCommand.COMMAND_WORD: + return new AddTagCommandParser().parse(arguments); + case EditTagCommand.COMMAND_WORD: + return new EditTagCommandParser().parse(arguments); + case DeleteTagCommand.COMMAND_WORD: + return new DeleteTagCommandParser().parse(arguments); + case ClearTasksCommand.COMMAND_WORD: + return new ClearTasksCommand(); + default: + throw new ParseException(String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "task")); + } + case MODULE_FEATURE_TYPE: + switch (commandWord.toLowerCase()) { + case AddModuleCommand.COMMAND_WORD: + return new AddModuleCommandParser().parse(arguments); + case EditModuleCommand.COMMAND_WORD: + return new EditModuleCommandParser().parse(arguments); + case DeleteModuleCommand.COMMAND_WORD: + return new DeleteModuleCommandParser().parse(arguments); + case ListModulesCommand.COMMAND_WORD: + return new ListModulesCommand(); + case FindModulesCommand.COMMAND_WORD: + return new FindModulesCommandParser().parse(arguments); + default: + throw new ParseException(String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "module")); + } + case EXAM_FEATURE_TYPE: + switch (commandWord.toLowerCase()) { + case AddExamCommand.COMMAND_WORD: + return new AddExamCommandParser().parse(arguments); + case EditExamCommand.COMMAND_WORD: + return new EditExamCommandParser().parse(arguments); + case DeleteExamCommand.COMMAND_WORD: + return new DeleteExamCommandParser().parse(arguments); + case LinkExamCommand.COMMAND_WORD: + return new LinkExamCommandParser().parse(arguments); + case UnlinkExamCommand.COMMAND_WORD: + return new UnlinkExamCommandParser().parse(arguments); + case ListExamTasksCommand.COMMAND_WORD: + return new ListExamTasksCommandParser().parse(arguments); + default: + throw new ParseException(String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "exam")); + } + case ClearAllCommand.COMMAND_WORD: + return new ClearAllCommand(); case HelpCommand.COMMAND_WORD: return new HelpCommand(); - + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..427fef9d7e7 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -6,10 +6,20 @@ public class CliSyntax { /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_MODULE = new Prefix("m/"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_MOD_CODE = new Prefix("c/"); + public static final Prefix PREFIX_PRIORITY_STATUS = new Prefix("p/"); + public static final Prefix PREFIX_EXAM_DESCRIPTION = new Prefix("ex/"); + public static final Prefix PREFIX_EXAM_DATE = new Prefix("ed/"); + public static final Prefix PREFIX_DEADLINE = new Prefix("dl/"); + public static final Prefix PREFIX_MOD_NAME = new Prefix("m/"); + public static final Prefix PREFIX_MOD_CREDIT = new Prefix("mc/"); + public static final Prefix PREFIX_EXAM_INDEX = new Prefix("e/"); + public static final Prefix PREFIX_TASK_INDEX = new Prefix("t/"); + public static final Prefix PREFIX_CRITERIA = new Prefix("c/"); + public static final Prefix PREFIX_IS_COMPLETE = new Prefix("c/"); + public static final Prefix PREFIX_IS_LINKED = new Prefix("l/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteExamCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteExamCommandParser.java new file mode 100644 index 00000000000..0ca6cdcffee --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteExamCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteExamCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteExamCommand object + */ +public class DeleteExamCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the DeleteExamCommand + * and returns a DeleteExamCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteExamCommand parse(String args) throws ParseException { + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteExamCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteExamCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_EXAM_INDEX); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteModuleCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteModuleCommandParser.java new file mode 100644 index 00000000000..79b055d0a60 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteModuleCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteModuleCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteModuleCommand object + */ +public class DeleteModuleCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the DeleteModuleCommand + * and returns a DeleteModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteModuleCommand parse(String args) throws ParseException { + + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteModuleCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteModuleCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_MODULE_INDEX); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java new file mode 100644 index 00000000000..d52294e69a9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses the user input for the command to create a DeleteTagCommand object. + */ +public class DeleteTagCommandParser implements Parser { + public static final String INVALID_INDEX_FOR_DELETE_TAG = "The index for tagdel should" + + " be an unsigned positive integer greater than 0 and lesser than 2147483648."; + private final Logger logger = LogsCenter.getLogger(DeleteTagCommand.class); + @Override + public DeleteTagCommand parse(String userInput) throws ParseException { + logger.info("Parsing arguments for DeleteTagCommand."); + ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(userInput, PREFIX_TAG); + Index index; + if (!isTagPrefixPresent(argMultiMap)) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + DeleteTagCommand.MESSAGE_USAGE)); + } + + if (!argMultiMap.getPreamble().matches("(-|\\+)?\\d+(\\.\\d+)?")) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + DeleteTagCommand.MESSAGE_USAGE)); + } + try { + index = ParserUtil.parseIndex(argMultiMap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(INVALID_INDEX_FOR_DELETE_TAG); + } + Set tags = ParserUtil.parseDeleteTagKeywords(argMultiMap.getValue(PREFIX_TAG) + .orElse(null)); + return new DeleteTagCommand(index, tags); + } + + //@@author dlimyy-reused + //Reused with minor modifications from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) + private static boolean isTagPrefixPresent(ArgumentMultimap argumentMultimap) { + return argumentMultimap.getValue(PREFIX_TAG).isPresent(); + } + //@@author +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java new file mode 100644 index 00000000000..54c684746f5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new DeleteTaskCommand object + */ +public class DeleteTaskCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTaskCommand + * and returns a DeleteTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTaskCommand parse(String args) throws ParseException { + + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteTaskCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_TASK_INDEX); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditExamCommandParser.java b/src/main/java/seedu/address/logic/parser/EditExamCommandParser.java new file mode 100644 index 00000000000..1de830d7c60 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditExamCommandParser.java @@ -0,0 +1,69 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX; +import static seedu.address.logic.commands.EditExamCommand.MESSAGE_NO_FIELDS_PROVIDED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.ParserUtil.parseIndex; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; + +/** + * Parses input arguments and creates a new EditExamCommand object + */ +public class EditExamCommandParser implements Parser { + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + + /** + * Parses the given {@code String} of arguments in the context of the EditExamCommand + * and returns an EditExamCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditExamCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_EXAM_DESCRIPTION, PREFIX_EXAM_DATE); + + Index index; + + if (!pattern.matcher(argMultimap.getPreamble().strip()).matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditExamCommand.MESSAGE_USAGE)); + } + + try { + index = parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_EXAM_INDEX); + } + + EditExamCommand.EditExamDescriptor editExamDescriptor = new EditExamCommand.EditExamDescriptor(); + if (argMultimap.getValue(PREFIX_EXAM_DESCRIPTION).isPresent()) { + editExamDescriptor.setDescription(ParserUtil + .parseExamDescription(argMultimap.getValue(PREFIX_EXAM_DESCRIPTION).get())); + } + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + editExamDescriptor.setModule(new Module(ParserUtil + .parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get()))); + } + if (argMultimap.getValue(PREFIX_EXAM_DATE).isPresent()) { + editExamDescriptor.setExamDate(ParserUtil + .parseExamDate(argMultimap.getValue(PREFIX_EXAM_DATE).get())); + } + + if (!editExamDescriptor.isAnyFieldEdited()) { + throw new ParseException(MESSAGE_NO_FIELDS_PROVIDED); + } + + return new EditExamCommand(index, editExamDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EditModuleCommandParser.java b/src/main/java/seedu/address/logic/parser/EditModuleCommandParser.java new file mode 100644 index 00000000000..a03947bea78 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditModuleCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_INDEX; +import static seedu.address.logic.commands.EditModuleCommand.MESSAGE_NO_FIELDS_PROVIDED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; + +/** + * Parses input arguments and creates a new EditModuleCommand object. + */ +public class EditModuleCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the EditModuleCommand + * and returns an EditModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditModuleCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT); + + if (!pattern.matcher(argMultimap.getPreamble()).matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditModuleCommand.MESSAGE_USAGE)); + } + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_MODULE_INDEX); + } + + EditModuleCommand.EditModuleDescriptor editModuleDescriptor = new EditModuleCommand.EditModuleDescriptor(); + + if (argMultimap.getValue(PREFIX_MOD_CODE).isPresent()) { + ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MOD_CODE).get()); + editModuleDescriptor.setModuleCode(moduleCode); + } + + if (argMultimap.getValue(PREFIX_MOD_NAME).isPresent()) { + ModuleName moduleName = ParserUtil.parseModuleName(argMultimap.getValue(PREFIX_MOD_NAME).get()); + editModuleDescriptor.setModuleName(moduleName); + } + + if (argMultimap.getValue(PREFIX_MOD_CREDIT).isPresent()) { + ModuleCredit moduleCredit = ParserUtil.parseModuleCredit(argMultimap.getValue(PREFIX_MOD_CREDIT).get()); + editModuleDescriptor.setModuleCredit(moduleCredit); + } + + if (!editModuleDescriptor.isAnyFieldEdited()) { + throw new ParseException(MESSAGE_NO_FIELDS_PROVIDED); + } + return new EditModuleCommand(index, editModuleDescriptor); + } + +} + diff --git a/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java new file mode 100644 index 00000000000..4c9fbd2c9bb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java @@ -0,0 +1,65 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; + +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; + +/** + * EditTagCommandParser parses the prefix arguments given by the user to + * create a EditTagCommand object. + */ +public class EditTagCommandParser implements Parser { + public static final String INVALID_INDEX_EDIT_TAG = "The index for tagedit should be an unsigned " + + "positive integer greater than 0 " + + "and lesser than 2147483648."; + private final Logger logger = LogsCenter.getLogger(EditTagCommand.class); + + @Override + public EditTagCommand parse(String args) throws ParseException { + logger.info("Parsing arguments for EditTagCommand."); + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer + .tokenize(args, PREFIX_DEADLINE, PREFIX_PRIORITY_STATUS); + Index index; + + if (!argumentMultimap.getPreamble().matches("(-|\\+)?\\d+(\\.\\d+)?")) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + EditTagCommand.MESSAGE_USAGE)); + } + + if (!areAnyPrefixesPresent(argumentMultimap, PREFIX_PRIORITY_STATUS, PREFIX_DEADLINE)) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + EditTagCommand.MESSAGE_USAGE)); + } + try { + index = ParserUtil.parseIndex(argumentMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(INVALID_INDEX_EDIT_TAG); + } + String priorityStatus = argumentMultimap.getValue(PREFIX_PRIORITY_STATUS).orElse(null); + String deadline = argumentMultimap.getValue(PREFIX_DEADLINE).orElse(null); + PriorityTag priorityTag = priorityStatus != null ? ParserUtil + .parsePriorityTag(priorityStatus) : null; + DeadlineTag deadlineTag = deadline != null ? ParserUtil + .parseDeadlineTag(deadline) : null; + return new EditTagCommand(index, priorityTag, deadlineTag); + } + + //@@author dlimyy-reused + //Reused from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) + private static boolean areAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + //@@author +} diff --git a/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java new file mode 100644 index 00000000000..bcb7f23ca84 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; +import static seedu.address.logic.commands.EditTaskCommand.MESSAGE_NO_FIELDS_PROVIDED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; + +/** + * Parses input arguments and creates a new EditTaskCommand object. + */ +public class EditTaskCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the EditTaskCommand + * and returns an EditTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditTaskCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_DESCRIPTION); + + if (!pattern.matcher(argMultimap.getPreamble().strip()).matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE)); + } + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_TASK_INDEX); + } + + EditTaskCommand.EditTaskDescriptor editTaskDescriptor = new EditTaskCommand.EditTaskDescriptor(); + + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get()); + editTaskDescriptor.setModule(new Module(moduleCode)); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editTaskDescriptor.setDescription( + ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + + if (!editTaskDescriptor.isAnyFieldEdited()) { + throw new ParseException(MESSAGE_NO_FIELDS_PROVIDED); + } + return new EditTaskCommand(index, editTaskDescriptor); + } + +} + diff --git a/src/main/java/seedu/address/logic/parser/FilterTasksCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterTasksCommandParser.java new file mode 100644 index 00000000000..7e430f03573 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterTasksCommandParser.java @@ -0,0 +1,96 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_COMPLETE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_LINKED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.logic.commands.FilterTasksCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.FilterPredicate; + +/** + * Parses input arguments and creates a new FilterTasksCommand object + */ +public class FilterTasksCommandParser implements Parser { + public static final String RESPONSE_CONSTRAINTS = "Response for filter criteria should be indicated as y or n"; + + /** + * Parses the given {@code String} of arguments in the context of the FilterTasksCommand + * and returns a FilterTasksCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterTasksCommand parse(String args) throws ParseException { + Optional module = Optional.empty(); + Optional isCompleted = Optional.empty(); + Optional isLinked = Optional.empty(); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_IS_COMPLETE, PREFIX_IS_LINKED); + + if (!isPrefixPresent(argMultimap, PREFIX_MODULE, PREFIX_IS_COMPLETE, PREFIX_IS_LINKED) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterTasksCommand.MESSAGE_USAGE)); + } + + if (hasPrefix(argMultimap, PREFIX_MODULE)) { + ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get()); + module = Optional.of(new Module(moduleCode)); + } + + if (hasPrefix(argMultimap, PREFIX_IS_COMPLETE)) { + isCompleted = Optional.of(parseYesNoResponse(argMultimap.getValue(PREFIX_IS_COMPLETE).get())); + } + + if (hasPrefix(argMultimap, PREFIX_IS_LINKED)) { + isLinked = Optional.of(parseYesNoResponse(argMultimap.getValue(PREFIX_IS_LINKED).get())); + } + + return new FilterTasksCommand(new FilterPredicate(module, isCompleted, isLinked)); + } + + /** + * Returns true if at least one prefix is not empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean isPrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if the prefix is not empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean hasPrefix(ArgumentMultimap argumentMultimap, Prefix prefix) { + return argumentMultimap.getValue(prefix).isPresent(); + } + + private static boolean isValidYesNoResponse(String response) { + return response.equals("y") || response.equals("n"); + } + + /** + * Parses a {@code String response} into a boolean. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code status} is invalid. + */ + private static boolean parseYesNoResponse(String response) throws ParseException { + requireNonNull(response); + String lowerCaseTrimmedResponse = response.trim().toLowerCase(); + if (!isValidYesNoResponse(lowerCaseTrimmedResponse)) { + throw new ParseException(RESPONSE_CONSTRAINTS); + } + if (lowerCaseTrimmedResponse.equals("y")) { + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindModulesCommandParser.java b/src/main/java/seedu/address/logic/parser/FindModulesCommandParser.java new file mode 100644 index 00000000000..addd2786a55 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindModulesCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindModulesCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; + + + +/** + * Parses input arguments and creates a new FindModulesCommand object + */ +public class FindModulesCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the FindModulesCommand + * and returns a FindModulesCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindModulesCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim().toLowerCase(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindModulesCommand.MESSAGE_USAGE)); + } + return new FindModulesCommand(new ModuleCodeContainsKeywordsPredicate(Arrays.asList(trimmedArgs))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java new file mode 100644 index 00000000000..af2688bd9d7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindTasksCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; + + + +/** + * Parses input arguments and creates a new FindTaskCommand object + */ +public class FindTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the FindTaskCommand + * and returns a FindTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTasksCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim().toLowerCase(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTasksCommand.MESSAGE_USAGE)); + } + return new FindTasksCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList(trimmedArgs))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/LinkExamCommandParser.java b/src/main/java/seedu/address/logic/parser/LinkExamCommandParser.java new file mode 100644 index 00000000000..6577f866159 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LinkExamCommandParser.java @@ -0,0 +1,54 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.LinkExamCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * LinkExamCommandParser parses the arguments provided by the user + * and creates a new LinkExamCommand object. + */ +public class LinkExamCommandParser implements Parser { + + public static final String TASK_INDEX_INVALID = "The index for the task should be " + + "an unsigned positive integer greater than 0 and lesser than 2147483648."; + public static final String EXAM_INDEX_INVALID = "The index for the exam should be " + + "an unsigned positive integer greater than 0 and lesser than 2147483648."; + + @Override + public LinkExamCommand parse(String userArgs) throws ParseException { + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(userArgs, + PREFIX_EXAM_INDEX, PREFIX_TASK_INDEX); + Index examIndex; + Index taskIndex; + if (!areAllPrefixesPresent(argumentMultimap, PREFIX_TASK_INDEX, PREFIX_EXAM_INDEX) + || !argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + LinkExamCommand.MESSAGE_USAGE)); + } + try { + examIndex = ParserUtil.parseIndex(argumentMultimap.getValue(PREFIX_EXAM_INDEX).get()); + } catch (ParseException pe) { + throw new ParseException(EXAM_INDEX_INVALID); + } + try { + taskIndex = ParserUtil.parseIndex(argumentMultimap.getValue(PREFIX_TASK_INDEX).get()); + } catch (ParseException pe) { + throw new ParseException(TASK_INDEX_INVALID); + } + return new LinkExamCommand(examIndex, taskIndex); + } + + //@@author dlimyy-reused + //Reused with minor modifications from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) + private static boolean areAllPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + //@@author +} diff --git a/src/main/java/seedu/address/logic/parser/ListExamTasksCommandParser.java b/src/main/java/seedu/address/logic/parser/ListExamTasksCommandParser.java new file mode 100644 index 00000000000..6b2078bdef9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListExamTasksCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.ListExamTasksCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ListExamTasksCommand object. + */ +public class ListExamTasksCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the ListExamTasksCommand + * and returns ListExamTasksCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListExamTasksCommand parse(String args) throws ParseException { + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListExamTasksCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new ListExamTasksCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_EXAM_INDEX); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/MarkCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java new file mode 100644 index 00000000000..1b1f3458214 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.MarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MarkCommand object. + */ +public class MarkCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the MarkCommand + * and returns a MarkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MarkCommand parse(String args) throws ParseException { + + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new MarkCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_TASK_INDEX); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..4bbf252dbba 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,25 +2,38 @@ import static java.util.Objects.requireNonNull; -import java.util.Collection; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; import java.util.HashSet; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.Criteria; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.TaskDescription; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ + public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_INDEX = "Index should be an unsigned integer that is" + + " greater than 0 and less than 2147483648."; + public static final String MESSAGE_INVALID_KEYWORDS = "The keywords for tagdel must be priority" + + " or deadline or both."; + public static final String MESSAGE_INVALID_NUMBER_OF_KEYWORDS = "The number of keywords used for tag" + + "del should be either 1 or 2"; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -36,89 +49,196 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { } /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. + * Parses the {@code moduleCode} String into a {@code ModuleCode} object. * - * @throws ParseException if the given {@code name} is invalid. + * @param moduleCode The module code of the module. + * @return The ModuleCode object created from the moduleCode string. + * @throws ParseException if the given {@code moduleCode} is not valid. */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); + public static ModuleCode parseModuleCode(String moduleCode) throws ParseException { + requireNonNull(moduleCode); + String trimmedModuleCode = moduleCode.strip(); + if (!ModuleCode.isValidModuleCode(trimmedModuleCode)) { + throw new ParseException(ModuleCode.MODULE_CODE_CONSTRAINTS); } - return new Name(trimmedName); + return new ModuleCode(trimmedModuleCode); } /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. + * Parses the {@code moduleName} String into a {@code ModuleName} object. * - * @throws ParseException if the given {@code phone} is invalid. + * @param moduleName The name of the module. + * @return The ModuleName object created from the moduleName string. + * @throws ParseException if the given {@code moduleName} is not valid. */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + public static ModuleName parseModuleName(String moduleName) throws ParseException { + requireNonNull(moduleName); + String trimmedModuleName = moduleName.strip(); + if (!ModuleName.isValidModuleName(trimmedModuleName)) { + throw new ParseException(ModuleName.MODULE_NAME_CONSTRAINTS); } - return new Phone(trimmedPhone); + return new ModuleName(trimmedModuleName); } /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. + * Parses the {@code moduleCredit} String into a {@code ModuleCredit} object. + * + * @param moduleCredit The module credit of the module. + * @return The ModuleCredit object created from the moduleCredit string. + * @throws ParseException if the given {@code moduleCredit} is not valid. + */ + public static ModuleCredit parseModuleCredit(String moduleCredit) throws ParseException { + requireNonNull(moduleCredit); + final int integerModuleCredit; + String trimmedModuleCredit = moduleCredit.strip(); + try { + integerModuleCredit = Integer.parseInt(trimmedModuleCredit); + } catch (NumberFormatException nfe) { + throw new ParseException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + } + if (!ModuleCredit.isValidModuleCredit(integerModuleCredit)) { + throw new ParseException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + } + return new ModuleCredit(integerModuleCredit); + } + + /** + * Parses delete tag keywords from a String into a Set containing each keyword. + * + * @param keywords The keywords used to specify which tag to delete + * @return The set of string containing the keywords specifying the tags to delete. + * @throws ParseException if the keywords given are invalid or are duplicated. + */ + public static Set parseDeleteTagKeywords(String keywords) throws ParseException { + requireNonNull(keywords); + String trimmedKeywords = keywords.strip(); + String[] keywordsList = trimmedKeywords.split("\\s+"); + final Set keywordSet = new HashSet<>(); + if (keywordsList.length > 2 || keywordsList.length < 1) { + throw new ParseException(MESSAGE_INVALID_NUMBER_OF_KEYWORDS); + } + for (String keyword : keywordsList) { + if (!(keyword.equalsIgnoreCase("priority") + || keyword.equalsIgnoreCase("deadline"))) { + throw new ParseException(MESSAGE_INVALID_KEYWORDS); + } + keywordSet.add(keyword.toLowerCase()); + } + return keywordSet; + } + + /** + * Parses the priority status into a PriorityTag. + * + * @param priorityTag The priority status added to the tag. + * @return The priorityTag object containing the priority status. + * @throws ParseException if the priority status is not valid. + */ + public static PriorityTag parsePriorityTag(String priorityTag) throws ParseException { + requireNonNull(priorityTag); + String trimmedPriorityStatus = priorityTag.strip(); + if (!PriorityTag.isValidTag(trimmedPriorityStatus)) { + throw new ParseException(PriorityTag.PRIORITY_TAG_CONSTRAINTS); + } + return new PriorityTag(trimmedPriorityStatus); + } + + /** + * Parses the deadline into a DeadlineTag. * - * @throws ParseException if the given {@code address} is invalid. + * @param deadline The deadline which is added to the DeadlineTag. + * @return The deadlineTag containing the deadline status. + * @throws ParseException if the deadline is in an invalid format. */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); + public static DeadlineTag parseDeadlineTag(String deadline) throws ParseException { + requireNonNull(deadline); + final LocalDate date; + if (!DeadlineTag.checkDateFormat(deadline)) { + throw new ParseException(DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS); } - return new Address(trimmedAddress); + //@@author dlimyy-reused + //Reused from https://stackoverflow.com/questions/32823368/ + //with minor modifications. + final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-uuuu") + .withResolverStyle(ResolverStyle.STRICT); + //@@author + try { + date = LocalDate.parse(deadline, dtf); + } catch (DateTimeParseException dtp) { + throw new ParseException(DeadlineTag.DEADLINE_TAG_INVALID_DATE); + } + if (!DeadlineTag.isValidDeadline(date)) { + throw new ParseException(DeadlineTag.DEADLINE_TAG_DATE_HAS_PASSED); + } + return new DeadlineTag(date); } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String description} into a {@code Description}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code description} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + public static TaskDescription parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!TaskDescription.isValidDescription(trimmedDescription)) { + throw new ParseException(TaskDescription.DESCRIPTION_CONSTRAINTS); } - return new Email(trimmedEmail); + return new TaskDescription(trimmedDescription); } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses the criteria given to create a new Criteria object. + * + * @param criteria The criteria given for sorting. + * @return The criteria object which contains the criteria used for sorting. + * @throws ParseException if the given {@code criteria} is invalid. + */ + public static Criteria parseCriteria(String criteria) throws ParseException { + requireNonNull(criteria); + String strippedCriteria = criteria.strip(); + if (!Criteria.isValidCriteria(strippedCriteria)) { + throw new ParseException(Criteria.CRITERIA_CONSTRAINTS); + } + return new Criteria(strippedCriteria); + } + + /** + * Parses a {@code String description} into a {@code ExamDescription}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code description} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static ExamDescription parseExamDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!ExamDescription.isValidDescription(trimmedDescription)) { + throw new ParseException(ExamDescription.DESCRIPTION_CONSTRAINTS); } - return new Tag(trimmedTag); + return new ExamDescription(trimmedDescription); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String examDate} into a {@code ExamDate}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code examDate} is invalid. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static ExamDate parseExamDate(String examDate) throws ParseException { + requireNonNull(examDate); + String trimmedDate = examDate.trim(); + + if (!ExamDate.isCorrectDateFormat(trimmedDate)) { + throw new ParseException(ExamDate.DATE_FORMAT_CONSTRAINTS); + } + if (!ExamDate.isExistingDate(trimmedDate)) { + throw new ParseException(ExamDate.VALID_DATE_CONSTRAINTS); } - return tagSet; + if (!ExamDate.isNotAPastDate(trimmedDate)) { + throw new ParseException(ExamDate.NOT_A_PAST_DATE_CONSTRAINTS); + } + return new ExamDate(trimmedDate); } + } diff --git a/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java new file mode 100644 index 00000000000..f2257e76b17 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_CRITERIA; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Criteria; + + +/** + * SortTaskCommandParser represents a parser which parses the arguments + * given by the user to create a SortTaskCommand object. + */ +public class SortTaskCommandParser implements Parser { + private final Logger logger = LogsCenter.getLogger(SortTaskCommand.class); + @Override + public SortTaskCommand parse(String args) throws ParseException { + logger.info("Parsing arguments for SortTaskCommand."); + ArgumentMultimap argumentMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CRITERIA); + if (!isPrefixPresent(argumentMultimap) + || !argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + SortTaskCommand.MESSAGE_USAGE)); + } + + Criteria criteria = ParserUtil.parseCriteria(argumentMultimap.getValue(PREFIX_CRITERIA).get()); + return new SortTaskCommand(criteria); + } + + private static boolean isPrefixPresent(ArgumentMultimap argumentMultimap) { + return argumentMultimap.getValue(PREFIX_CRITERIA).isPresent(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnlinkExamCommandParser.java b/src/main/java/seedu/address/logic/parser/UnlinkExamCommandParser.java new file mode 100644 index 00000000000..c277d985260 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnlinkExamCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnlinkExamCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * UnlinkExamCommandParser parses the arguments provided by the user and creates a new UnlinkExamCommand object. + */ +public class UnlinkExamCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the UnlinkExamCommand + * and returns a UnlinkedExamCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnlinkExamCommand parse(String args) throws ParseException { + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnlinkExamCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new UnlinkExamCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_TASK_INDEX); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java new file mode 100644 index 00000000000..5fd8ff596d8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; + +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnmarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UnmarkCommand object. + */ +public class UnmarkCommandParser implements Parser { + + private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?"); + + /** + * Parses the given {@code String} of arguments in the context of the UnmarkCommand + * and returns an UnmarkCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UnmarkCommand parse(String args) throws ParseException { + + if (!pattern.matcher(args.strip()).matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new UnmarkCommand(index); + } catch (ParseException pe) { + throw new ParseException(MESSAGE_INVALID_TASK_INDEX); + } + } + +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..3e556bf06e3 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -1,12 +1,20 @@ package seedu.address.model; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.List; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.exam.DistinctExamList; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.DistinctModuleList; +import seedu.address.model.module.Module; +import seedu.address.model.module.exceptions.DuplicateModuleException; +import seedu.address.model.task.DistinctTaskList; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.DuplicateTaskException; /** * Wraps all data at the address-book level @@ -14,7 +22,10 @@ */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final DistinctModuleList modules; + private final DistinctTaskList tasks; + private final DistinctExamList exams; + /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -24,7 +35,9 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + modules = new DistinctModuleList(); + tasks = new DistinctTaskList(); + exams = new DistinctExamList(); } public AddressBook() {} @@ -39,12 +52,17 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { //// list overwrite operations - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + + public void setModules(List modules) { + this.modules.setModules(modules); + } + + public void setExams(List exams) { + this.exams.setExams(exams); + } + + public void setTasks(List tasks) { + this.tasks.setTasks(tasks); } /** @@ -52,69 +70,298 @@ public void setPersons(List persons) { */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); + setTasks(newData.getTaskList()); + setModules(newData.getModuleList()); + setExams(newData.getExamList()); + } + + //// module-level operations - setPersons(newData.getPersonList()); + public void addModule(Module mod) { + modules.addModule(mod); + } + + /** + * Checks whether the module list contains the module. + * + * @param module The module that is being checked. + * @return true if the module list contains the module; else returns false. + */ + public boolean hasModule(Module module) { + requireNonNull(module); + return modules.containsModule(module); } - //// person-level operations + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + * {@code key} must not be tied to any tasks in the address book. + */ + public void removeModule(Module key) { + modules.remove(key); + } /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Replaces the given Module {@code target} with {@code editedModule}. + * {@code target} must exist in the module list. + * + * @throws DuplicateModuleException if module identity of {@code editedModule} is the same as another module + * in the list (other than {@code target}). */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public void replaceModule(Module target, Module editedModule) throws DuplicateModuleException { + requireAllNonNull(target, editedModule); + + modules.replaceModule(target, editedModule); } + //// exam-level operations + /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Returns true if an exam with the same module and exam description and exam date + * as {@code exam} exists in the exam list. */ - public void addPerson(Person p) { - persons.add(p); + public boolean hasExam(Exam exam) { + requireNonNull(exam); + return exams.contains(exam); + } + + public boolean hasExamWithModule(Module module) { + return exams.containsModule(module); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * Adds an exam to the exam list. + * The exam must not already exist in the exam list. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public void addExam(Exam exam) { + exams.addExam(exam); + } - persons.setPerson(target, editedPerson); + + /** + * Replaces the given exam {@code target} with {@code editedExam}. + * {@code target} must exist in the exam list. + * + * @throws DuplicateExamException if task identity of {@code editedExam} is the same as another exam + * in the list (other than {@code target}). + */ + public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException { + requireAllNonNull(target, editedExam); + exams.replaceExam(target, editedExam, isSameExam); } /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. */ - public void removePerson(Person key) { - persons.remove(key); + public void removeExam(Exam key) { + exams.remove(key); + } + + + //// task-level operations + + /** + * Returns true if a task with the same module and description as {@code task} exists in the task list. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + + public boolean hasTaskWithModule(Module module) { + return tasks.containsModule(module); + } + + /** + * Adds a task to the task list. + * The task must not already exist in the task list. + */ + public void addTask(Task task) { + tasks.addTask(task); + modules.updateTotalNumOfTasks(task.getModule(), tasks); + modules.updateNumOfCompletedTasks(task.getModule(), tasks); + if (task.isLinked()) { + exams.updateTotalNumOfTasks(task.getExam(), tasks); + exams.updateNumOfCompletedTasks(task.getExam(), tasks); + } + } + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the task list. + * If {@code isSameTask} is true, the task identity of {@code editedTask} should be the same as {@code target}. + * + * @param target the task to be replaced. + * @param editedTask the edited task to replace {@code target}. + * @param isSameTask true if {@code target} has the same task identity as {@code editedTask}, false otherwise. + * @throws DuplicateTaskException if {@code isSameTask} is false but task identity of {@code editedTask} + * is the same as another task in the list (other than {@code target}). + */ + public void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException { + requireAllNonNull(target, editedTask); + tasks.replaceTask(target, editedTask, isSameTask); + modules.updateNumOfCompletedTasks(target.getModule(), tasks); + modules.updateTotalNumOfTasks(target.getModule(), tasks); + modules.updateNumOfCompletedTasks(editedTask.getModule(), tasks); + modules.updateTotalNumOfTasks(editedTask.getModule(), tasks); + + if (target.isLinked() && !editedTask.isLinked()) { + // to update exam when a task is unlinked from an exam + exams.updateTotalNumOfTasks(target.getExam(), tasks); + exams.updateNumOfCompletedTasks(target.getExam(), tasks); + } + + if (editedTask.isLinked()) { + // to update exam for linked tasks + exams.updateTotalNumOfTasks(editedTask.getExam(), tasks); + exams.updateNumOfCompletedTasks(editedTask.getExam(), tasks); + } + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTask(Task key) { + tasks.remove(key); + modules.updateNumOfCompletedTasks(key.getModule(), tasks); + modules.updateTotalNumOfTasks(key.getModule(), tasks); + if (key.isLinked()) { + exams.updateNumOfCompletedTasks(key.getExam(), tasks); + exams.updateTotalNumOfTasks(key.getExam(), tasks); + } } //// util methods + /** + * Resets number of tasks and number of completed tasks of all modules and exams to 0. + */ + public void resetAllTaskCount() { + modules.resetAllTaskCount(); + exams.resetAllTaskCount(); + } + @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later + public ObservableList getModuleList() { + return modules.getUnmodifiableModuleList(); } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getTaskList() { + return tasks.getUnmodifiableTaskList(); + } + + @Override + public ObservableList getExamList() { + return exams.getUnmodifiableExamList(); + } + + /** + * Sorts the task list in the address book. + * + * @param criteria The criteria used for sorting the task list. + */ + public void sortTaskList(Criteria criteria) { + requireNonNull(criteria); + tasks.sortTasks(criteria); + } + + /** + * Unlinks all tasks that are currently linked to {@code exam}. + * @param exam The exam to unlink all tasks from. + */ + public void unlinkTasksFromExam(Exam exam) { + tasks.unlinkTasksFromExam(exam); + } + + /** + * Replaces task by changing its given exam field from {@code previousExam} + * to {@code newExam} for tasks that have their exam field as {@code previousExam}. + * @param previousExam The exam in the task's exam field. + * @param newExam The new exam which will replace the previous exam in the task's exam field. + */ + public void updateExamFieldForTask(Exam previousExam, Exam newExam) { + requireAllNonNull(previousExam, newExam); + tasks.updateExamFieldForTask(previousExam, newExam); + } + + /** + * Returns true if {@code examToEdit} is linked to any task, otherwise false. + */ + public boolean isExamLinkedToTask(Exam examToEdit) { + requireNonNull(examToEdit); + return tasks.isExamLinkedToTask(examToEdit); + } + /** + * Replaces task by changing its given module field from {@code previousModule} + * to {@code newModule} for tasks that have their module field as {@code previousModule}. + * @param previousModule The module in the task's module field. + * @param newModule The new module which will replace the previous module in the task's module field. + */ + public void updateModuleFieldForTask(Module previousModule, Module newModule) { + requireAllNonNull(previousModule, newModule); + tasks.updateModuleFieldForTask(previousModule, newModule); + modules.updateTotalNumOfTasks(newModule, tasks); + modules.updateNumOfCompletedTasks(newModule, tasks); + } + + /** + * Replaces exam by changing its given module field from {@code previousModule} + * to {@code newModule} for all exams that have their module field as {@code previousModule}. It also links + * the replaced exam to tasks previously linked to the exam with {code previousModule}. + * + * @param previousModule The module in the exam's module field. + * @param newModule The new module which will replace the previous module in the exam's module field. + */ + public void updateModuleFieldForExam(Module previousModule, Module newModule) { + requireAllNonNull(previousModule, newModule); + exams.updateModuleFieldForExam(tasks, previousModule, newModule); + exams.updateTotalNumOfTasksForAllExams(tasks); + exams.updateNumOfCompletedTasksForAllExams(tasks); + } + + /** + * Deletes tasks that have their module field as {@code module}. + * @param module The module in the task's module field. + */ + public void deleteTasksWithModule(Module module) { + requireNonNull(module); + tasks.deleteTasksWithModule(module); + } + + /** + * Deletes exams that have their module field as {@code module}. + * @param module The module in the exam's module field. + */ + public void deleteExamsWithModule(Module module) { + requireNonNull(module); + exams.deleteExamsWithModule(module); + } + + @Override + public String toString() { + return modules.getUnmodifiableModuleList().size() + " modules" + + "\n" + tasks.getUnmodifiableTaskList().size() + "tasks" + + "\n" + exams.getUnmodifiableExamList().size() + "exams"; + // TODO: refine later } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && tasks.equals(((AddressBook) other).tasks) + && exams.equals(((AddressBook) other).exams) + && modules.equals(((AddressBook) other).modules)); } + //@@author @Override public int hashCode() { - return persons.hashCode(); + return modules.hashCode(); } + //@@author + } diff --git a/src/main/java/seedu/address/model/Criteria.java b/src/main/java/seedu/address/model/Criteria.java new file mode 100644 index 00000000000..17faf1b0a6b --- /dev/null +++ b/src/main/java/seedu/address/model/Criteria.java @@ -0,0 +1,51 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Criteria class which represents the criteria that the user is sorting + * the task list by. + */ +public class Criteria { + public static final String CRITERIA_CONSTRAINTS = + "The sorting criteria should be either priority, deadline, module or description"; + private final String criteria; + /** + * The constructor of the criteria class. Sets the criteria which + * will be used for sorting. + * + * @param criteria The criteria which is used for sorting. + */ + public Criteria(String criteria) { + requireNonNull(criteria); + checkArgument(isValidCriteria(criteria), CRITERIA_CONSTRAINTS); + this.criteria = criteria; + } + + /** + * Checks whether the criteria given by the user is valid. + * + * @param criteria The criteria that is being checked for validity. + * @return true if the criteria is valid; else return false. + */ + public static boolean isValidCriteria(String criteria) { + requireNonNull(criteria); + return criteria.equalsIgnoreCase("priority") + || criteria.equalsIgnoreCase("deadline") + || criteria.equalsIgnoreCase("module") + || criteria.equalsIgnoreCase("description"); + } + + public String getCriteria() { + return criteria; + } + + + + @Override + public boolean equals(Object otherCriteria) { + return otherCriteria instanceof Criteria + && criteria.equalsIgnoreCase(((Criteria) otherCriteria).criteria); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..34b335d97eb 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,14 +5,27 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.DuplicateTaskException; /** * The API of the Model component. */ public interface Model { + + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; + /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_MODULES = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_EXAMS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -52,36 +65,146 @@ public interface Model { /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); + void addModule(Module module); + + ObservableList getFilteredModuleList(); + + /** + * Returns true if a task with the same description and module as {@code task} exists in the task list. + */ + boolean hasTask(Task task); + + /** + * Returns true if a task with {@code module} exists in the task list. + */ + boolean hasTaskWithModule(Module module); + /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Adds the given task. + * {@code task} must not already exist in the task list. */ - boolean hasPerson(Person person); + void addTask(Task task); /** - * Deletes the given person. - * The person must exist in the address book. + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the task list. + * If {@code isSameTask} is true, the task identity of {@code editedTask} should be the same as {@code target}. + * + * @param target the task to be replaced. + * @param editedTask the edited task to replace {@code target}. + * @param isSameTask true if {@code target} has the same task identity as {@code editedTask}, false otherwise. + * @throws DuplicateTaskException if {@code isSameTask} is false but task identity of {@code editedTask} + * is the same as another task in the list (other than {@code target}). */ - void deletePerson(Person target); + void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException; + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredTaskList(); + + boolean hasModule(Module module); + void updateFilteredModuleList(Predicatepredicate); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Deletes the given task. + * The task must exist in the address book. */ - void addPerson(Person person); + void deleteTask(Task target); /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * Deletes the given module. + * The module must exist in the address book. */ - void setPerson(Person target, Person editedPerson); + void deleteModule(Module target); + + void replaceModule(Module target, Module editedModule); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered task list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredTaskList(Predicate predicate); + + + void sortTaskList(Criteria criteria); + + /** + * Updates the task list to unlink all tasks that are currently linked to the give {@code exam}. + * @param exam + */ + void unlinkTasksFromExam(Exam exam); + + /** + * Returns true if an exam with the same description and module and exam date + * as {@code exam} exists in the exam list. + */ + boolean hasExam(Exam exam); + + void deleteExam(Exam target); + + boolean hasExamWithModule(Module module); + + /** + * Adds the given exam. + * {@code exam} must not already exist in the exam list. + */ + void addExam(Exam exam); + + + /** + * Replaces the given exam {@code target} with {@code editedExam}. + * {@code target} must exist in the exam list. + * + * @throws DuplicateExamException if task identity of {@code editedExam} is the same as another exam + * in the list (other than {@code target}). + */ + void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException; + + /** Returns an unmodifiable view of the filtered exam list */ + ObservableList getFilteredExamList(); + + /** + * Updates the filter of the filtered exam list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredExamList(Predicatepredicate); + + + /** + * Updates the exam field in task by replacing the previous exam with the new exam. + * @param previousExam The exam in the task's exam field. + * @param newExam The new exam which will replace the previous exam in the task's exam field. + */ + void updateExamFieldForTask(Exam previousExam, Exam newExam); + + /** + * Returns true if {@code examToEdit} is linked to any task, otherwise false. + */ + boolean isExamLinkedToTask(Exam examToEdit); + + /** + * Updates the module field in task by replacing the previous module with the new module. + * @param previousModule The module in the task's module field. + * @param newModule The new module which will replace the previous module in the task's module field. + */ + void updateModuleFieldForTask(Module previousModule, Module newModule); + + /** + * Updates the module field in exam by replacing the previous module with the new module. + * @param previousModule The module in the exam's module field. + * @param newModule The new module which will replace the previous module in the exam's module field. + */ + void updateModuleFieldForExam(Module previousModule, Module newModule); + + /** + * Deletes tasks that have their module field as {@code module}. + * @param module The module in the task's module field. + */ + void deleteTasksWithModule(Module module); + + /** + * Deletes exams that have their module field as {@code module}. + * @param module The module in the exam's module field. + */ + void deleteExamsWithModule(Module module); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..0e27e3cad26 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,7 +11,11 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; + + /** * Represents the in-memory model of the address book data. @@ -21,7 +25,13 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + + private final FilteredList moduleFilteredList; + + private final FilteredList taskFilteredList; + + private final FilteredList examFilteredList; + /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -33,7 +43,9 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + moduleFilteredList = new FilteredList<>(this.addressBook.getModuleList()); + taskFilteredList = new FilteredList<>(this.addressBook.getTaskList()); + examFilteredList = new FilteredList<>(this.addressBook.getExamList()); } public ModelManager() { @@ -87,64 +99,182 @@ public ReadOnlyAddressBook getAddressBook() { return addressBook; } + //========== Task List ================================================================================== + @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public boolean hasTaskWithModule(Module module) { + requireNonNull(module); + return addressBook.hasTaskWithModule(module); } + @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void addTask(Task task) { + addressBook.addTask(task); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void replaceTask(Task target, Task editedTask, boolean isSameTask) { + requireAllNonNull(target, editedTask, isSameTask); - addressBook.setPerson(target, editedPerson); + addressBook.replaceTask(target, editedTask, isSameTask); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void deleteTask(Task target) { + addressBook.removeTask(target); + } + + //========== Exam List ================================================================================== + @Override + public boolean hasExam(Exam exam) { + requireNonNull(exam); + return addressBook.hasExam(exam); + } + + @Override + public boolean hasExamWithModule(Module module) { + requireNonNull(module); + return addressBook.hasExamWithModule(module); + } + + + @Override + public void addExam(Exam exam) { + addressBook.addExam(exam); + updateFilteredExamList(PREDICATE_SHOW_ALL_EXAMS); + } + + @Override + public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) { + requireAllNonNull(target, editedExam); + addressBook.replaceExam(target, editedExam, isSameExam); + } + + @Override + public void deleteExam(Exam target) { + addressBook.removeExam(target); + } + + //=============================Module Commands================================ + @Override + public void addModule(Module module) { + addressBook.addModule(module); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + @Override + public boolean hasModule(Module module) { + requireNonNull(module); + return addressBook.hasModule(module); + } + + @Override + public ObservableList getFilteredModuleList() { + return moduleFilteredList; + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + requireNonNull(predicate); + moduleFilteredList.setPredicate(predicate); + } + + @Override + public void deleteModule(Module target) { + addressBook.removeModule(target); + } + + @Override + public void replaceModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + + addressBook.replaceModule(target, editedModule); + } + + //================================Task Commands===================================== + @Override + public ObservableList getFilteredTaskList() { + return taskFilteredList; + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + requireNonNull(predicate); + taskFilteredList.setPredicate(predicate); + } + + @Override + public void sortTaskList(Criteria criteria) { + requireNonNull(criteria); + addressBook.sortTaskList(criteria); + } + + @Override + public void unlinkTasksFromExam(Exam exam) { + requireNonNull(exam); + addressBook.unlinkTasksFromExam(exam); + } + + @Override + public void updateExamFieldForTask(Exam previousExam, Exam newExam) { + requireAllNonNull(previousExam, newExam); + addressBook.updateExamFieldForTask(previousExam, newExam); + } + + @Override + public void updateModuleFieldForTask(Module previousModule, Module newModule) { + requireAllNonNull(previousModule, newModule); + addressBook.updateModuleFieldForTask(previousModule, newModule); + } - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public void deleteTasksWithModule(Module module) { + requireNonNull(module); + addressBook.deleteTasksWithModule(module); } + //================================Exam Commands===================================== @Override - public void updateFilteredPersonList(Predicate predicate) { + public ObservableList getFilteredExamList() { + return examFilteredList; + } + + @Override + public void updateFilteredExamList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + examFilteredList.setPredicate(predicate); } @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } + public boolean isExamLinkedToTask(Exam examToEdit) { + requireNonNull(examToEdit); + return addressBook.isExamLinkedToTask(examToEdit); + } - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } + @Override + public void updateModuleFieldForExam(Module previousModule, Module newModule) { + requireAllNonNull(previousModule, newModule); + addressBook.updateModuleFieldForExam(previousModule, newModule); + } - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + @Override + public void deleteExamsWithModule(Module module) { + requireNonNull(module); + addressBook.deleteExamsWithModule(module); } + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof ModelManager + && this.addressBook.equals(((ModelManager) other).addressBook)); + } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..379e658f9b7 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,10 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; + /** * Unmodifiable view of an address book @@ -12,6 +15,19 @@ public interface ReadOnlyAddressBook { * Returns an unmodifiable view of the persons list. * This list will not contain any duplicate persons. */ - ObservableList getPersonList(); + + ObservableList getModuleList(); + + /** + * Returns an unmodifiable view of the task list. + * This list will not contain any duplicate tasks. + */ + ObservableList getTaskList(); + + /** + * Returns an unmodifiable view of the exam list. + * This list will not contain any duplicate exams. + */ + ObservableList getExamList(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..6aa52b3f131 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "modpro.json"); /** * Creates a {@code UserPrefs} with default values. diff --git a/src/main/java/seedu/address/model/exam/DistinctExamList.java b/src/main/java/seedu/address/model/exam/DistinctExamList.java new file mode 100644 index 00000000000..fe8bd137b66 --- /dev/null +++ b/src/main/java/seedu/address/model/exam/DistinctExamList.java @@ -0,0 +1,239 @@ +package seedu.address.model.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.exam.exceptions.ExamIdentityModifiedException; +import seedu.address.model.exam.exceptions.ExamNotFoundException; +import seedu.address.model.module.Module; +import seedu.address.model.task.DistinctTaskList; +import seedu.address.model.task.Task; + +/** + * This class represents a list which contains Exam objects which are distinct from + * each other. Exam objects are distinct from each other when they have different module + * codes and different description and different dates. + */ +public class DistinctExamList implements Iterable { + + public final ObservableList examList = FXCollections.observableArrayList(); + public final ObservableList unmodifiableExamList = FXCollections + .unmodifiableObservableList(examList); + + /** + * Returns true if the exam list contains an equivalent exam as the given argument. + */ + public boolean contains(Exam toCheck) { + requireNonNull(toCheck); + return examList.stream().anyMatch(toCheck::isSameExam); + } + + /** + * Returns true if the exam list contains an exam with an equivalent module as the given argument. + */ + public boolean containsModule(Module toCheck) { + requireNonNull(toCheck); + + return examList.stream().map(Exam::getModule).anyMatch(toCheck::isSameModule); + } + + /** + * Adds the exam to the examList. + * The exam must not already exist in the list. + * + * @param examAdded The exam to be added. + */ + public void addExam(Exam examAdded) { + requireNonNull(examAdded); + if (contains(examAdded)) { + throw new DuplicateExamException(); + } + examList.add(examAdded); + } + + /** + * Replaces the given exam {@code target} with {@code editedExam}. + * {@code target} must exist in the exam list. + * + * @throws DuplicateExamException if task identity of {@code editedExam} is the same as another exam + * in the exam list (other than {@code target}). + */ + public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException { + requireAllNonNull(target, editedExam); + + int index = examList.indexOf(target); + if (index == -1) { + throw new ExamNotFoundException(); + } + + if (isSameExam && !target.isSameExam(editedExam)) { + throw new ExamIdentityModifiedException(); + } + + if (!isSameExam && contains(editedExam) && !editedExam.isSameExam(target)) { + throw new DuplicateExamException(); + } + examList.set(index, editedExam); + } + + /** + * Removes the equivalent exam from the exam list. + * The exam must exist in the list. + */ + public void remove(Exam toRemove) { + requireNonNull(toRemove); + if (!examList.remove(toRemove)) { + throw new ExamNotFoundException(); + } + } + + /** + * Counts the number of tasks in {@code tasks} linked to {@code exam}, + * and updates this number in {@code exam}. + * {@code exam} must exist in the exam list. + * + * @param exam The exam to check for number of tasks. + * @param tasks The list of tasks to check with the exam. + */ + public void updateTotalNumOfTasks(Exam exam, DistinctTaskList tasks) { + requireAllNonNull(exam, tasks); + int totalNumOfTasks = tasks.getTotalNumOfExamTasks(exam); + + int index = examList.indexOf(exam); + if (index == -1) { + throw new ExamNotFoundException(); + } + + Exam examToEdit = examList.get(index); + Exam updatedExam = examToEdit.setTotalNumOfTasks(totalNumOfTasks); + examList.set(index, updatedExam); + } + + /** + * Counts the number of tasks in {@code tasks} linked to each exam in the exam list + * and updates this number in each exam. + * + * @param tasks The list of tasks to check with the exam. + */ + public void updateTotalNumOfTasksForAllExams(DistinctTaskList tasks) { + examList.forEach(exam -> updateTotalNumOfTasks(exam, tasks)); + } + + /** + * Counts the number of completed tasks in {@code tasks} linked to {@code exam}, + * and updates this number in {@code exam}. + * {@code exam} must exist in the exam list. + * + * @param exam The exam to check for number of completed tasks. + * @param tasks The list of tasks to check with the exam. + */ + public void updateNumOfCompletedTasks(Exam exam, DistinctTaskList tasks) { + requireAllNonNull(exam, tasks); + int numOfCompletedTasks = tasks.getNumOfCompletedExamTasks(exam); + + int index = examList.indexOf(exam); + if (index == -1) { + throw new ExamNotFoundException(); + } + Exam examToEdit = examList.get(index); + Exam updatedExam = examToEdit.setNumOfCompletedTasks(numOfCompletedTasks); + examList.set(index, updatedExam); + } + + /** + * Counts the number of completed tasks in {@code tasks} linked to each exam in the exam list, + * and updates this number in each exam. + * + * @param tasks The list of tasks to check with the exam. + */ + public void updateNumOfCompletedTasksForAllExams(DistinctTaskList tasks) { + examList.forEach(exam -> updateNumOfCompletedTasks(exam, tasks)); + } + + /** + * Resets number of tasks and number of completed tasks of all exams to 0. + */ + public void resetAllTaskCount() { + examList.forEach(exam -> { + int index = examList.indexOf(exam); + Exam updatedExam = exam.setNumOfCompletedTasks(0); + updatedExam = updatedExam.setTotalNumOfTasks(0); + examList.set(index, updatedExam); + }); + } + + /** + * Replaces all exams that have their module field as {@code previousModule} by changing its given module field + * to {@code newModule}. It also links all tasks in {@code taskList} which has an exam having + * {@code previousModule} to the corresponding exam with {@code newModule}. + * + * @param taskList The list of tasks. + * @param previousModule The module in the exam's module field. + * @param newModule The new module which will replace the previous module in the exams's module field. + */ + public void updateModuleFieldForExam(DistinctTaskList taskList, Module previousModule, Module newModule) { + requireAllNonNull(taskList, previousModule, newModule); + examList.forEach(exam -> { + if (exam.getModule().equals(previousModule)) { + Exam editedExam = exam.edit(newModule, null, null); + updateExamFieldForTask(taskList, exam, editedExam); + } + }); + } + + /** + * Unlinks all tasks from {@code previousExam} and links these tasks to {@code newExam}. It also replaces + * the {@code previousExam} with {@code newExam}. + * + * @param taskList The list of tasks. + * @param previousExam The exam to be replaced and linked to. + * @param newExam The exam which will replace {@code previousExam} and will be linked to. + */ + private void updateExamFieldForTask(DistinctTaskList taskList, Exam previousExam, Exam newExam) { + List unlinkedTasks = taskList.unlinkTasksFromExam(previousExam); + replaceExam(previousExam, newExam, false); + taskList.linkTasksToExam(newExam, unlinkedTasks); + } + + /** + * Remove exams that have their module field as {@code module} from the examlist. + * @param module The module in the exam's module field. + */ + public void deleteExamsWithModule(Module module) { + requireAllNonNull(module); + for (int i = 0; i < examList.size(); i++) { + Exam exam = examList.get(i); + if (exam.getModule().equals(module)) { + remove(exam); + --i; + } + } + } + + @Override + public Iterator iterator() { + return examList.iterator(); + } + + @Override + public boolean equals(Object otherExam) { + return otherExam == this + || (otherExam instanceof DistinctExamList + && examList.equals(((DistinctExamList) otherExam).examList)); + } + + public ObservableList getUnmodifiableExamList() { + return unmodifiableExamList; + } + + public void setExams(List exams) { + requireNonNull(exams); + examList.setAll(exams); + } +} diff --git a/src/main/java/seedu/address/model/exam/Exam.java b/src/main/java/seedu/address/model/exam/Exam.java new file mode 100644 index 00000000000..de13064c1fa --- /dev/null +++ b/src/main/java/seedu/address/model/exam/Exam.java @@ -0,0 +1,209 @@ +package seedu.address.model.exam; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAnyNonNull; + +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.model.exam.exceptions.NoLinkedTaskForExamException; +import seedu.address.model.module.Module; + +/** + * Exam class represents an exam which stores the module code, the + * description and the date of the exam. + */ +public class Exam { + + public static final String MESSAGE_NO_TASKS_FOR_EXAM = "You have no tasks for this exam"; + + private final Module module; + private final ExamDescription examDescription; + private final ExamDate examDate; + private int totalNumOfTasks; + private int numOfCompletedTasks; + + /** + * The constructor of the Exam class. Sets the module, + * exam description and exam date. + * + * @param module The module associated with the exam. + * @param examDescription The description of the exam. + * @param examDate The date of the exam. + * + */ + public Exam(Module module, ExamDescription examDescription, ExamDate examDate) { + this.module = module; + this.examDescription = examDescription; + this.examDate = examDate; + this.totalNumOfTasks = 0; + this.numOfCompletedTasks = 0; + } + + /** + * The constructor of the Exam class. Sets the module, + * exam description, exam date, the total number of tasks + * and number of completed tasks linked to the exam. + * + * @param module The module being added. + * @param examDescription The description of the exam. + * @param examDate The date of the exam. + * @param totalNumOfTasks The total number of tasks linked to the exam. + * @param numOfCompletedTasks The number of completed tasks linked to the exam. + * + */ + public Exam(Module module, ExamDescription examDescription, ExamDate examDate, + int totalNumOfTasks, int numOfCompletedTasks) { + this.module = module; + this.examDescription = examDescription; + this.examDate = examDate; + this.totalNumOfTasks = totalNumOfTasks; + this.numOfCompletedTasks = numOfCompletedTasks; + } + + public ExamDescription getDescription() { + return examDescription; + } + + public Module getModule() { + return module; + } + + public ExamDate getExamDate() { + return examDate; + } + + public int getNumOfCompletedTasks() { + return numOfCompletedTasks; + } + public int getTotalNumOfTasks() { + return totalNumOfTasks; + } + + /** + * Returns true if both exams have the same data fields. + */ + public boolean isSameExam(Exam otherExam) { + return this.equals(otherExam); + } + + public Exam setTotalNumOfTasks(Integer totalNumOfTasks) { + return new Exam(this.module, this.examDescription, this.examDate, totalNumOfTasks, + this.numOfCompletedTasks); + } + + public Exam setNumOfCompletedTasks(Integer numOfCompletedTasks) { + return new Exam(this.module, this.examDescription, this.examDate, this.totalNumOfTasks, + numOfCompletedTasks); + } + + /** + * Returns the percentage of tasks completed for the exam. + */ + public double getPercentageCompleted() { + assert(numOfCompletedTasks >= 0); + assert(totalNumOfTasks >= 0); + if (totalNumOfTasks == 0) { + throw new NoLinkedTaskForExamException(); + } + return (double) numOfCompletedTasks / (double) totalNumOfTasks; + } + + /** + * Returns a string representation of the number of completed tasks and number of total tasks linked to the exam. + */ + public String generateProgressMessage() { + if (totalNumOfTasks == 0) { + return MESSAGE_NO_TASKS_FOR_EXAM; + } else { + return numOfCompletedTasks + " / " + totalNumOfTasks + " task(s) completed"; + } + } + + public boolean hasTasks() { + return !(totalNumOfTasks == 0); + } + + /** + * Creates and returns a {@code Exam} with the details of {@code this} + * edited with {@code editExamDescriptor}. + */ + public Exam edit(EditExamCommand.EditExamDescriptor editExamDescriptor) { + requireNonNull(editExamDescriptor); + Module updatedModule = editExamDescriptor.getModule().orElse(module); + ExamDescription updatedDescription = editExamDescriptor.getDescription().orElse(examDescription); + ExamDate updatedExamDate = editExamDescriptor.getExamDate().orElse(examDate); + + if (!module.isSameModule(updatedModule)) { + return new Exam(updatedModule, updatedDescription, updatedExamDate); + } + return new Exam(updatedModule, updatedDescription, updatedExamDate, totalNumOfTasks, numOfCompletedTasks); + } + + /** + * Creates and returns a {@code Exam} with the details of {@code this} + * edited with {@code newModule}, {@code newDescription}, {@code newExamDate}. + */ + public Exam edit(Module newModule, ExamDescription newDescription, ExamDate newExamDate) { + requireAnyNonNull(newModule, newDescription, newExamDate); + Module updatedModule = module; + ExamDescription updatedDescription = examDescription; + ExamDate updatedExamDate = examDate; + if (newModule != null) { + updatedModule = newModule; + } + if (newDescription != null) { + updatedDescription = newDescription; + } + if (newExamDate != null) { + updatedExamDate = newExamDate; + } + if (!module.isSameModule(updatedModule)) { + return new Exam(updatedModule, updatedDescription, updatedExamDate); + } + return new Exam(updatedModule, updatedDescription, updatedExamDate, totalNumOfTasks, numOfCompletedTasks); + } + + /** + * Checks whether the two exams have the exact same fields. + * + * @param otherExam The other exam being compared against. + * @return true if the two Exam objects have the same module, exam description, exam date, number of completed tasks + * and total number of tasks. + */ + public boolean hasAllSameFields(Exam otherExam) { + requireNonNull(otherExam); + return this.module.equals(otherExam.module) + && this.examDescription.equals(otherExam.examDescription) + && this.examDate.equals(otherExam.examDate) + && this.numOfCompletedTasks == otherExam.numOfCompletedTasks + && this.totalNumOfTasks == otherExam.totalNumOfTasks; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Exam)) { + return false; + } + + Exam otherExam = (Exam) other; + return otherExam.getDescription().equals(getDescription()) + && otherExam.getModule().equals(getModule()) + && otherExam.getExamDate().equals(getExamDate()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Module: ") + .append(getModule()) + .append("; ExamDescription: ") + .append(getDescription()) + .append("; ExamDate: ") + .append(getExamDate()); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/exam/ExamDate.java b/src/main/java/seedu/address/model/exam/ExamDate.java new file mode 100644 index 00000000000..4496aea1cd0 --- /dev/null +++ b/src/main/java/seedu/address/model/exam/ExamDate.java @@ -0,0 +1,135 @@ +package seedu.address.model.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + + +/** + * ExamDate class represents the date of the exam. + */ +public class ExamDate { + public static final String DATE_CONSTRAINTS = + "Exam Date should be in the format DD-MM-YYYY and a valid date. DD should be between " + + "1 and 31(both inclusive)\nand MM " + + "should be between 1 and 12(both inclusive)"; + public static final String DATE_FORMAT_CONSTRAINTS = + "Exam Date should be in the format DD-MM-YYYY. DD should be between " + + "1 and 31(both inclusive)\nand MM " + + "should be between 1 and 12(both inclusive)"; + public static final String NOT_A_PAST_DATE_CONSTRAINTS = + "Exam Date should not be earlier than today's date."; + public static final String VALID_DATE_CONSTRAINTS = + "Exam Date should be a valid date"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("dd-MM-uuuu").withResolverStyle(ResolverStyle.STRICT); + public final String examDate; + + + /** + * Constructs a {@code ExamDate}. + * + * @param date A valid exam date. + */ + public ExamDate(String date) { + requireNonNull(date); + checkArgument(isExistingDate(date), DATE_CONSTRAINTS); + checkArgument(isCorrectDateFormat(date), DATE_CONSTRAINTS); + examDate = date; + } + + + /** + * Checks if the format given for the date is valid. + * + * @param date The date provided. + * @return true if the date has the correct format; else return false. + */ + public static boolean isValidDateFormat(String date) { + try { + LocalDate.parse(date, DATE_TIME_FORMATTER); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + public static boolean isValidDate(String date) { + return isCorrectDateFormat(date) && isExistingDate(date) && isNotAPastDate(date); + } + + /** + * Checks if format is in DD-MM-YYYY where DD is between 1 to 31(both inclusive), + * MM is between 1 to 12(both inclusive), yyyy is between 0 to 9999(both inclusive). + * @param date The date provided. + * @return true if date is in DD-MM-YYYY format. + */ + public static boolean isCorrectDateFormat(String date) { + try { + String[] a = date.split("-"); + Integer days = Integer.parseInt(a[0]); + Integer month = Integer.parseInt(a[1]); + Integer year = Integer.parseInt(a[2]); + if (year >= 0000 && year <= 9999 && month >= 01 && month + <= 12 && days >= 01 && days <= 31 && isValidDateLength(date)) { + return true; + } else { + return false; + } + } catch (NumberFormatException | ArrayIndexOutOfBoundsException ex) { + return false; + } + } + + + public static boolean isValidDateLength(String date) { + return date.length() == 10; + } + + /** + * Returns true if date inputted exists. + * @param date A String that represents a date that is in the DD-MM-YYYY format. + * @return true if date exist, otherwise false. + */ + public static boolean isExistingDate(String date) { + try { + LocalDate.parse(date, DATE_TIME_FORMATTER).format(DateTimeFormatter.ofPattern("dd MMM yyyy")); + return true; + } catch (DateTimeException e) { + return false; + } + } + + /** + * Returns true if date inputted is not a date before the current date. + * @param date A String that represents a date that is in the DD-MM-YYYY format. + * @return true if date is not a past date, otherwise false. + */ + public static boolean isNotAPastDate(String date) { + LocalDate d = LocalDate.parse(date, DATE_TIME_FORMATTER); + return d.compareTo(LocalDate.now()) >= 0; + } + + + @Override + public String toString() { + return examDate; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExamDate // instanceof handles nulls + && examDate.equals(((ExamDate) other).examDate)); // state check + } + + @Override + public int hashCode() { + return examDate.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/exam/ExamDescription.java b/src/main/java/seedu/address/model/exam/ExamDescription.java new file mode 100644 index 00000000000..ce8955a0245 --- /dev/null +++ b/src/main/java/seedu/address/model/exam/ExamDescription.java @@ -0,0 +1,49 @@ +package seedu.address.model.exam; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * ExamDescription class represents the description of the exam. + */ +public class ExamDescription { + public static final String DESCRIPTION_CONSTRAINTS = + "The description of the exam should not be empty"; + + public final String description; + + /** + * The constructor of the ExamDescription class. Sets the + * description of the class. + * + * @param description The description of the class. + */ + public ExamDescription(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), DESCRIPTION_CONSTRAINTS); + this.description = description; + } + + public static boolean isValidDescription(String description) { + return description.strip().length() > 0; + } + + @Override + public boolean equals(Object otherDescription) { + return otherDescription == this || (otherDescription instanceof ExamDescription + && description.equalsIgnoreCase((((ExamDescription) otherDescription).description))); + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + @Override + public String toString() { + return description; + } +} + + diff --git a/src/main/java/seedu/address/model/exam/exceptions/DuplicateExamException.java b/src/main/java/seedu/address/model/exam/exceptions/DuplicateExamException.java new file mode 100644 index 00000000000..3bebf72bdd2 --- /dev/null +++ b/src/main/java/seedu/address/model/exam/exceptions/DuplicateExamException.java @@ -0,0 +1,11 @@ +package seedu.address.model.exam.exceptions; + +/** + * Signals that the operation will result in duplicate Exams (Exams are considered duplicates if they have the same + * module and exam description and exam date). + */ +public class DuplicateExamException extends RuntimeException { + public DuplicateExamException() { + super("Operation would result in duplicate exams."); + } +} diff --git a/src/main/java/seedu/address/model/exam/exceptions/ExamIdentityModifiedException.java b/src/main/java/seedu/address/model/exam/exceptions/ExamIdentityModifiedException.java new file mode 100644 index 00000000000..abd8a9dc259 --- /dev/null +++ b/src/main/java/seedu/address/model/exam/exceptions/ExamIdentityModifiedException.java @@ -0,0 +1,10 @@ +package seedu.address.model.exam.exceptions; + +/** + * Signals that the operation will modify the identity of the exam. + */ +public class ExamIdentityModifiedException extends RuntimeException { + public ExamIdentityModifiedException() { + super("Operation would change exam identity"); + } +} diff --git a/src/main/java/seedu/address/model/exam/exceptions/ExamNotFoundException.java b/src/main/java/seedu/address/model/exam/exceptions/ExamNotFoundException.java new file mode 100644 index 00000000000..5244ab6ade4 --- /dev/null +++ b/src/main/java/seedu/address/model/exam/exceptions/ExamNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.exam.exceptions; + +/** + * Signals that the operation is unable to find the specified exam. + */ +public class ExamNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/exam/exceptions/NoLinkedTaskForExamException.java b/src/main/java/seedu/address/model/exam/exceptions/NoLinkedTaskForExamException.java new file mode 100644 index 00000000000..004bd52e29f --- /dev/null +++ b/src/main/java/seedu/address/model/exam/exceptions/NoLinkedTaskForExamException.java @@ -0,0 +1,10 @@ +package seedu.address.model.exam.exceptions; + +/** + * Signals that the operation is unable to find linked tasks for the specified exam. + */ +public class NoLinkedTaskForExamException extends RuntimeException { + public NoLinkedTaskForExamException() { + super("Operation cannot find any linked tasks for the specified exam"); + } +} diff --git a/src/main/java/seedu/address/model/module/DistinctModuleList.java b/src/main/java/seedu/address/model/module/DistinctModuleList.java new file mode 100644 index 00000000000..9d8d26ec0ea --- /dev/null +++ b/src/main/java/seedu/address/model/module/DistinctModuleList.java @@ -0,0 +1,163 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.module.exceptions.DuplicateModuleException; +import seedu.address.model.module.exceptions.ModuleNotFoundException; +import seedu.address.model.task.DistinctTaskList; + +/** + * This class represents a list which contains Module objects which are distinct from + * each other. Module Objects are distinct from each other when they have different module + * codes and different module names. + */ +public class DistinctModuleList implements Iterable { + + private final ObservableList moduleList = FXCollections.observableArrayList(); + private final ObservableList unmodifiableModuleList = FXCollections + .unmodifiableObservableList(moduleList); + + /** + * Checks whether the moduleList contains a Module with the same module code. + * + * @param module The Module object being compared against. + * @return true if the Module object has the same module code as one of the + * Module objects in the list; else return false + */ + public boolean containsModule(Module module) { + requireNonNull(module); + return moduleList.stream().anyMatch(module::isSameModule); + } + + /** + * Adds modules to the moduleList. + * + * @param moduleAdded The module which is being added. + */ + public void addModule(Module moduleAdded) { + requireNonNull(moduleAdded); + if (containsModule(moduleAdded)) { + throw new DuplicateModuleException(); + } + moduleList.add(moduleAdded); + } + + public void setModules(List modules) { + requireAllNonNull(modules); + moduleList.setAll(modules); + } + + /** + * Counts the number of tasks in {@code tasks} that belong to {@code module}, + * and updates this number in {@code module}. + * {@code module} must exist in the module list. + * + * @param module The module to check for number of tasks. + * @param tasks the list of tasks to check with the module. + */ + public void updateTotalNumOfTasks(Module module, DistinctTaskList tasks) { + requireAllNonNull(module, tasks); + int totalNumOfTasks = tasks.getTotalNumOfModuleTasks(module); + + int index = moduleList.indexOf(module); + if (index == -1) { + throw new ModuleNotFoundException(); + } + + Module moduleToEdit = moduleList.get(index); + Module updatedModule = moduleToEdit.setTotalNumOfTasks(totalNumOfTasks); + moduleList.set(index, updatedModule); + } + + /** + * Counts the number of completed tasks in {@code tasks} that belong to {@code module}, + * and updates this number in {@code module}. + * {@code module} must exist in the module list. + * @param module The module to check for number of completed tasks. + * @param tasks the list of tasks to check with the module. + */ + public void updateNumOfCompletedTasks(Module module, DistinctTaskList tasks) { + requireAllNonNull(module, tasks); + int numOfCompletedTasks = tasks.getNumOfCompletedModuleTasks(module); + + int index = moduleList.indexOf(module); + if (index == -1) { + throw new ModuleNotFoundException(); + } + Module moduleToEdit = moduleList.get(index); + Module updatedModule = moduleToEdit.setNumOfCompletedTasks(numOfCompletedTasks); + moduleList.set(index, updatedModule); + } + + /** + * Resets number of tasks and number of completed tasks of all modules to 0. + */ + public void resetAllTaskCount() { + moduleList.forEach(module -> { + int index = moduleList.indexOf(module); + Module updatedModule = module.setNumOfCompletedTasks(0); + updatedModule = updatedModule.setTotalNumOfTasks(0); + moduleList.set(index, updatedModule); + }); + } + + /** + * Removes the equivalent module from the module list. + * The module must exist in the list. + */ + public void remove(Module toRemove) { + requireNonNull(toRemove); + if (!moduleList.remove(toRemove)) { + throw new ModuleNotFoundException(); + } + } + + /** + * Replaces the given task {@code target} with {@code editedModule}. + * {@code target} must exist in the module list. + * + * @throws DuplicateModuleException if module {@code editedModule} is the same as another module + * in the list (other than {@code target}). + * @throws ModuleNotFoundException if module {@code target} does not exist in the list. + */ + public void replaceModule(Module target, Module editedModule) throws DuplicateModuleException, + ModuleNotFoundException { + requireAllNonNull(target, editedModule); + + int index = moduleList.indexOf(target); + if (index == -1) { + throw new ModuleNotFoundException(); + } + if (containsModule(editedModule) && !editedModule.isSameModule(target)) { + throw new DuplicateModuleException(); + } + moduleList.set(index, editedModule); + } + + @Override + public Iterator iterator() { + return moduleList.iterator(); + } + + @Override + public int hashCode() { + return moduleList.hashCode(); + } + + @Override + public boolean equals(Object otherMod) { + return otherMod == this + || (otherMod instanceof DistinctModuleList + && moduleList.equals(((DistinctModuleList) otherMod).moduleList)); + } + + public ObservableList getUnmodifiableModuleList() { + return unmodifiableModuleList; + } +} diff --git a/src/main/java/seedu/address/model/module/Module.java b/src/main/java/seedu/address/model/module/Module.java new file mode 100644 index 00000000000..f1c25aaa1f3 --- /dev/null +++ b/src/main/java/seedu/address/model/module/Module.java @@ -0,0 +1,232 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.model.module.exceptions.NullModuleCodeException; + +/** + * Module class represents a Module being taken. + */ +public class Module implements Comparable { + + public static final String MESSAGE_NO_TASKS_FOR_MODULE = "You have no tasks for this module"; + + private final ModuleCode moduleCode; + private int totalNumOfTasks; + private int numOfCompletedTasks; + private final ModuleName moduleName; + private final ModuleCredit moduleCredit; + + /** + * Constructor of the Module class. + * Module code must be present. + * + * @param moduleCode The module code of the module. + * @param moduleName The module name of the module. + * @param moduleCredit The module credit of the module. + */ + public Module(ModuleCode moduleCode, ModuleName moduleName, ModuleCredit moduleCredit) { + requireAllNonNull(moduleCode, moduleName, moduleCredit); + this.moduleCode = moduleCode; + this.moduleName = moduleName; + this.moduleCredit = moduleCredit; + this.totalNumOfTasks = 0; + this.numOfCompletedTasks = 0; + } + + /** + * Constructor of the Module class. + * Module code must be present. + * + * @param moduleCode The module code of the module. + */ + public Module(ModuleCode moduleCode) { + requireNonNull(moduleCode); + this.moduleCode = moduleCode; + this.moduleName = null; + this.moduleCredit = null; + this.numOfCompletedTasks = 0; + this.totalNumOfTasks = 0; + } + + /** + * Constructor of the Module class. + * Module code, number of completed tasks and total number of tasks must be specified. + * + * @param moduleCode The module code of the module. + * @param moduleName The name of the module. + * @param moduleCredit The number of module credit of the module. + * @param numOfCompletedTasks The number of completed tasks the module has. + * @param totalNumOfTasks The total number of tasks the module has. + */ + private Module(ModuleCode moduleCode, ModuleName moduleName, ModuleCredit moduleCredit, + int numOfCompletedTasks, int totalNumOfTasks) { + requireAllNonNull(moduleCode, moduleName, moduleCredit); + this.moduleCode = moduleCode; + this.moduleName = moduleName; + this.moduleCredit = moduleCredit; + this.numOfCompletedTasks = numOfCompletedTasks; + this.totalNumOfTasks = totalNumOfTasks; + } + + public ModuleCode getModuleCode() { + return moduleCode; + } + + public ModuleName getModuleName() { + return moduleName; + } + + public ModuleCredit getModuleCredit() { + return moduleCredit; + } + + public int getNumOfCompletedTasks() { + return numOfCompletedTasks; + } + + public int getTotalNumOfTasks() { + return totalNumOfTasks; + } + + + /** + * Checks whether two modules have the same module code. + * + * @param otherModule The other module being compared against. + * @return true if the two Module objects have the same module code; + * else return false + */ + public boolean isSameModule(Module otherModule) { + return this.equals(otherModule); + } + + /** + * Checks whether the two modules has the exact same fields. + * + * @param otherModule The other module being compared against. + * @return true if the two Module objects have the same module code, module name and module credit; + * else return false + */ + public boolean hasAllSameFields(Module otherModule) { + return hasSameModuleCode(otherModule) && hasSameModuleName(otherModule) && hasSameModuleCredit(otherModule); + } + + /** + * Checks whether the two modules has the same module code. + * + * @param otherModule The other module being compared against. + * @return true if the two Module objects have the same module code; + * else return false + * @throws NullModuleCodeException if the current module or other module {@code otherModule} + * has a null module code field. + */ + private boolean hasSameModuleCode(Module otherModule) throws NullModuleCodeException { + requireNonNull(otherModule); + if (moduleCode == null || otherModule.moduleCode == null) { + throw new NullModuleCodeException(); + } + return moduleCode.equals(otherModule.moduleCode); + } + + /** + * Checks whether the two modules has the same module name. + * + * @param otherModule The other module being compared against. + * @return true if the two Module objects have the same module name; + * else return false + */ + private boolean hasSameModuleName(Module otherModule) { + if (moduleName != null && otherModule.moduleName != null) { + return moduleName.equals(otherModule.moduleName); + } + return false; + } + + /** + * Checks whether the two modules has the same module credit. + * + * @param otherModule The other module being compared against. + * @return true if the two Module objects have the same module credit; + * else return false + */ + private boolean hasSameModuleCredit(Module otherModule) { + if (moduleCredit != null && otherModule.moduleCredit != null) { + return moduleCredit.equals(otherModule.moduleCredit); + } + return false; + } + + /** + * Creates and returns a {@code Module} with the details of {@code this} + * edited with {@code editModuleDescriptor}. + */ + public Module edit(EditModuleCommand.EditModuleDescriptor editModuleDescriptor) { + requireNonNull(editModuleDescriptor); + + ModuleCode updatedModuleCode = editModuleDescriptor.getModuleCode().orElse(this.moduleCode); + ModuleName updatedModuleName = editModuleDescriptor.getModuleName().orElse(this.moduleName); + ModuleCredit updatedModuleCredit = editModuleDescriptor.getModuleCredit().orElse(this.moduleCredit); + + return new Module(updatedModuleCode, updatedModuleName, updatedModuleCredit, + this.numOfCompletedTasks, this.totalNumOfTasks); + } + + public Module setTotalNumOfTasks(Integer numOfTasks) { + return new Module(this.moduleCode, this.moduleName, this.moduleCredit, this.numOfCompletedTasks, numOfTasks); + } + + public Module setNumOfCompletedTasks(Integer numOfCompletedTasks) { + return new Module(this.moduleCode, this.moduleName, this.moduleCredit, + numOfCompletedTasks, this.totalNumOfTasks); + } + + /** + * Returns the percentage of tasks completed for the module. + */ + public double getPercentageCompleted() { + return (double) numOfCompletedTasks / (double) totalNumOfTasks; + } + + /** + * Returns a string representation of the number of completed tasks and number of total tasks. + */ + public String generateProgressMessage() { + if (totalNumOfTasks == 0) { + return MESSAGE_NO_TASKS_FOR_MODULE; + } else { + return numOfCompletedTasks + " / " + totalNumOfTasks + " task(s) completed"; + } + } + + public boolean hasTasks() { + return totalNumOfTasks > 0; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Module)) { + return false; + } + + Module otherModule = (Module) other; + + return hasSameModuleCode(otherModule); + } + + @Override + public String toString() { + return getModuleCode().toString(); + } + + @Override + public int compareTo(Module mod) { + return this.getModuleCode().moduleCode.compareTo(mod.getModuleCode().moduleCode); + } +} diff --git a/src/main/java/seedu/address/model/module/ModuleCode.java b/src/main/java/seedu/address/model/module/ModuleCode.java new file mode 100644 index 00000000000..7ca86e6db0b --- /dev/null +++ b/src/main/java/seedu/address/model/module/ModuleCode.java @@ -0,0 +1,49 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * ModuleCode represents the module code of the module. + */ +public class ModuleCode { + public static final String MODULE_CODE_CONSTRAINTS = "Module code should be at least 6 " + + "characters long, with first two characters " + + "being alphabetical characters and the rest of the characters being alphanumeric characters"; + public static final String VALIDATION_REGEX = "^[A-Za-z]{2}[A-Za-z0-9]*"; + + public final String moduleCode; + + /** + * The constructor of the ModuleCode class which stores + * the module code of the module. + * + * @param moduleCode The module code of the module. + */ + public ModuleCode(String moduleCode) { + requireNonNull(moduleCode); + checkArgument(isValidModuleCode(moduleCode), MODULE_CODE_CONSTRAINTS); + this.moduleCode = moduleCode; + } + + public static boolean isValidModuleCode(String testModuleCode) { + return testModuleCode.matches(VALIDATION_REGEX) && testModuleCode.length() >= 6; + } + + @Override + public boolean equals(Object obj) { + return obj == this || (obj instanceof ModuleCode + && moduleCode.equalsIgnoreCase((((ModuleCode) obj).moduleCode))); + } + + @Override + public String toString() { + return moduleCode; + } + + @Override + public int hashCode() { + return moduleCode.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicate.java new file mode 100644 index 00000000000..2ab23c7ef1a --- /dev/null +++ b/src/main/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.module; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + + +/** + * Tests that a {@code Module}'s {@code ModuleCode} matches any of the keywords given. + */ +public class ModuleCodeContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public ModuleCodeContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Module module) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWord(module.getModuleCode() + .moduleCode.toLowerCase(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleCodeContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ModuleCodeContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/module/ModuleCredit.java b/src/main/java/seedu/address/model/module/ModuleCredit.java new file mode 100644 index 00000000000..abd62047916 --- /dev/null +++ b/src/main/java/seedu/address/model/module/ModuleCredit.java @@ -0,0 +1,49 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * ModuleCredit class represents the number of module credit of the module. + */ +public class ModuleCredit { + public static final String MODULE_CREDIT_CONSTRAINTS = + "The module credit of a module should be a integer from 0 to 45"; + + public final int moduleCredit; + + /** + * The constructor of the ModuleCredit class. Sets the + * number of module credit of the module. + * + * @param moduleCredit The number of module credit the class has. + */ + public ModuleCredit(int moduleCredit) { + requireNonNull(moduleCredit); + checkArgument(isValidModuleCredit(moduleCredit), MODULE_CREDIT_CONSTRAINTS); + this.moduleCredit = moduleCredit; + } + + public static boolean isValidModuleCredit(int moduleCredit) { + return moduleCredit >= 0 && moduleCredit <= 45; + } + + @Override + public boolean equals(Object other) { + return other == this || (other instanceof ModuleCredit + && moduleCredit == ((ModuleCredit) other).moduleCredit); + } + + @Override + public int hashCode() { + return moduleCredit; + } + + @Override + public String toString() { + return Integer.toString(moduleCredit); + } +} + + + diff --git a/src/main/java/seedu/address/model/module/ModuleName.java b/src/main/java/seedu/address/model/module/ModuleName.java new file mode 100644 index 00000000000..8d5abfe8904 --- /dev/null +++ b/src/main/java/seedu/address/model/module/ModuleName.java @@ -0,0 +1,48 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * ModuleName class represents the name of the module. + */ +public class ModuleName { + public static final String MODULE_NAME_CONSTRAINTS = + "The name of the module should not be empty."; + + public final String moduleName; + + /** + * The constructor of the ModuleName class. Sets the + * string name of the module. + * + * @param moduleName The string name of the module. + */ + public ModuleName(String moduleName) { + requireNonNull(moduleName); + checkArgument(isValidModuleName(moduleName), MODULE_NAME_CONSTRAINTS); + this.moduleName = moduleName; + } + + public static boolean isValidModuleName(String moduleName) { + return moduleName.strip().length() > 0; + } + + @Override + public boolean equals(Object other) { + return other == this || (other instanceof ModuleName + && moduleName.equalsIgnoreCase((((ModuleName) other).moduleName))); + } + + @Override + public int hashCode() { + return moduleName.hashCode(); + } + + @Override + public String toString() { + return moduleName; + } +} + + diff --git a/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java b/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java new file mode 100644 index 00000000000..3faa3293759 --- /dev/null +++ b/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java @@ -0,0 +1,11 @@ +package seedu.address.model.module.exceptions; + +/** + * DuplicateModuleException class represents an exception that there + * are duplicate modules being added. + */ +public class DuplicateModuleException extends RuntimeException { + public DuplicateModuleException() { + super("This would result in duplicate modules being added."); + } +} diff --git a/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java b/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java new file mode 100644 index 00000000000..52abdad64ea --- /dev/null +++ b/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.module.exceptions; + +/** + * Signals that the operation is unable to find the specified module. + */ +public class ModuleNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/module/exceptions/NullModuleCodeException.java b/src/main/java/seedu/address/model/module/exceptions/NullModuleCodeException.java new file mode 100644 index 00000000000..3adeb07021b --- /dev/null +++ b/src/main/java/seedu/address/model/module/exceptions/NullModuleCodeException.java @@ -0,0 +1,10 @@ +package seedu.address.model.module.exceptions; + +/** + * Signals that a module object was instantiated without a module code field. + */ +public class NullModuleCodeException extends RuntimeException { + public NullModuleCodeException() { + super("A module should have been instantiated with a ModuleCode field."); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index f866e7133de..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "+_.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " - + "characters.\n" - + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " - + "separated by periods.\n" - + "The domain name must:\n" - + " - end with a domain label at least 2 characters long\n" - + " - have each domain label start and end with alphanumeric characters\n" - + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; - // alphanumeric and special characters - private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore - private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" - + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE - + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars - private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 79244d71cf7..00000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - - public final String fullName; - - /** - * Constructs a {@code Name}. - * - * @param name A valid name. - */ - public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; - } - - /** - * Returns true if a given string is a valid name. - */ - public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 8ff1d83fe89..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,123 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/DeadlineTag.java b/src/main/java/seedu/address/model/tag/DeadlineTag.java new file mode 100644 index 00000000000..8976df1478f --- /dev/null +++ b/src/main/java/seedu/address/model/tag/DeadlineTag.java @@ -0,0 +1,80 @@ +package seedu.address.model.tag; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Deadline Tag represents a tag which contains the deadline for the task + */ +public class DeadlineTag implements Comparable { + public static final String DEADLINE_TAG_FORMAT_CONSTRAINTS = + "The deadline should be in the format DD-MM-YYYY. DD should be between " + + "1 and 31(both inclusive)\nand MM " + + "should be between 1 and 12(both inclusive)"; + public static final String DEADLINE_TAG_INVALID_DATE = + "The deadline should be a valid date"; + public static final String DEADLINE_TAG_DATE_HAS_PASSED = + "The deadline should not be earlier than today's date."; + private static final String dateFormat = "^(?:0[1-9]|[1-2][0-9]|3[0-1])[-]" + + "(?:0[1-9]|1[0-2])[-][0-9]{4}"; + private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + + public final LocalDate deadline; + + /** + * Constructor of the DeadlineTag. Sets the deadline of the + * tag. + * + * @param deadline The deadline of the tag. + */ + public DeadlineTag(LocalDate deadline) { + requireNonNull(deadline); + this.deadline = deadline; + } + + /** + * Checks that the format of the date is correct + * + * @param date The date string being checked + * @return true if the format of string is correct; else return false; + */ + public static boolean checkDateFormat(String date) { + return date.matches(dateFormat); + } + + /** + * Checks whether the deadline given is valid. + * + * @param testDateAdded The date being checked for validity. + * @return true if the date is valid; else return false. + */ + public static boolean isValidDeadline(LocalDate testDateAdded) { + if (testDateAdded == null) { + return false; + } + return testDateAdded.compareTo(LocalDate.now()) >= 0; + } + + @Override + public boolean equals(Object otherDate) { + return otherDate == this || (otherDate instanceof DeadlineTag + && deadline.equals(((DeadlineTag) otherDate).deadline)); + } + + @Override + public String toString() { + return deadline.format(dtf); + } + + @Override + public int hashCode() { + return deadline.hashCode(); + } + + @Override + public int compareTo(DeadlineTag otherDeadlineTag) { + return this.deadline.compareTo(otherDeadlineTag.deadline); + } +} diff --git a/src/main/java/seedu/address/model/tag/PriorityTag.java b/src/main/java/seedu/address/model/tag/PriorityTag.java new file mode 100644 index 00000000000..dd31e2a4e82 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/PriorityTag.java @@ -0,0 +1,84 @@ +package seedu.address.model.tag; + + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Priority Tag represents a tag which contains the priority status of the task. + */ +public class PriorityTag implements Comparable { + public static final String PRIORITY_TAG_CONSTRAINTS = + "Priority status of tag should only be HIGH, MEDIUM or LOW"; + public static final String VALIDATION_REGEX = "high|low|medium"; + public final String status; + + /** + * Constructor of the priority tag. Sets the priority + * status to the tag. + * + * @param status The priority status of the tag. + */ + public PriorityTag(String status) { + requireNonNull(status); + checkArgument(isValidTag(status), PRIORITY_TAG_CONSTRAINTS); + this.status = status; + } + + /** + * Checks whether the priority status in the tag is valid. + * + * @param testTag The tag that is being checked. + * @return true if the priority status is valid; else return false. + */ + public static boolean isValidTag(String testTag) { + if (testTag == null) { + return false; + } + String testTagStatus = testTag.toLowerCase(); + return testTagStatus.matches(VALIDATION_REGEX); + } + + @Override + public int compareTo(PriorityTag otherPriorityTag) { + if (this.status.equalsIgnoreCase("high")) { + if (otherPriorityTag.status.equalsIgnoreCase("high")) { + return 0; + } + return -1; + } + if (this.status.equalsIgnoreCase("medium")) { + if (otherPriorityTag.status.equalsIgnoreCase("high")) { + return 1; + } + if (otherPriorityTag.status.equalsIgnoreCase("medium")) { + return 0; + } + return -1; + } + if (this.status.equalsIgnoreCase("low")) { + if (otherPriorityTag.status.equalsIgnoreCase("low")) { + return 0; + } + return 1; + } + return -1; + } + + @Override + public boolean equals(Object otherTag) { + return otherTag == this || (otherTag instanceof PriorityTag + && status.equalsIgnoreCase((((PriorityTag) otherTag).status))); + } + + @Override + public String toString() { + return status; + } + + @Override + public int hashCode() { + return status.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index b0ea7e7dad7..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/tag/exceptions/BothTagsCannotBeNullException.java b/src/main/java/seedu/address/model/tag/exceptions/BothTagsCannotBeNullException.java new file mode 100644 index 00000000000..fe4a754897f --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/BothTagsCannotBeNullException.java @@ -0,0 +1,11 @@ +package seedu.address.model.tag.exceptions; + +/** + * BothTagsCannotBeNullException is an exception which is thrown when both priority tag + * and the deadline tag are null in AddTagCommand method + */ +public class BothTagsCannotBeNullException extends RuntimeException { + public BothTagsCannotBeNullException() { + super("Both the priority tag and the deadline tag cannot be null!"); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagAlreadyExistsException.java b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagAlreadyExistsException.java new file mode 100644 index 00000000000..1a524970cfe --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagAlreadyExistsException.java @@ -0,0 +1,11 @@ +package seedu.address.model.tag.exceptions; + +/** + * DeadlineTagAlreadyExistsException is an exception which is + * thrown when the task already contains the deadline. + */ +public class DeadlineTagAlreadyExistsException extends RuntimeException { + public DeadlineTagAlreadyExistsException() { + super("There is already a deadline for the task."); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagDoesNotExistException.java b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagDoesNotExistException.java new file mode 100644 index 00000000000..4af61e083b0 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagDoesNotExistException.java @@ -0,0 +1,15 @@ +package seedu.address.model.tag.exceptions; + +/** + * DeadlineTagDoesNotExist is an exception which is thrown when the deadline tag + * does not exist. + */ +public class DeadlineTagDoesNotExistException extends RuntimeException { + /** + * The constructor of DeadlineTagDoesNotExist. Sets the message that + * the deadline tag does not exist. + */ + public DeadlineTagDoesNotExistException() { + super("The deadline tag does not exist."); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagUnchangedException.java b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagUnchangedException.java new file mode 100644 index 00000000000..0b107619bca --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagUnchangedException.java @@ -0,0 +1,16 @@ +package seedu.address.model.tag.exceptions; + +/** + * DeadlineTagUnchangedException is an exception which is thrown when the + * newly provided deadline tag is the same as the existing deadline tag. + */ +public class DeadlineTagUnchangedException extends RuntimeException { + /** + * The constructor of the DeadlineTagUnchangedException. Sets the message for the + * exception. + */ + public DeadlineTagUnchangedException() { + super("The deadline provided is" + + " the same as the current deadline for the task."); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/PriorityTagAlreadyExistsException.java b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagAlreadyExistsException.java new file mode 100644 index 00000000000..ce27c39b89b --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagAlreadyExistsException.java @@ -0,0 +1,11 @@ +package seedu.address.model.tag.exceptions; + +/** + * PriorityTagAlreadyExistsException is an exception which is + * thrown when the task already contains the priority status. + */ +public class PriorityTagAlreadyExistsException extends RuntimeException { + public PriorityTagAlreadyExistsException() { + super("The priority status already exists for the task."); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/PriorityTagDoesNotExistException.java b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagDoesNotExistException.java new file mode 100644 index 00000000000..7b6efe7d087 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagDoesNotExistException.java @@ -0,0 +1,15 @@ +package seedu.address.model.tag.exceptions; + +/** + * PriorityTagDoesNotExistException is an exception which is thrown when the priority + * tag does not exist. + */ +public class PriorityTagDoesNotExistException extends RuntimeException { + /** + * The constructor of PriorityTagDoesNotExist. Sets the message of priority tag + * does not exist. + */ + public PriorityTagDoesNotExistException() { + super("The priority tag does not exist."); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/PriorityTagUnchangedException.java b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagUnchangedException.java new file mode 100644 index 00000000000..192c1577810 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagUnchangedException.java @@ -0,0 +1,16 @@ +package seedu.address.model.tag.exceptions; + +/** + * PriorityTagUnchangedException is an exception which is thrown when the + * newly provided priority tag is the same as the current priority tag. + */ +public class PriorityTagUnchangedException extends RuntimeException { + /** + * The constructor of the PriorityTagUnchangedException. Sets the message for the + * exception. + */ + public PriorityTagUnchangedException() { + super("The priority status provided is " + + "the same as the current priority status for the task."); + } +} diff --git a/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e0b075f1b1a --- /dev/null +++ b/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.task; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +/** + * Tests that a {@code Task}'s {@code TaskDescription} matches any of the keywords given. + */ +public class DescriptionContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public DescriptionContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWord(task.getDescription() + .description.toLowerCase(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DescriptionContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((DescriptionContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/task/DistinctTaskList.java b/src/main/java/seedu/address/model/task/DistinctTaskList.java new file mode 100644 index 00000000000..0381f7004d1 --- /dev/null +++ b/src/main/java/seedu/address/model/task/DistinctTaskList.java @@ -0,0 +1,268 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.Criteria; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; +import seedu.address.model.task.exceptions.WrongTaskModifiedException; + +/** + * This class represents a list which contains Tasks objects which are distinct from + * each other. Tasks are distinct from each other when they have different descriptions and + * module codes. + */ +public class DistinctTaskList implements Iterable { + + public final ObservableList taskList = FXCollections.observableArrayList(); + public final ObservableList unmodifiableTaskList = FXCollections + .unmodifiableObservableList(taskList); + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return taskList.stream().anyMatch(toCheck::isSameTask); + } + + /** + * Returns true if the list contains a task with an equivalent module as the given argument. + */ + public boolean containsModule(Module toCheck) { + requireNonNull(toCheck); + return taskList.stream().map(Task::getModule).anyMatch(toCheck::isSameModule); + } + + /** + * Adds the task to the taskList. + * The task must not already exist in the list. + * + * @param taskAdded The task to be added. + */ + public void addTask(Task taskAdded) { + requireNonNull(taskAdded); + //Might have to add address book as param to check if addressBook already has module + if (contains(taskAdded)) { + throw new DuplicateTaskException(); + } + taskList.add(taskAdded); + } + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the task list. + * If {@code isSameTask} is true, the task identity of {@code editedTask} should be the same as {@code target}. + * + * @param target the task to be replaced. + * @param editedTask the edited task to replace {@code target}. + * @param isSameTask true if {@code target} has the same task identity as {@code editedTask}, false otherwise. + * @throws DuplicateTaskException if {@code isSameTask} is false but task identity of {@code editedTask} + * is the same as another task in the list (other than {@code target}). + */ + public void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException { + requireAllNonNull(target, editedTask, isSameTask); + + int index = taskList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (isSameTask && !target.isSameTask(editedTask)) { + throw new WrongTaskModifiedException(); + } + + boolean isDuplicateTask = contains(editedTask) && !editedTask.isSameTask(target); + if (!isSameTask && isDuplicateTask) { + throw new DuplicateTaskException(); + } + + taskList.set(index, editedTask); + } + + /** + * Unlinks all tasks that are currently linked to {@code exam} and returns a list of these tasks. + * @param exam the exam for the tasks to be unlinked from + */ + public List unlinkTasksFromExam(Exam exam) { + requireNonNull(exam); + List matchedTasks = new ArrayList(); + taskList.forEach(task-> { + if (task.isLinked() && task.getExam().equals(exam)) { + Task unlinkedTask = task.unlinkTask(); + matchedTasks.add(unlinkedTask); + replaceTask(task, unlinkedTask, true); + } + }); + return matchedTasks; + } + + /** + * Returns true if {@code examToEdit} is linked to any task, otherwise false. + */ + public boolean isExamLinkedToTask(Exam exam) { + requireNonNull(exam); + for (int i = 0; i < taskList.size(); i++) { + if (taskList.get(i).isLinked() && taskList.get(i).getExam().equals(exam)) { + return true; + } + } + return false; + } + + /** + * Replaces task by changing its given exam field from {@code previousExam} + * to {@code newExam} for tasks that have their exam field as {@code previousExam}. + * @param previousExam The exam in the task's exam field. + * @param newExam The new exam which will replace the previous exam in the task's exam field. + */ + public void updateExamFieldForTask(Exam previousExam, Exam newExam) { + requireAllNonNull(previousExam, newExam); + taskList.forEach(task-> { + if (task.isLinked() && task.getExam().equals(previousExam)) { + Task editedTask = task.linkTask(newExam); + replaceTask(task, editedTask, true); + } + }); + } + + /** + * Replaces task by changing its given module field from {@code previousModule} + * to {@code newModule} for tasks that have their module field as {@code previousModule}. + * @param previousModule The module in the task's module field. + * @param newModule The new module which will replace the previous module in the task's module field. + */ + public void updateModuleFieldForTask(Module previousModule, Module newModule) { + requireAllNonNull(previousModule, newModule); + taskList.forEach(task-> { + if (task.getModule().equals(previousModule)) { + Task editedTask = task.editWithoutUnlinkingExam(newModule, null); + replaceTask(task, editedTask, false); + } + }); + } + + /** + * Links all tasks in {@code tasks} to {@code exam}. + * @param exam The exam to link to. + * @param tasks The list of tasks to link each task to {@code exam}. + */ + public void linkTasksToExam(Exam exam, List tasks) { + tasks.forEach(task-> { + Task linkedTask = task.linkTask(exam); + replaceTask(task, linkedTask, true); + }); + } + + /** + * Removes the equivalent task from the tasklist. + * The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!taskList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + /** + * Removes tasks that have their module field as {@code module} from the tasklist. + * @param module The module in the task's module field. + */ + public void deleteTasksWithModule(Module module) { + requireAllNonNull(module); + for (int i = 0; i < taskList.size(); i++) { + Task task = taskList.get(i); + if (task.getModule().equals(module)) { + remove(task); + --i; + } + } + } + + public int getNumOfCompletedModuleTasks(Module module) { + requireNonNull(module); + return (int) taskList.stream().filter(Task::isComplete).map(Task::getModule) + .filter(module::isSameModule).count(); + } + + public int getTotalNumOfModuleTasks(Module module) { + requireNonNull(module); + return (int) taskList.stream().map(Task::getModule).filter(module::isSameModule).count(); + } + + public int getNumOfCompletedExamTasks(Exam exam) { + requireNonNull(exam); + return (int) taskList.stream().filter(Task::isComplete).map(Task::getExam) + .filter(exam::isSameExam).count(); + } + + public int getTotalNumOfExamTasks(Exam exam) { + requireNonNull(exam); + return (int) taskList.stream().map(Task::getExam).filter(exam::isSameExam).count(); + } + + /** + * Sorts the tasks stored in the task list. + * + * @param criteria The criteria used for sorting. + */ + public void sortTasks(Criteria criteria) { + + //@@author dlimyy-reused + //Reused from https://stackoverflow.com/questions/51186174/ + //with slight modifications + switch (criteria.getCriteria().toLowerCase()) { + case "priority": + FXCollections.sort(taskList, Comparator.comparing(Task::getPriorityTag, + Comparator.nullsLast(Comparator.naturalOrder()))); + break; + case "deadline": + FXCollections.sort(taskList, Comparator.comparing(Task::getDeadlineTag, + Comparator.nullsLast(Comparator.naturalOrder()))); + break; + case "module": + FXCollections.sort(taskList, Comparator.comparing(Task::getModule, + Comparator.naturalOrder())); + break; + case "description": + FXCollections.sort(taskList, Comparator.comparing(Task::getDescription, + Comparator.naturalOrder())); + break; + default: + break; + } + //@@author + } + + @Override + public Iterator iterator() { + return taskList.iterator(); + } + + @Override + public boolean equals(Object otherTask) { + return otherTask == this + || (otherTask instanceof DistinctTaskList + && taskList.equals(((DistinctTaskList) otherTask).taskList)); + } + + public ObservableList getUnmodifiableTaskList() { + return unmodifiableTaskList; + } + + public void setTasks(List tasks) { + requireNonNull(tasks); + taskList.setAll(tasks); + } +} diff --git a/src/main/java/seedu/address/model/task/FilterPredicate.java b/src/main/java/seedu/address/model/task/FilterPredicate.java new file mode 100644 index 00000000000..6e104ededdf --- /dev/null +++ b/src/main/java/seedu/address/model/task/FilterPredicate.java @@ -0,0 +1,90 @@ +package seedu.address.model.task; + +import java.util.Optional; +import java.util.function.Predicate; + +import seedu.address.model.module.Module; + +/** + * Tests that a {@code Task} fulfils the given predicate. + */ +public class FilterPredicate implements Predicate { + private final Optional moduleToCheck; + private final Optional isCompleted; + private final Optional isLinked; + + /** + * Tests that a {@code Task} matches all the module and status constraints given. + * @param module check if task has same module code. + * @param isCompleted check if task has same completion status. + * @param isLinked check if task has same link status. + */ + public FilterPredicate(Optional module, Optional isCompleted, Optional isLinked) { + this.moduleToCheck = module; + this.isCompleted = isCompleted; + this.isLinked = isLinked; + } + + public Module getModuleToCheck() { + return moduleToCheck.get(); + } + + public boolean hasModuleToCheck() { + return moduleToCheck.isPresent(); + } + + public String getCompleteCondition() { + if (isCompleted.get()) { + return "Complete"; + } else { + return "Incomplete"; + } + } + + public String getLinkCondition() { + if (isLinked.get()) { + return "Linked"; + } else { + return "Unlinked"; + } + } + + @Override + public boolean test(Task task) { + boolean result = true; + if (moduleToCheck.isPresent()) { + result &= task.getModule().equals(moduleToCheck.get()); + } + if (isCompleted.isPresent()) { + result &= task.getStatus().isComplete() == isCompleted.get(); + } + if (isLinked.isPresent()) { + result &= task.isLinked() == isLinked.get(); + } + return result; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPredicate // instanceof handles nulls + && moduleToCheck.equals(((FilterPredicate) other).moduleToCheck) + && isCompleted.equals(((FilterPredicate) other).isCompleted) + && isLinked.equals(((FilterPredicate) other).isLinked)); // state check + } + + @Override + public String toString() { + String result = ""; + if (moduleToCheck.isPresent()) { + result += " Module: " + moduleToCheck.get(); + } + if (isCompleted.isPresent()) { + result += " " + getCompleteCondition(); + } + if (isLinked.isPresent()) { + result += " " + getLinkCondition(); + } + return result; + } +} diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java new file mode 100644 index 00000000000..59646b6f618 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Task.java @@ -0,0 +1,358 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAnyNonNull; + +import java.util.Objects; + +import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.tag.exceptions.DeadlineTagAlreadyExistsException; +import seedu.address.model.tag.exceptions.DeadlineTagDoesNotExistException; +import seedu.address.model.tag.exceptions.DeadlineTagUnchangedException; +import seedu.address.model.tag.exceptions.PriorityTagAlreadyExistsException; +import seedu.address.model.tag.exceptions.PriorityTagDoesNotExistException; +import seedu.address.model.tag.exceptions.PriorityTagUnchangedException; +import seedu.address.model.task.exceptions.TaskAlreadyMarkedException; +import seedu.address.model.task.exceptions.TaskAlreadyUnmarkedException; + +/** + * Task class represents a task which stores the module code and the + * description of the task. + */ +public class Task { + private final Module module; + private final TaskDescription description; + private final PriorityTag priorityTag; + private final DeadlineTag deadlineTag; + private final TaskStatus status; + private final Exam linkedExam; + + /** + * The constructor of the Task class. Sets the module and + * description of the task. + * + * @param module The module being added. + * @param description The description of the task. + */ + public Task(Module module, TaskDescription description) { + this.module = module; + this.description = description; + status = TaskStatus.INCOMPLETE; + priorityTag = null; + deadlineTag = null; + linkedExam = null; + } + + /** + * The constructor of the Task class. Sets the module and + * description of the task. + * + * @param module The module being added. + * @param description The description of the task. + */ + public Task(Module module, TaskDescription description, TaskStatus status) { + this.module = module; + this.description = description; + this.status = status; + priorityTag = null; + deadlineTag = null; + linkedExam = null; + } + + /** + * The constructor of the Task class. Sets the module, description, + * completion status, the priority status of the task and the deadline + * of the task. + * + * @param module The module being set. + * @param description The description being set. + * @param status The completion status of the task. + * @param priorityTag The tag marking the priority status of the task. + * @param deadlineTag The tag marking the deadline of the task. + */ + public Task(Module module, TaskDescription description, TaskStatus status, PriorityTag priorityTag, + DeadlineTag deadlineTag) { + this.module = module; + this.description = description; + this.status = status; + this.priorityTag = priorityTag; + this.deadlineTag = deadlineTag; + linkedExam = null; + } + + /** + * The constructor of the Task class. Sets the module, description, + * completion status, the priority status of the task, the deadline + * of the task and the exam description of the task. + * + * @param module The module being set. + * @param description The description being set. + * @param status The completion status of the task. + * @param priorityTag The tag marking the priority status of the task. + * @param deadlineTag The tag marking the deadline of the task. + * @param linkedExam The exam the task is linked to. + */ + public Task(Module module, TaskDescription description, TaskStatus status, PriorityTag priorityTag, + DeadlineTag deadlineTag, Exam linkedExam) { + this.module = module; + this.description = description; + this.status = status; + this.priorityTag = priorityTag; + this.deadlineTag = deadlineTag; + this.linkedExam = linkedExam; + } + + public TaskDescription getDescription() { + return description; + } + + public Module getModule() { + return module; + } + + public TaskStatus getStatus() { + return status; + } + + /** + * Returns true if both tasks have the same data fields. + */ + public boolean isSameTask(Task otherTask) { + if (otherTask == null) { + return false; + } + return otherTask == this || (this.getModule().equals(otherTask.getModule()) + && this.getDescription().equals(otherTask.getDescription())); + } + + /** + * Returns true if task is complete. + */ + public boolean isComplete() { + return this.status.isComplete(); + } + + /** + * Marks the task as complete and returns the task. + * + * @return a task with identical fields as {@this}, but labelled as complete. + * @throws TaskAlreadyMarkedException if the task is already marked. + */ + public Task mark() { + if (isComplete()) { + throw new TaskAlreadyMarkedException(); + } + return new Task(module, description, TaskStatus.COMPLETE, priorityTag, deadlineTag, linkedExam); + } + + public Task setPriorityTag(PriorityTag tag) { + requireNonNull(tag); + if (priorityTag != null) { + throw new PriorityTagAlreadyExistsException(); + } + return new Task(module, description, status, tag, deadlineTag, linkedExam); + } + + /** + * Replaces the priority tag stored in the task. + * + * @param tag The new priority tag. + * @return The task which contains the new priority tag. + */ + public Task replacePriorityTag(PriorityTag tag) { + requireNonNull(tag); + if (priorityTag != null && priorityTag.compareTo(tag) == 0) { + throw new PriorityTagUnchangedException(); + } + return new Task(module, description, status, tag, deadlineTag, linkedExam); + } + + /** + * Deletes the deadline tag stored in the task. + * + * @return The task which contains no deadline tag. + */ + public Task deletePriorityTag() { + if (priorityTag == null) { + throw new PriorityTagDoesNotExistException(); + } + return new Task(module, description, status, null, deadlineTag, linkedExam); + } + + public boolean hasPriorityTag() { + return priorityTag != null; + } + + public PriorityTag getPriorityTag() { + return priorityTag; + } + + public boolean hasDeadlineTag() { + return deadlineTag != null; + } + + public DeadlineTag getDeadlineTag() { + return deadlineTag; + } + + public Task setDeadlineTag(DeadlineTag tag) { + requireNonNull(tag); + if (deadlineTag != null) { + throw new DeadlineTagAlreadyExistsException(); + } + return new Task(module, description, status, priorityTag, tag, linkedExam); + } + + /** + * Replaces the deadline tag stored in the task. + * + * @param tag The new deadline tag. + * @return The task which contains the new deadline tag. + */ + public Task replaceDeadlineTag(DeadlineTag tag) { + requireNonNull(tag); + if (deadlineTag != null && deadlineTag.compareTo(tag) == 0) { + throw new DeadlineTagUnchangedException(); + } + return new Task(module, description, status, priorityTag, tag, linkedExam); + } + + /** + * Deletes the priority tag stored in the task. + * + * @return The task which contains no deadline tag. + */ + public Task deleteDeadlineTag() { + if (deadlineTag == null) { + throw new DeadlineTagDoesNotExistException(); + } + return new Task(module, description, status, priorityTag, null, linkedExam); + } + + public boolean isLinked() { + return linkedExam != null; + } + + public Exam getExam() { + return linkedExam; + } + + /** + * Unmarks (labels as incomplete) the task and returns the task. + * + * @return a task with identical fields as {@this} but labelled as incomplete. + * @throws TaskAlreadyUnmarkedException if the task is already unmarked. + */ + public Task unmark() { + if (!isComplete()) { + throw new TaskAlreadyUnmarkedException(); + } + return new Task(module, description, TaskStatus.INCOMPLETE, priorityTag, deadlineTag, linkedExam); + } + + /** + * Creates and returns a {@code Task} with the details of {@code this} + * edited with {@code editTaskDescriptor}. + */ + public Task edit(EditTaskDescriptor editTaskDescriptor) { + requireNonNull(editTaskDescriptor); + Module updatedModule = editTaskDescriptor.getModule().orElse(module); + TaskDescription updatedDescription = editTaskDescriptor.getDescription().orElse(description); + + if (editTaskDescriptor.getModule().isPresent() + && !editTaskDescriptor.getModule().get().equals(module)) { + return new Task(updatedModule, updatedDescription, status, priorityTag, deadlineTag); + } + + return new Task(updatedModule, updatedDescription, status, priorityTag, deadlineTag, linkedExam); + } + + /** + * Creates and returns a {@code Task} with the details of {@code this} + * edited with {@code newModule} and {@code newTaskDescription}. Preserves the link between the task + * and its corresponding exam even if the module is changed. + */ + public Task editWithoutUnlinkingExam(Module newModule, TaskDescription newTaskDescription) { + requireAnyNonNull(newModule, newTaskDescription); + Module updatedModule = module; + TaskDescription updatedDescription = description; + if (newModule != null) { + updatedModule = newModule; + } + if (newTaskDescription != null) { + updatedDescription = newTaskDescription; + } + return new Task(updatedModule, updatedDescription, status, priorityTag, deadlineTag, linkedExam); + } + + /** + * Links the task to the exam in the exam list. + * + * @param exam The exam which the task will be linked to. + * @return Task object which now contains the linked exam. + */ + public Task linkTask(Exam exam) { + requireNonNull(exam); + return new Task(module, description, status, priorityTag, deadlineTag, exam); + } + + public Task unlinkTask() { + return new Task(module, description, status, priorityTag, deadlineTag, null); + } + + /** + * Checks whether the two tasks have the exact same fields. + * + * @param otherTask The other task being compared against. + * @return true if the two Task objects have the same module, description, status, priority tag, deadline tag and + * linked exam, else return false. + */ + public boolean hasAllSameFields(Task otherTask) { + return this.module.equals(otherTask.module) + && this.description.equals(otherTask.description) + && this.status.equals(otherTask.status) + && (!hasPriorityTag() && !otherTask.hasPriorityTag() + || this.priorityTag.equals(otherTask.priorityTag)) + && (!hasDeadlineTag() && !otherTask.hasDeadlineTag() + || this.deadlineTag.equals(otherTask.deadlineTag)) + && (!isLinked() && !otherTask.isLinked() + || this.linkedExam.equals(otherTask.linkedExam)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.getDescription().equals(getDescription()) + && otherTask.getModule().equals(getModule()) + && otherTask.getStatus().equals(getStatus()) + && Objects.equals(otherTask.getDeadlineTag(), getDeadlineTag()) + && Objects.equals(otherTask.getPriorityTag(), getPriorityTag()); + } + + @Override + public int hashCode() { + return Objects.hash(description, module); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Description: ") + .append(getDescription()) + .append("; Module: ") + .append(getModule()); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/task/TaskDescription.java b/src/main/java/seedu/address/model/task/TaskDescription.java new file mode 100644 index 00000000000..2d24ea2c823 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskDescription.java @@ -0,0 +1,53 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * TaskDescription class represents the description of the task. + */ +public class TaskDescription implements Comparable { + public static final String DESCRIPTION_CONSTRAINTS = + "The description of the task should not be empty"; + + public final String description; + + /** + * The constructor of the TaskDescription class. Sets the + * description of the class. + * + * @param description The description of the class. + */ + public TaskDescription(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), DESCRIPTION_CONSTRAINTS); + this.description = description; + } + + public static boolean isValidDescription(String description) { + return description.strip().length() > 0; + } + + @Override + public boolean equals(Object otherDescription) { + return otherDescription == this || (otherDescription instanceof TaskDescription + && description.equalsIgnoreCase((((TaskDescription) otherDescription).description))); + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + @Override + public String toString() { + return description; + } + + @Override + public int compareTo(TaskDescription otherDescription) { + return this.description.compareTo(otherDescription.description); + } +} + + diff --git a/src/main/java/seedu/address/model/task/TaskLinkedToExamPredicate.java b/src/main/java/seedu/address/model/task/TaskLinkedToExamPredicate.java new file mode 100644 index 00000000000..eb4600e4a00 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskLinkedToExamPredicate.java @@ -0,0 +1,32 @@ +package seedu.address.model.task; + +import java.util.function.Predicate; + +import seedu.address.model.exam.Exam; + +/** + * Tests that a {@code Task} fulfils the given predicate. + */ +public class TaskLinkedToExamPredicate implements Predicate { + private final Exam examToCheck; + + /** + * Tests that a {@code Task} is linked to a specific exam. + * @param exam Exam to be checked if task is linked with. + */ + public TaskLinkedToExamPredicate(Exam exam) { + this.examToCheck = exam; + } + + @Override + public boolean test(Task task) { + return task.isLinked() && task.getExam().equals(examToCheck); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskLinkedToExamPredicate // instanceof handles nulls + && examToCheck.equals(((TaskLinkedToExamPredicate) other).examToCheck)); // state check + } +} diff --git a/src/main/java/seedu/address/model/task/TaskStatus.java b/src/main/java/seedu/address/model/task/TaskStatus.java new file mode 100644 index 00000000000..0421a1bf054 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskStatus.java @@ -0,0 +1,82 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * TaskStatus class represents the status of the task - whether it is complete. + */ +public class TaskStatus { + + private static final String COMPLETE_STRING = "complete"; + private static final String INCOMPLETE_STRING = "incomplete"; + + public static final TaskStatus COMPLETE = new TaskStatus(COMPLETE_STRING); + public static final TaskStatus INCOMPLETE = new TaskStatus(INCOMPLETE_STRING); + + public static final String STATUS_CONSTRAINTS = + "The status of the task should be " + COMPLETE + " or " + INCOMPLETE; + + public final String status; + + private TaskStatus(String status) { + requireNonNull(status); + checkArgument(isValidStatus(status), STATUS_CONSTRAINTS); + this.status = status; + } + + /** + * Constructs a {@code TaskStatus}. + * It is either complete or incomplete. + * + * @param status A valid status. + */ + public static TaskStatus of(String status) { + requireNonNull(status); + checkArgument(isValidStatus(status), STATUS_CONSTRAINTS); + if (status.equalsIgnoreCase(COMPLETE_STRING)) { + return COMPLETE; + } else { + return INCOMPLETE; + } + } + + /** + * Returns true if the given string is "complete" or "incomplete", + * false otherwise. + * This comparison is not case-sensitive. + * + * @param status string representation of a status. + * @return true if the given string is a valid status, false otherwise. + */ + public static boolean isValidStatus(String status) { + return status.equalsIgnoreCase(COMPLETE_STRING) || status.equalsIgnoreCase(INCOMPLETE_STRING); + } + + /** + * Returns true if task status is "complete", + * false otherwise. + * + * @return true if task status is "complete". + */ + public boolean isComplete() { + return this == COMPLETE; + } + + @Override + public boolean equals(Object otherStatus) { + return otherStatus == this || (otherStatus instanceof TaskStatus + && status.equalsIgnoreCase((((TaskStatus) otherStatus).status))); + } + + @Override + public int hashCode() { + return status.hashCode(); + } + + @Override + public String toString() { + return status; + } +} + diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 00000000000..5262c173db3 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation will result in duplicate Task (Tasks are considered duplicates if they have the same + * module and description). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyMarkedException.java b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyMarkedException.java new file mode 100644 index 00000000000..af05e46e2f3 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyMarkedException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation marks a task that is already indicated as completed. + */ +public class TaskAlreadyMarkedException extends RuntimeException { + public TaskAlreadyMarkedException() { + super("The operation marks a task that is already unmarked"); + } + +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyUnmarkedException.java b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyUnmarkedException.java new file mode 100644 index 00000000000..02600e50712 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyUnmarkedException.java @@ -0,0 +1,10 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation unmarks a task that is already indicated as not completed. + */ +public class TaskAlreadyUnmarkedException extends RuntimeException { + public TaskAlreadyUnmarkedException() { + super("The operation unmarks a task that is already unmarked"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..3c84ae73c35 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,8 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends RuntimeException { +} + diff --git a/src/main/java/seedu/address/model/task/exceptions/WrongTaskModifiedException.java b/src/main/java/seedu/address/model/task/exceptions/WrongTaskModifiedException.java new file mode 100644 index 00000000000..08457793f72 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/WrongTaskModifiedException.java @@ -0,0 +1,10 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation modifies the wrong task. + */ +public class WrongTaskModifiedException extends RuntimeException { + public WrongTaskModifiedException() { + super("Operation modifies the wrong task"); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedExam.java b/src/main/java/seedu/address/storage/JsonAdaptedExam.java new file mode 100644 index 00000000000..f0c40ca6161 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedExam.java @@ -0,0 +1,81 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; + +/** + * This class represents a Jackson friendly version of the Exam. + */ +public class JsonAdaptedExam { + public static final String MISSING_EXAM_DESCRIPTION = "Exam description is not present"; + public static final String MISSING_MODULE_CODE = "Module code is not present"; + public static final String MISSING_EXAM_DATE = "Exam date is not present"; + private final String description; + private final String moduleCode; + private final String date; + + /** + * Builds a {@code JsonAdaptedExam} with the description and module code and date. + * + * @param description The description of the exam. + * @param moduleCode The module code of the exam. + * @param date The date of the exam. + */ + public JsonAdaptedExam(@JsonProperty("description") String description, + @JsonProperty("modCode") String moduleCode, + @JsonProperty("date") String date) { + this.description = description; + this.moduleCode = moduleCode; + this.date = date; + } + + /** + * Converts an existing exam into {@code JsonAdaptedExam} object + * + * @param exam The exam object being converted. + */ + public JsonAdaptedExam(Exam exam) { + description = exam.getDescription().description; + moduleCode = exam.getModule().getModuleCode().moduleCode; + date = exam.getExamDate().examDate; + } + + /** + * Converts this Jackson-friendly exam object into a Exam object for the model. + * + * @return The Exam object which has been created. + * @throws IllegalValueException if the exam has invalid fields. + */ + public Exam toModelType() throws IllegalValueException { + if (description == null) { + throw new IllegalValueException(MISSING_EXAM_DESCRIPTION); + } + if (moduleCode == null) { + throw new IllegalValueException(MISSING_MODULE_CODE); + } + if (date == null) { + throw new IllegalValueException(MISSING_EXAM_DATE); + } + if (!ExamDescription.isValidDescription(description)) { + throw new IllegalValueException(ExamDescription.DESCRIPTION_CONSTRAINTS); + } + if (!ModuleCode.isValidModuleCode(moduleCode)) { + throw new IllegalValueException(ModuleCode.MODULE_CODE_CONSTRAINTS); + } + if (!ExamDate.isValidDateFormat(date)) { + throw new IllegalValueException(ExamDate.DATE_CONSTRAINTS); + } + final ExamDescription examDescription = new ExamDescription(description); + final ModuleCode modCode = new ModuleCode(moduleCode); + final Module module = new Module(modCode); + final ExamDate examDate = new ExamDate(date); + return new Exam(module, examDescription, examDate); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedModule.java b/src/main/java/seedu/address/storage/JsonAdaptedModule.java new file mode 100644 index 00000000000..783bccd0f63 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedModule.java @@ -0,0 +1,76 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; + +/** + * This class represents a Jackson friendly version of {@link Module}. + */ +public class JsonAdaptedModule { + public static final String MISSING_MODULE_MESSAGE_FORMAT = + "Module's fields are not present"; + private final String moduleCode; + private final String moduleName; + private final String moduleCredit; + + /** + * Builds a {@code JsonAdaptedModule} with the module code, module name and module credit. + * + * @param moduleCode The module code of the module, + * @param moduleName The module name of the module. + * @param moduleCredit The module credit of the module. + */ + public JsonAdaptedModule(@JsonProperty("modCode") String moduleCode, @JsonProperty("modName") String moduleName, + @JsonProperty("modCredit") String moduleCredit) { + this.moduleCode = moduleCode; + this.moduleName = moduleName; + this.moduleCredit = moduleCredit; + } + + /** + * Converts an existing module into {@code JsonAdaptedModule} object + * + * @param mod The module object being converted. + */ + public JsonAdaptedModule(Module mod) { + moduleCode = mod.getModuleCode().moduleCode; + moduleName = mod.getModuleName().moduleName; + moduleCredit = String.valueOf(mod.getModuleCredit().moduleCredit); + } + + /** + * Converts this Jackson-friendly module object into a Module Object. + * + * @return The Module Object which is created. + * @throws IllegalValueException If moduleCode is null. + */ + public Module toModelType() throws IllegalValueException { + if (moduleCode == null || moduleName == null || moduleCredit == null) { + throw new IllegalValueException(MISSING_MODULE_MESSAGE_FORMAT); + } + if (!ModuleCode.isValidModuleCode(moduleCode)) { + throw new IllegalValueException(ModuleCode.MODULE_CODE_CONSTRAINTS); + } + if (!ModuleName.isValidModuleName(moduleName)) { + throw new IllegalValueException(ModuleName.MODULE_NAME_CONSTRAINTS); + } + int integerModuleCredit; + try { + integerModuleCredit = Integer.parseInt(moduleCredit); + } catch (NumberFormatException nfe) { + throw new IllegalValueException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + } + if (!ModuleCredit.isValidModuleCredit(integerModuleCredit)) { + throw new IllegalValueException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + } + final ModuleCode modCode = new ModuleCode(moduleCode); + final ModuleName modName = new ModuleName(moduleName); + final ModuleCredit modCredit = new ModuleCredit(integerModuleCredit); + return new Module(modCode, modName, modCredit); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..c86cd4e78c5 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,153 @@ +package seedu.address.storage; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskStatus; + +/** + * This class represents a Jackson friendly version of the Task. + */ +public class JsonAdaptedTask { + public static final String MISSING_TASK_DESCRIPTION = "Description of the task is not present"; + public static final String MISSING_TASK_MODULE = "Module of the task is not present"; + public static final String MISSING_TASK_STATUS = "Completion status of the task is not present"; + public static final String WRONG_EXAM_FORMAT = "The task must either have both exam description" + + " and exam date or have no exam description and exam date."; + private final String taskDescription; + private final String moduleCode; + private final String status; + private final String priority; + private final String deadline; + private final String examDescription; + private final String examDate; + + /** + * Builds a {@code JsonAdaptedTask} with the description and module code. + * + * @param taskDescription The description of the task. + * @param moduleCode The module code of the task. + * @param status The completion status of the task. + * @param priority The tag which carries the priority status of the task. + * @param deadline The deadline to complete the task. + * @param examDate The date of the exam. + * @param examDescription The description of the exam. + */ + public JsonAdaptedTask(@JsonProperty("taskDescription") String taskDescription, + @JsonProperty("modCode") String moduleCode, @JsonProperty("status") String status, + @JsonProperty("priority") String priority, @JsonProperty("deadline") String deadline, + @JsonProperty("examDate") String examDate, + @JsonProperty("examDescription") String examDescription) { + this.taskDescription = taskDescription; + this.moduleCode = moduleCode; + this.status = status; + this.priority = priority; + this.deadline = deadline; + this.examDate = examDate; + this.examDescription = examDescription; + } + + /** + * Converts an existing task into {@code JsonAdaptedTask} object + * + * @param task The task object being converted. + */ + public JsonAdaptedTask(Task task) { + taskDescription = task.getDescription().description; + moduleCode = task.getModule().getModuleCode().moduleCode; + status = task.getStatus().status; + priority = task.getPriorityTag() == null ? null : task.getPriorityTag().status; + deadline = task.getDeadlineTag() == null ? null : task.getDeadlineTag().toString(); + examDate = task.getExam() == null ? null : task.getExam().getExamDate().examDate; + examDescription = task.getExam() == null ? null : task.getExam().getDescription().description; + } + + /** + * Converts this Jackson-friendly task object into a Task object for the model. + * + * @return The Task object which has been created. + * @throws IllegalValueException if the task has invalid fields. + */ + public Task toModelType() throws IllegalValueException { + if (taskDescription == null) { + throw new IllegalValueException(MISSING_TASK_DESCRIPTION); + } + if (moduleCode == null) { + throw new IllegalValueException(MISSING_TASK_MODULE); + } + if (status == null) { + throw new IllegalValueException(MISSING_TASK_STATUS); + } + if (!TaskDescription.isValidDescription(taskDescription)) { + throw new IllegalValueException(TaskDescription.DESCRIPTION_CONSTRAINTS); + } + if (!ModuleCode.isValidModuleCode(moduleCode)) { + throw new IllegalValueException(ModuleCode.MODULE_CODE_CONSTRAINTS); + } + if (!TaskStatus.isValidStatus(status)) { + throw new IllegalValueException(TaskStatus.STATUS_CONSTRAINTS); + } + if (priority != null && !PriorityTag.isValidTag(priority)) { + throw new IllegalValueException(PriorityTag.PRIORITY_TAG_CONSTRAINTS); + } + + final LocalDate date; + if (deadline != null && !DeadlineTag.checkDateFormat(deadline)) { + throw new IllegalValueException(DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS); + } + try { + if (deadline != null) { + //@@author dlimyy-reused + //Reused from https://stackoverflow.com/questions/32823368/ + //with minor modifications. + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-uuuu") + .withResolverStyle(ResolverStyle.STRICT); + //@@author + date = LocalDate.parse(deadline, dtf); + } else { + date = null; + } + } catch (DateTimeParseException dtp) { + throw new IllegalValueException(DeadlineTag.DEADLINE_TAG_INVALID_DATE); + } + if (!((examDate == null && examDescription == null) + || (examDate != null && examDescription != null))) { + throw new IllegalValueException(WRONG_EXAM_FORMAT); + } + + if (examDate != null && !ExamDate.isValidDateFormat(examDate)) { + throw new IllegalValueException(ExamDate.DATE_CONSTRAINTS); + } + if (examDescription != null && !ExamDescription.isValidDescription(examDescription)) { + throw new IllegalValueException(ExamDescription.DESCRIPTION_CONSTRAINTS); + } + final ExamDescription descriptionOfExam = examDescription == null + ? null : new ExamDescription(this.examDescription); + final ExamDate dateOfExam = examDate == null ? null : new ExamDate(examDate); + + final DeadlineTag deadlineTag = date == null ? null : new DeadlineTag(date); + final TaskDescription descriptionOfTask = new TaskDescription(taskDescription); + final ModuleCode modCode = new ModuleCode(moduleCode); + final Module module = new Module(modCode); + final Exam exam = examDate == null ? null + : new Exam(module, descriptionOfExam, dateOfExam); + final TaskStatus taskStatus = TaskStatus.of(status); + final PriorityTag priorityTag = priority == null ? null : new PriorityTag(priority); + return new Task(module, descriptionOfTask, taskStatus, priorityTag, deadlineTag, exam); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..33cca11ac05 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,7 +11,9 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; /** * An Immutable AddressBook that is serializable to JSON format. @@ -19,26 +21,43 @@ @JsonRootName(value = "addressbook") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); + public static final String MESSAGE_MODULE_NOT_PRESENT = "This module does not exist"; + public static final String MESSAGE_DUPLICATE_MODULE = "There is/are duplicate module(s) " + + "present in the module list"; + public static final String MESSAGE_DUPLICATE_EXAM = "There is/are duplicate exam(s) " + + "present in the exam list"; + public static final String INVALID_EXAM_LINKED = "Invalid exam is linked to the task"; + public static final String MESSAGE_DUPLICATE_TASK = "There is/are duplicate task(s) " + + "present in the task list"; + private final List modules = new ArrayList<>(); + private final List tasks = new ArrayList<>(); + private final List exams = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given tasks and modules. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("tasks") List tasks, + @JsonProperty("modules") List modules, + @JsonProperty("exams") List exams) { + this.tasks.addAll(tasks); + this.modules.addAll(modules); + this.exams.addAll(exams); } + //@@author dlimyy-reused + //Reused with minor modifications from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) /** * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. * * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. */ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); + modules.addAll(source.getModuleList().stream().map(JsonAdaptedModule::new).collect(Collectors.toList())); + tasks.addAll(source.getTaskList().stream().map(JsonAdaptedTask::new).collect(Collectors.toList())); + exams.addAll(source.getExamList().stream().map(JsonAdaptedExam::new).collect(Collectors.toList())); } + //@@author /** * Converts this address book into the model's {@code AddressBook} object. @@ -47,12 +66,35 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { */ public AddressBook toModelType() throws IllegalValueException { AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + for (JsonAdaptedModule jsonAdaptedModule: modules) { + Module module = jsonAdaptedModule.toModelType(); + if (addressBook.hasModule(module)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_MODULE); + } + addressBook.addModule(module); + } + for (JsonAdaptedExam jsonAdaptedExam: exams) { + Exam exam = jsonAdaptedExam.toModelType(); + if (addressBook.hasExam(exam)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_EXAM); + } + if (!addressBook.hasModule(exam.getModule())) { + throw new IllegalValueException(MESSAGE_MODULE_NOT_PRESENT); + } + addressBook.addExam(exam); + } + for (JsonAdaptedTask jsonAdaptedTask: tasks) { + Task task = jsonAdaptedTask.toModelType(); + if (addressBook.hasTask(task)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_TASK); + } + if (!addressBook.hasModule(task.getModule())) { + throw new IllegalValueException(MESSAGE_MODULE_NOT_PRESENT); + } + if (task.getExam() != null && !addressBook.hasExam(task.getExam())) { + throw new IllegalValueException(INVALID_EXAM_LINKED); } - addressBook.addPerson(person); + addressBook.addTask(task); } return addressBook; } diff --git a/src/main/java/seedu/address/ui/ExamCard.java b/src/main/java/seedu/address/ui/ExamCard.java new file mode 100644 index 00000000000..17a3bb4e173 --- /dev/null +++ b/src/main/java/seedu/address/ui/ExamCard.java @@ -0,0 +1,66 @@ +package seedu.address.ui; + + +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.Region; +import seedu.address.model.exam.Exam; + + +/** + * ExamCard represents an Exam Card which contains details of the Exam and the position in the + * listView. + */ +public class ExamCard extends UiPart { + private static final String FXML = "ExamListCard.fxml"; + + public final Exam exam; + + @FXML + private Label id; + + @FXML + private Label examDescription; + + @FXML + private Label examDate; + + @FXML + private Label moduleCode; + + @FXML + private ProgressBar percentageCompleted; + + @FXML + private Label progressMessage; + + + /** + * Constructor of the ExamCard. Sets the exam and the position. + * + * @param exam The exam being set. + * @param position The position of the exam in the listView. + */ + public ExamCard(Exam exam, int position) { + super(FXML); + this.exam = exam; + id.setText(position + ". "); + moduleCode.setText("Module Code: " + exam.getModule().getModuleCode().moduleCode); + examDescription.setText(exam.getDescription().description); + examDate.setText("Date: " + exam.getExamDate().examDate); + if (!exam.hasTasks()) { + percentageCompleted.setPrefWidth(0); + } else { + percentageCompleted.setPadding(new Insets(0, 5, 0, 0)); + } + + if (exam.hasTasks()) { + percentageCompleted.setProgress(exam.getPercentageCompleted()); + } + percentageCompleted.setStyle("-fx-accent:limegreen"); + progressMessage.setText(exam.generateProgressMessage()); + + } +} diff --git a/src/main/java/seedu/address/ui/ExamListPanel.java b/src/main/java/seedu/address/ui/ExamListPanel.java new file mode 100644 index 00000000000..8eb05f435a9 --- /dev/null +++ b/src/main/java/seedu/address/ui/ExamListPanel.java @@ -0,0 +1,47 @@ +package seedu.address.ui; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.model.exam.Exam; + + +/** + * ExamListPanel class represents a panel which contains a list of all + * the exams. + */ +public class ExamListPanel extends UiPart { + private static final String FXML = "ExamListPanel.fxml"; + + @FXML + private ListView examListView; + + /** + * The constructor of the ExamListPanel. Sets the list of exams + * to the ListView. + * + * @param examList + */ + public ExamListPanel(ObservableList examList) { + super(FXML); + examListView.setItems(examList); + examListView.setCellFactory(listView -> new ExamListViewCell()); + } + + static class ExamListViewCell extends ListCell { + @Override + protected void updateItem(Exam exam, boolean empty) { + super.updateItem(exam, empty); + + if (empty || exam == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ExamCard(exam, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..d6399372802 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-f11-2.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..c1163835277 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -31,7 +31,10 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + + private TaskListPanel taskListPanel; + private ModuleListPanel moduleListPanel; + private ExamListPanel examListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -50,6 +53,15 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane moduleListPanelPlaceholder; + + @FXML + private StackPane taskListPanelPlaceholder; + + @FXML + private StackPane examListPanelPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -59,7 +71,6 @@ public MainWindow(Stage primaryStage, Logic logic) { // Set dependencies this.primaryStage = primaryStage; this.logic = logic; - // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); @@ -110,8 +121,18 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + //@@author dlimyy-reused + //Reused with modifications + // from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp) + taskListPanel = new TaskListPanel(logic.getFilteredTaskList()); + taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + + moduleListPanel = new ModuleListPanel(logic.getFilteredModuleList()); + moduleListPanelPlaceholder.getChildren().add(moduleListPanel.getRoot()); + //@@author + + examListPanel = new ExamListPanel(logic.getFilteredExamList()); + examListPanelPlaceholder.getChildren().add(examListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -163,10 +184,6 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - /** * Executes the command and returns the result. * @@ -177,15 +194,12 @@ private CommandResult executeCommand(String commandText) throws CommandException CommandResult commandResult = logic.execute(commandText); logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - if (commandResult.isShowHelp()) { handleHelp(); } - if (commandResult.isExit()) { handleExit(); } - return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/seedu/address/ui/ModuleCard.java b/src/main/java/seedu/address/ui/ModuleCard.java new file mode 100644 index 00000000000..043d0f45753 --- /dev/null +++ b/src/main/java/seedu/address/ui/ModuleCard.java @@ -0,0 +1,62 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.Region; +import seedu.address.model.module.Module; + +/** + * ModuleCard class represents a module card which contains the details of + * the module card. + */ +public class ModuleCard extends UiPart { + //@@author dlimyy-reused + //Reused with minor modifications from existing AddressBook 3 + + private static final String FXML = "ModuleListCard.fxml"; + @FXML + private Label id; + + @FXML + private Label moduleCode; + + @FXML + private Label moduleName; + + @FXML + private Label moduleCredit; + + @FXML + private ProgressBar percentageCompleted; + + @FXML + private Label progressString; + + /** + * The constructor of ModuleCard. Sets the id and module + * code fields with their values. + * + * @param module The module whose module code is being set. + * @param position The position of the module in the module list. + */ + public ModuleCard(Module module, int position) { + super(FXML); + id.setText(position + ". "); + moduleCode.setText(module.getModuleCode().moduleCode); + moduleName.setText("Name: " + module.getModuleName().moduleName); + moduleCredit.setText("Module Credit: " + module.getModuleCredit().moduleCredit); + + if (!module.hasTasks()) { + percentageCompleted.setPrefWidth(0); + } else { + percentageCompleted.setPadding(new Insets(0, 5, 0, 0)); + } + + percentageCompleted.setProgress(module.getPercentageCompleted()); + percentageCompleted.setStyle("-fx-accent: limegreen"); + progressString.setText(module.generateProgressMessage()); + } + //@@author +} diff --git a/src/main/java/seedu/address/ui/ModuleListPanel.java b/src/main/java/seedu/address/ui/ModuleListPanel.java new file mode 100644 index 00000000000..7c6243bf55d --- /dev/null +++ b/src/main/java/seedu/address/ui/ModuleListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.model.module.Module; + +/** + * ModuleListPanel class represents a panel which contains a list of all + * the module card. + */ +public class ModuleListPanel extends UiPart { + //@@author dlimyy-reused + //Reused with minor modifications from existing AddressBook 3 + private static final String FXML = "ModuleListPanel.fxml"; + + @FXML + private ListView moduleListView; + + /** + * The constructor of the ModuleListPanel. Sets the list of modules + * to the ListView. + * + * @param moduleList + */ + public ModuleListPanel(ObservableList moduleList) { + super(FXML); + moduleListView.setItems(moduleList); + moduleListView.setCellFactory(listView -> new ModuleListViewCell()); + } + + static class ModuleListViewCell extends ListCell { + @Override + protected void updateItem(Module module, boolean empty) { + super.updateItem(module, empty); + + if (empty || module == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ModuleCard(module, getIndex() + 1).getRoot()); + } + } + } + + //@@author +} diff --git a/src/main/java/seedu/address/ui/ModuleUtil.java b/src/main/java/seedu/address/ui/ModuleUtil.java new file mode 100644 index 00000000000..01b72893091 --- /dev/null +++ b/src/main/java/seedu/address/ui/ModuleUtil.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME; + +import seedu.address.logic.commands.AddModuleCommand; +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.model.module.Module; + + +/** + * A utility class for Module. + */ +public class ModuleUtil { + + /** + * Returns an add module command string for adding the {@code module}. + */ + public static String getAddModuleCommand(Module module) { + return "m " + AddModuleCommand.COMMAND_WORD + " " + getModuleDetails(module); + } + + /** + * Returns the part of command string for the given {@code module}'s details. + */ + public static String getModuleDetails(Module module) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_MOD_CODE + module.getModuleCode().moduleCode + " "); + sb.append(PREFIX_MOD_NAME + module.getModuleName().moduleName + " "); + sb.append(PREFIX_MOD_CREDIT + module.getModuleCredit().toString() + " "); + + return sb.toString(); + } + + /** + * Returns the part of command string for the given {@code EditModuleDescriptor}'s details. + */ + public static String getEditModuleDescriptorDetails(EditModuleCommand.EditModuleDescriptor descriptor) { + StringBuilder sb = new StringBuilder(); + descriptor.getModuleCode().ifPresent(moduleCode + -> sb.append(PREFIX_MOD_CODE).append(moduleCode).append(" ")); + descriptor.getModuleName().ifPresent(moduleName + -> sb.append(PREFIX_MOD_NAME).append(moduleName).append(" ")); + descriptor.getModuleCredit().ifPresent(moduleCredit + -> sb.append(PREFIX_MOD_CREDIT).append(moduleCredit).append(" ")); + return sb.toString(); + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 7fc927bc5d9..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. - */ - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 00000000000..1843780f0ff --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,74 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.task.Task; + +/** + * TaskCard represents a Task Card which contains details of the Task and the position in the + * listView. + */ +public class TaskCard extends UiPart { + //@@author dlimyy-reused + //Reused with minor modifications from AddressBook 3 + private static final String FXML = "TaskListCard.fxml"; + + public final Task task; + + @FXML + private Label id; + + @FXML + private Label description; + + @FXML + private Label moduleCode; + + @FXML + private CheckBox isComplete; + + @FXML + private Button priorityTag; + + @FXML + private Button deadlineTag; + + @FXML + private Label examDescription; + + /** + * Constructor of the TaskCard. Sets the task and the position. + * + * @param task The task being set. + * @param position The position of the task in the listView. + */ + public TaskCard(Task task, int position) { + super(FXML); + this.task = task; + id.setText(position + ". "); + moduleCode.setText("Module Code: " + task.getModule().getModuleCode()); + description.setText(task.getDescription().description); + isComplete.setSelected(task.isComplete()); + if (task.getPriorityTag() != null) { + priorityTag.setText(task.getPriorityTag().status.toUpperCase()); + } else { + priorityTag.setManaged(false); + } + + if (task.getDeadlineTag() != null) { + deadlineTag.setText(task.getDeadlineTag().toString()); + } else { + deadlineTag.setManaged(false); + } + + if (task.getExam() != null) { + examDescription.setText("Exam Description: " + task.getExam().getDescription().description); + } else { + examDescription.setManaged(false); + } + } + //@@author +} diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java new file mode 100644 index 00000000000..397bbe54453 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.model.task.Task; + +/** + * TaskListPanel is a panel which represents the list of tasks. + * + */ +public class TaskListPanel extends UiPart { + //@@author dlimyy-reused + //Reused with minor modifications from AddressBook 3 + private static final String FXML = "TaskListPanel.fxml"; + + @FXML + private ListView taskListView; + + /** + * Constructor of the TaskListPanel. Sets the list of tasks + * to the ListView + * + * @param tasks The list of tasks which will be set. + */ + public TaskListPanel(ObservableList tasks) { + super(FXML); + taskListView.setItems(tasks); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + + static class TaskListViewCell extends ListCell { + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskCard(task, getIndex() + 1).getRoot()); + } + } + } + //@@author +} + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..7fceabb62d6 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -77,14 +77,14 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; + -fx-background-color: derive(#0D187B, 20%); + -fx-border-color: transparent; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#0D187B, 20%); } .list-view { @@ -121,36 +121,58 @@ } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "PT Mono"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: black; } .cell_small_label { - -fx-font-family: "Segoe UI"; - -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-font-family: "PT Sans"; + -fx-font-size: 15px; + -fx-text-fill: black; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#0D187B, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: derive(#0D187B, 20%); +} + +.exams-pane-with-border { + -fx-background-color: derive(#2A61EF, 20%); + -fx-border-color: black; + -fx-border-width: 2 2 2 2; + -fx-background-radius: 11px; + -fx-border-radius: 10px; +} + +.modules-pane-with-border { + -fx-background-color: derive(#329BFF, 20%); + -fx-border-color: black; + -fx-border-width: 2 2 2 2; + -fx-background-radius: 11px; + -fx-border-radius: 10px; +} + +.tasks-pane-with-border { + -fx-background-color: derive(#303EBD, 20%); + -fx-border-color: black; + -fx-border-width: 2 2 2 2; + -fx-background-radius: 11px; + -fx-border-radius: 10px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#0D187B, 20%); } .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; + -fx-font-weight: bold; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: black; } .result-display .label { @@ -158,16 +180,14 @@ } .status-bar .label { - -fx-font-family: "Segoe UI Light"; + -fx-font-weight: bold; -fx-text-fill: white; -fx-padding: 4px; -fx-pref-height: 30px; } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); - -fx-border-width: 1px; + -fx-background-color: derive(#0D187B, 20%); } .status-bar-with-border .label { @@ -193,14 +213,16 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#050D55, 20%); + -fx-border-color: black; + -fx-border-width: 0 0 2 0; } .menu-bar .label { -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; -fx-text-fill: white; -fx-opacity: 0.9; + -fx-font-weight: bold; } .menu .left-container { @@ -213,14 +235,14 @@ * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ */ .button { - -fx-padding: 5 22 5 22; + -fx-padding: 5 10 5 10; -fx-border-color: #e2e2e2; -fx-border-width: 2; -fx-background-radius: 0; -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; + -fx-font-family: "PT Mono", Helvetica, Arial, sans-serif; + -fx-font-size: 10pt; + -fx-text-fill: black; -fx-background-insets: 0 0 0 0, 0, 1, 2; } @@ -307,6 +329,10 @@ -fx-padding: 8 1 8 1; } +.check-box:disabled { + -fx-opacity: 1; +} + #cardPane { -fx-background-color: transparent; -fx-border-width: 0; @@ -318,12 +344,12 @@ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: transparent; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: derive(#0D187B, 20%) derive(#0D187B, 20%) #ffffff derive(#0D187B, 20%); -fx-border-insets: 0; - -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-border-width: 2; + -fx-font-weight: bold; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -333,8 +359,7 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; - -fx-background-radius: 0; + -fx-background-color: #B3C1D0; } #tags { @@ -350,3 +375,76 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +#titleBox { + -fx-background-color: derive(#000533, 15%); + -fx-border-color: black; + -fx-border-width: 0 0 2 0; + -fx-alignment: center; +} + +#title { + -fx-font-size: 15pt; + -fx-text-fill: white; +} + +#examsHeaderBox { + -fx-background-color: derive(#2A61EF, 20%); + -fx-alignment: top-center; +} + +#examsHeader { + -fx-font-size: 15pt; + -fx-text-fill: white; +} + +#modulesHeaderBox { + -fx-background-color: derive(#329BFF, 20%); + -fx-alignment: top-center; +} + +#modulesHeader { + -fx-font-size: 15pt; + -fx-text-fill: white; +} + +#tasksHeaderBox { + -fx-background-color: derive(#303EBD, 20%); + -fx-alignment: top-center; +} + +#tasksHeader { + -fx-font-size: 15pt; + -fx-text-fill: white; +} + +#examListView .list-cell { + -fx-background-color: derive(#2A61EF, 20%); +} + +#examListView { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#2A61EF, 20%); +} + +#moduleListView .list-cell { + -fx-background-color: derive(#329BFF, 20%); +} + +#moduleListView { + -fx-background-color: derive(#329BFF, 20%); + -fx-background-insets: 0; + -fx-padding: 0; +} + +#taskListView .list-cell { + -fx-background-color: derive(#303EBD, 20%); +} + +#taskListView { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#303EBD, 20%); +} + diff --git a/src/main/resources/view/ExamListCard.fxml b/src/main/resources/view/ExamListCard.fxml new file mode 100644 index 00000000000..b1132413ace --- /dev/null +++ b/src/main/resources/view/ExamListCard.fxml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ExamListPanel.fxml b/src/main/resources/view/ExamListPanel.fxml new file mode 100644 index 00000000000..94c30524259 --- /dev/null +++ b/src/main/resources/view/ExamListPanel.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css index 17e8a8722cd..430d13cac5b 100644 --- a/src/main/resources/view/HelpWindow.css +++ b/src/main/resources/view/HelpWindow.css @@ -14,6 +14,21 @@ -fx-background-color: darkgray; } -#helpMessageContainer { - -fx-background-color: derive(#1d1d1d, 20%); + +.label { +-fx-font-family: "segoe ui semibold"; +-fx-font-weight: 600; +-fx-text-fill: white +} + +#firstHalfCommands { +-fx-background-color: derive(#2d2d30, 15%); +} + +#secondHalfCommands { +-fx-background-color: derive(#2A61EF, 35%); +} + +#helpLinkContainer { +-fx-background-color: #329BFF } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index 5dea0adef70..300f1115581 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,15 +1,14 @@ - - - - - - - - + + + + + + + - + @@ -18,27 +17,152 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 00000000000..10ea8dd7987 --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidModuleAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidModuleAddressBook.json new file mode 100644 index 00000000000..64ea099759c --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidModuleAddressBook.json @@ -0,0 +1,13 @@ +{ + "tasks" : [ ], + "modules" : [{ + "moduleCode" : "CS2109S", + "moduleName" : "AI and Machine Learning", + "moduleCredit" : "4" + } ,{ + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "-9999999999999999999" + }], + "exams" : [ ] +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json deleted file mode 100644 index 6a4d2b7181c..00000000000 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "persons": [ { - "name": "Valid Person", - "phone": "9482424", - "email": "hans@example.com", - "address": "4th street" - }, { - "name": "Person With Invalid Phone Field", - "phone": "948asdf2424", - "email": "hans@example.com", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidModuleAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidModuleAddressBook.json new file mode 100644 index 00000000000..ffc6a77b952 --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidModuleAddressBook.json @@ -0,0 +1,9 @@ +{ + "tasks" : [ ], + "modules" : [{ + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "-9999999999999999999" + }], + "exams" : [ ] +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json deleted file mode 100644 index ccd21f7d1a9..00000000000 --- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "persons": [ { - "name": "Person with invalid name field: Ha!ns Mu@ster", - "phone": "9482424", - "email": "hans@example.com", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateExamAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateExamAddressBook.json new file mode 100644 index 00000000000..afd34920128 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateExamAddressBook.json @@ -0,0 +1,17 @@ +{ + "tasks" : [ ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + }], + "exams" : [ { + "description" : "Final Exam", + "date" : "28-12-2022", + "moduleCode" : "cs2030" + }, { + "description" : "Final Exam", + "date" : "28-12-2022", + "moduleCode" : "cs2030" + }] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateModuleAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateModuleAddressBook.json new file mode 100644 index 00000000000..ac6495ff78d --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateModuleAddressBook.json @@ -0,0 +1,13 @@ +{ + "tasks" : [ ], + "modules" : [{ + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + }, { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + }], + "exams" : [ ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json deleted file mode 100644 index 48831cc7674..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "persons": [ { - "name": "Alice Pauline", - "phone": "94351253", - "email": "alice@example.com", - "address": "123, Jurong West Ave 6, #08-111", - "tagged": [ "friends" ] - }, { - "name": "Alice Pauline", - "phone": "94351253", - "email": "pauline@example.com", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateTaskAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateTaskAddressBook.json new file mode 100644 index 00000000000..40e5a48937c --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateTaskAddressBook.json @@ -0,0 +1,25 @@ +{ + "tasks" : [ { + "taskDescription" : "Task A", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2030" + }, { + "taskDescription" : "Task A", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2030" + } ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + } ], + "exams" : [ ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/examWithInvalidModuleAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/examWithInvalidModuleAddressBook.json new file mode 100644 index 00000000000..dc71dd1f16e --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/examWithInvalidModuleAddressBook.json @@ -0,0 +1,13 @@ +{ + "tasks" : [ ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + }], + "exams" : [ { + "description" : "Final Exam", + "date" : "28-12-2022", + "moduleCode" : "cs1101s" + }] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidExamAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidExamAddressBook.json new file mode 100644 index 00000000000..41fb6c2fbf9 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/invalidExamAddressBook.json @@ -0,0 +1,13 @@ +{ + "tasks" : [ ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + }], + "exams" : [ { + "description" : "Final Exam", + "date" : "28/12/22", + "moduleCode" : "cs2030" + } ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidModuleAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidModuleAddressBook.json new file mode 100644 index 00000000000..f0dcd1c9b87 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/invalidModuleAddressBook.json @@ -0,0 +1,9 @@ +{ + "tasks" : [ ], + "modules" : [ { + "moduleCode" : "2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + } ], + "exams" : [ ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json deleted file mode 100644 index ad3f135ae42..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "persons": [ { - "name": "Hans Muster", - "phone": "9482424", - "email": "invalid@email!3e", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidTaskAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidTaskAddressBook.json new file mode 100644 index 00000000000..13f5cefa392 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/invalidTaskAddressBook.json @@ -0,0 +1,17 @@ +{ + "tasks" : [ { + "taskDescription" : "Task A", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2030" + } ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "-999999999" + } ], + "exams" : [ ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/taskWithInvalidExamAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/taskWithInvalidExamAddressBook.json new file mode 100644 index 00000000000..5a228a08098 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/taskWithInvalidExamAddressBook.json @@ -0,0 +1,21 @@ +{ + "tasks" : [ { + "taskDescription" : "Task A", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : "30-12-2022", + "examDescription" : "Finals", + "moduleCode" : "cs2030" + } ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + } ], + "exams" : [ { + "description" : "Midterm Exam", + "date" : "29-11-2022", + "moduleCode" : "cs2030" + } ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/taskWithInvalidModuleAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/taskWithInvalidModuleAddressBook.json new file mode 100644 index 00000000000..2c6f8fc8c02 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/taskWithInvalidModuleAddressBook.json @@ -0,0 +1,17 @@ +{ + "tasks" : [ { + "taskDescription" : "Task A", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2030" + } ], + "modules" : [ { + "moduleCode" : "cs1101s", + "moduleName" : "Programming Methodology for CS Students", + "moduleCredit" : "4" + } ], + "exams" : [ ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json deleted file mode 100644 index f10eddee12e..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()", - "persons" : [ { - "name" : "Alice Pauline", - "phone" : "94351253", - "email" : "alice@example.com", - "address" : "123, Jurong West Ave 6, #08-111", - "tagged" : [ "friends" ] - }, { - "name" : "Benson Meier", - "phone" : "98765432", - "email" : "johnd@example.com", - "address" : "311, Clementi Ave 2, #02-25", - "tagged" : [ "owesMoney", "friends" ] - }, { - "name" : "Carl Kurz", - "phone" : "95352563", - "email" : "heinz@example.com", - "address" : "wall street", - "tagged" : [ ] - }, { - "name" : "Daniel Meier", - "phone" : "87652533", - "email" : "cornelia@example.com", - "address" : "10th street", - "tagged" : [ "friends" ] - }, { - "name" : "Elle Meyer", - "phone" : "9482224", - "email" : "werner@example.com", - "address" : "michegan ave", - "tagged" : [ ] - }, { - "name" : "Fiona Kunz", - "phone" : "9482427", - "email" : "lydia@example.com", - "address" : "little tokyo", - "tagged" : [ ] - }, { - "name" : "George Best", - "phone" : "9482442", - "email" : "anna@example.com", - "address" : "4th street", - "tagged" : [ ] - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalTasksAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalTasksAddressBook.json new file mode 100644 index 00000000000..799575bccdd --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/typicalTasksAddressBook.json @@ -0,0 +1,189 @@ +{ + "tasks" : [ { + "taskDescription" : "Task A", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2030" + }, { + "taskDescription" : "Task B", + "status" : "incomplete", + "priority" : "low", + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2030" + }, { + "taskDescription" : "Task C", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2040" + }, { + "taskDescription" : "Task D", + "status" : "complete", + "priority" : "high", + "deadline" : "29-12-2022", + "examDate" : "29-12-2022", + "examDescription" : "Midterm Exam", + "moduleCode" : "cs2040" + }, { + "taskDescription" : "Task E", + "status" : "complete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2100" + }, { + "taskDescription" : "Task F", + "status" : "complete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2100" + }, { + "taskDescription" : "Task G", + "status" : "incomplete", + "priority" : "high", + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2100" + }, { + "taskDescription" : "Task H", + "status" : "incomplete", + "priority" : null, + "deadline" : "31-12-2022", + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2103t" + }, { + "taskDescription" : "Task I", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "cs2103t" + }, { + "taskDescription" : "Task one", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2030S" + }, { + "taskDescription" : "Task two", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2030S" + }, { + "taskDescription" : "HW1", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2030S" + }, { + "taskDescription" : "HW2", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2040S" + }, { + "taskDescription" : "HW3", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2030S" + }, { + "taskDescription" : "homework", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2030s" + }, { + "taskDescription" : "PAST YEAR PAPER", + "status" : "incomplete", + "priority" : null, + "deadline" : null, + "examDate" : null, + "examDescription" : null, + "moduleCode" : "CS2030s" + } ], + "modules" : [ { + "moduleCode" : "cs2030", + "moduleName" : "Programming Methodology", + "moduleCredit" : "4" + }, { + "moduleCode" : "cs2040", + "moduleName" : "Data and Algorithms", + "moduleCredit" : "4" + }, { + "moduleCode" : "cs2100", + "moduleName" : "Computer Organisation", + "moduleCredit" : "4" + }, { + "moduleCode" : "cs2103t", + "moduleName" : "Software Engineering", + "moduleCredit" : "4" + }, { + "moduleCode" : "MA1521", + "moduleName" : "Math", + "moduleCredit" : "4" + }, { + "moduleCode" : "CS2030S", + "moduleName" : "Programming Methodology for CS students", + "moduleCredit" : "4" + }, { + "moduleCode" : "CS2040S", + "moduleName" : "Data and Algorithms for CS Students", + "moduleCredit" : "4" + }, { + "moduleCode" : "MA2001", + "moduleName" : "Linear Algebra", + "moduleCredit" : "4" + } ], + "exams" : [ { + "description" : "Midterm Exam", + "date" : "29-12-2022", + "moduleCode" : "cs2040" + }, { + "description" : "Final Exam", + "date" : "28-12-2022", + "moduleCode" : "cs2030" + }, { + "description" : "Exam one", + "date" : "20-08-2023", + "moduleCode" : "CS2030S" + }, { + "description" : "Exam two", + "date" : "20-10-2023", + "moduleCode" : "CS2040S" + }, { + "description": "CS2040 Finals", + "date": "29-11-2022", + "moduleCode": "cs2040" + }, { + "description": "Midterm", + "date": "20-11-2023", + "moduleCode": "ma1521" + } ] +} diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index 1037548a9cd..ca16578a3a3 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -9,5 +9,5 @@ "z" : 99 } }, - "addressBookFilePath" : "addressbook.json" + "addressBookFilePath" : "modpro.json" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index b819bed900a..5bb2983fd32 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -7,5 +7,5 @@ "y" : 100 } }, - "addressBookFilePath" : "addressbook.json" + "addressBookFilePath" : "modpro.json" } diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..ea227b32ec8 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -1,14 +1,9 @@ package seedu.address.logic; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; import java.io.IOException; import java.nio.file.Path; @@ -17,20 +12,21 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; +import seedu.address.commons.core.Messages; import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListModulesCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; +import seedu.address.model.module.Module; import seedu.address.storage.JsonAddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.StorageManager; -import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.ModuleBuilder; public class LogicManagerTest { private static final IOException DUMMY_IO_EXCEPTION = new IOException("dummy exception"); @@ -38,36 +34,43 @@ public class LogicManagerTest { @TempDir public Path temporaryFolder; - private Model model = new ModelManager(); + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); private Logic logic; + private AddressBook addressBook; @BeforeEach public void setUp() { + addressBook = new AddressBook(model.getAddressBook()); JsonAddressBookStorage addressBookStorage = - new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + new JsonAddressBookStorage(temporaryFolder.resolve("modpro.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); logic = new LogicManager(model, storage); } + @Test public void execute_invalidCommandFormat_throwsParseException() { String invalidCommand = "uicfhmowqewca"; assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND); } + @Test public void execute_commandExecutionError_throwsCommandException() { - String deleteCommand = "delete 9"; - assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + String markCommand = "t mark 100"; + assertCommandException(markCommand, String.format(Messages + .MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, + model.getFilteredTaskList().size() + 1)); } @Test public void execute_validCommand_success() throws Exception { - String listCommand = ListCommand.COMMAND_WORD; - assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model); + String listModulesCommand = "m list"; + assertCommandSuccess(listModulesCommand, ListModulesCommand.MESSAGE_SUCCESS, model); } + @Test public void execute_storageThrowsIoException_throwsCommandException() { // Setup LogicManager with JsonAddressBookIoExceptionThrowingStub @@ -76,23 +79,41 @@ public void execute_storageThrowsIoException_throwsCommandException() { JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json")); StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); + model = new ModelManager(); logic = new LogicManager(model, storage); // Execute add command - String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY - + ADDRESS_DESC_AMY; - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); + String addModuleCommand = "m add c/CS3230 m/Design and Analysis of Algorithms mc/4"; + Module moduleAdded = new ModuleBuilder().withModuleCode("CS3230") + .withModuleName("Design and Analysis of Algorithms") + .withModuleCredit(4).build(); ModelManager expectedModel = new ModelManager(); - expectedModel.addPerson(expectedPerson); + expectedModel.addModule(moduleAdded); String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION; - assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel); + assertCommandFailure(addModuleCommand, CommandException.class, expectedMessage, expectedModel); } @Test - public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0)); + public void testGetAddressBook() { + assertEquals(addressBook, logic.getAddressBook()); } + @Test + public void getFilteredTaskList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredTaskList().remove(100)); + } + + @Test + public void getFilteredModuleList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredModuleList().remove(42)); + } + + @Test + public void getFilteredExamList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredTaskList().remove(67)); + } + + /** * Executes the command and confirms that * - no exceptions are thrown
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java deleted file mode 100644 index cb8714bb055..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) for {@code AddCommand}. - */ -public class AddCommandIntegrationTest { - - private Model model; - - @BeforeEach - public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - } - - @Test - public void execute_newPerson_success() { - Person validPerson = new PersonBuilder().build(); - - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.addPerson(validPerson); - - assertCommandSuccess(new AddCommand(validPerson), model, - String.format(AddCommand.MESSAGE_SUCCESS, validPerson), expectedModel); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person personInList = model.getAddressBook().getPersonList().get(0); - assertCommandFailure(new AddCommand(personInList), model, AddCommand.MESSAGE_DUPLICATE_PERSON); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java deleted file mode 100644 index 5865713d5dd..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandTest { - - @Test - public void constructor_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new AddCommand(null)); - } - - @Test - public void execute_personAcceptedByModel_addSuccessful() throws Exception { - ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); - Person validPerson = new PersonBuilder().build(); - - CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); - - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); - assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person validPerson = new PersonBuilder().build(); - AddCommand addCommand = new AddCommand(validPerson); - ModelStub modelStub = new ModelStubWithPerson(validPerson); - - assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); - } - - @Test - public void equals() { - Person alice = new PersonBuilder().withName("Alice").build(); - Person bob = new PersonBuilder().withName("Bob").build(); - AddCommand addAliceCommand = new AddCommand(alice); - AddCommand addBobCommand = new AddCommand(bob); - - // same object -> returns true - assertTrue(addAliceCommand.equals(addAliceCommand)); - - // same values -> returns true - AddCommand addAliceCommandCopy = new AddCommand(alice); - assertTrue(addAliceCommand.equals(addAliceCommandCopy)); - - // different types -> returns false - assertFalse(addAliceCommand.equals(1)); - - // null -> returns false - assertFalse(addAliceCommand.equals(null)); - - // different person -> returns false - assertFalse(addAliceCommand.equals(addBobCommand)); - } - - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - throw new AssertionError("This method should not be called."); - } - - @Override - public GuiSettings getGuiSettings() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - throw new AssertionError("This method should not be called."); - } - - @Override - public Path getAddressBookFilePath() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void addPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBook(ReadOnlyAddressBook newData) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - throw new AssertionError("This method should not be called."); - } - - @Override - public boolean hasPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void deletePerson(Person target) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ObservableList getFilteredPersonList() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - throw new AssertionError("This method should not be called."); - } - } - - /** - * A Model stub that contains a single person. - */ - private class ModelStubWithPerson extends ModelStub { - private final Person person; - - ModelStubWithPerson(Person person) { - requireNonNull(person); - this.person = person; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return this.person.isSamePerson(person); - } - } - - /** - * A Model stub that always accept the person being added. - */ - private class ModelStubAcceptingPersonAdded extends ModelStub { - final ArrayList personsAdded = new ArrayList<>(); - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return personsAdded.stream().anyMatch(person::isSamePerson); - } - - @Override - public void addPerson(Person person) { - requireNonNull(person); - personsAdded.add(person); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return new AddressBook(); - } - } - -} diff --git a/src/test/java/seedu/address/logic/commands/AddExamCommandTest.java b/src/test/java/seedu/address/logic/commands/AddExamCommandTest.java new file mode 100644 index 00000000000..5dbdfc81de5 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddExamCommandTest.java @@ -0,0 +1,317 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EXAM; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Criteria; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; +import seedu.address.testutil.ExamBuilder; + +public class AddExamCommandTest { + @Test + public void constructor_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddExamCommand(null)); + } + @Test + public void execute_examAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingExamAdded modelStub = new ModelStubAcceptingExamAdded(); + Exam validExam = new ExamBuilder().build(); + CommandResult commandResult = new AddExamCommand(validExam).execute(modelStub); + assertEquals(String.format(AddExamCommand.MESSAGE_SUCCESS, validExam), commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validExam), modelStub.examsAdded); + } + @Test + public void execute_duplicateExam_throwsCommandException() { + Exam validExam = new ExamBuilder().build(); + AddExamCommand addExamCommand = new AddExamCommand(validExam); + ModelStub modelStub = new ModelStubWithExam(validExam); + assertThrows(CommandException.class, MESSAGE_DUPLICATE_EXAM, () -> addExamCommand.execute(modelStub)); + } + + @Test + public void execute_moduleNotFound_throwsCommandException() { + Exam validExam = new ExamBuilder().build(); + AddExamCommand addExamCommand = new AddExamCommand(validExam); + ModelStubWithNoModules modelStub = new ModelStubWithNoModules(); + assertThrows(CommandException.class, MESSAGE_MODULE_NOT_FOUND, () -> addExamCommand.execute(modelStub)); + } + + @Test + public void equals() { + Exam examOne = new ExamBuilder().withModule("CS2030s") + .withDate("02-02-2024").withDescription("exam one").build(); + Exam examTwo = new ExamBuilder().withModule("CS2040s") + .withDate("02-06-2026").withDescription("exam two").build(); + Exam examOneWithDiffDate = new ExamBuilder().withModule("CS2030s") + .withDate("02-06-2026").withDescription("exam one").build(); + Exam examOneWithDiffModule = new ExamBuilder().withModule("CS2040s") + .withDate("02-02-2024").withDescription("exam one").build(); + Exam examOneWithDiffDescription = new ExamBuilder().withModule("CS2030s") + .withDate("02-02-2024").withDescription("exam one one").build(); + + AddExamCommand addExamOneCommand = new AddExamCommand(examOne); + AddExamCommand addExamTwoCommand = new AddExamCommand(examTwo); + AddExamCommand addExamOneWithDiffDateCommand = new AddExamCommand(examOneWithDiffDate); + AddExamCommand addExamOneWithDiffModCommand = new AddExamCommand(examOneWithDiffModule); + AddExamCommand addExamOneWithDiffDescCommand = new AddExamCommand(examOneWithDiffDescription); + assertTrue(addExamOneCommand.equals(addExamOneCommand)); + AddExamCommand addExamOneCommandCopy = new AddExamCommand(examOne); + assertTrue(addExamOneCommand.equals(addExamOneCommandCopy)); + assertFalse(addExamOneCommand.equals(1)); + assertFalse(addExamOneCommand.equals(null)); + assertFalse(addExamOneCommand.equals(addExamTwoCommand)); + assertFalse(addExamOneCommand.equals(addExamOneWithDiffDateCommand)); + assertFalse(addExamOneCommand.equals(addExamOneWithDiffDescCommand)); + assertFalse(addExamOneCommand.equals(addExamOneWithDiffModCommand)); + } + /** + * A default model stub that have all of the methods failing. + */ + + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + @Override + public Path getAddressBookFilePath() { + throw new AssertionError("This method should not be called."); + } + @Override + public void setAddressBookFilePath(Path addressBookFilePath) { + throw new AssertionError("This method should not be called."); + } + @Override + public void addModule(Module module) { + throw new AssertionError("This method should not even be called."); + } + @Override + public void setAddressBook(ReadOnlyAddressBook newData) { + throw new AssertionError("This method should not be called."); + } + @Override + public ReadOnlyAddressBook getAddressBook() { + throw new AssertionError("This method should not be called."); + } + @Override + public boolean hasModule(Module module) { + throw new AssertionError("This method should not be called"); + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortTaskList(Criteria criteria) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void unlinkTasksFromExam(Exam exam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasExam(Exam exam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteExam(Exam target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasExamWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addExam(Exam exam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredExamList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredExamList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + public void updateExamFieldForTask(Exam previousExam, Exam newExam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean isExamLinkedToTask(Exam examToEdit) { + throw new AssertionError("This method should not be called."); + } + + public void updateModuleFieldForTask(Module previousModule, Module newModule) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateModuleFieldForExam(Module previousModule, Module newModule) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTasksWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteExamsWithModule(Module module) { + + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredModuleList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTaskList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTask(Task task) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTask(Task task) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTask(Task target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteModule(Module target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTaskWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void replaceTask(Task task, Task editedTask, boolean isSameTask) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void replaceModule(Module module, Module editedModule) { + throw new AssertionError("This method should not be called."); + } + } + /** + * A Model stub that contains a single exam. + */ + private class ModelStubWithExam extends ModelStub { + private final Exam exam; + ModelStubWithExam(Exam exam) { + requireNonNull(exam); + this.exam = exam; + } + @Override + public boolean hasExam(Exam exam) { + requireNonNull(exam); + return this.exam.isSameExam(exam); + } + @Override + public boolean hasModule(Module module) { + return true; + } + } + /** + * A Model stub that always accept the exam being added. + */ + private class ModelStubAcceptingExamAdded extends ModelStub { + final ArrayList examsAdded = new ArrayList<>(); + @Override + public boolean hasExam(Exam exam) { + requireNonNull(exam); + return examsAdded.stream().anyMatch(exam::isSameExam); + } + @Override + public boolean hasModule(Module module) { + return true; + } + @Override + public void addExam(Exam exam) { + requireNonNull(exam); + examsAdded.add(exam); + } + } + + private class ModelStubWithNoModules extends ModelStub { + final ArrayList examsAdded = new ArrayList<>(); + + @Override + public boolean hasExam(Exam exam) { + requireNonNull(exam); + return examsAdded.stream().anyMatch(exam::isSameExam); + } + + @Override + public boolean hasModule(Module module) { + return false; + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddModuleCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddModuleCommandIntegrationTest.java new file mode 100644 index 00000000000..f2d199f823a --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddModuleCommandIntegrationTest.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.module.Module; +import seedu.address.testutil.ModuleBuilder; + + +public class AddModuleCommandIntegrationTest { + private Model model; + + @BeforeEach + public void initialize() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @Test + public void execute_addUniqueModule_success() { + Module module = new ModuleBuilder().withModuleCode("CS9999").build(); + AddModuleCommand addModuleCommand = new AddModuleCommand(module); + String expectedMessage = AddModuleCommand.MODULE_ADDED_SUCCESS; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.addModule(module); + assertCommandSuccess(addModuleCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_addSameModule_throwsCommandException() { + model.addModule(new ModuleBuilder().withModuleCode("CS2111").build()); + Module module = new ModuleBuilder().withModuleCode("CS2111").build(); + //Checks that model has module + assertTrue(model.hasModule(module)); + AddModuleCommand addModuleCommand = new AddModuleCommand(module); + String expectedMessage = AddModuleCommand.DUPLICATE_MODULE_DETECTED; + + assertCommandFailure(addModuleCommand, model, expectedMessage); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/AddModuleCommandTest.java b/src/test/java/seedu/address/logic/commands/AddModuleCommandTest.java new file mode 100644 index 00000000000..8c801136e74 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddModuleCommandTest.java @@ -0,0 +1,296 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Criteria; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.testutil.ModuleBuilder; + +/** + * Unit testing for AddModuleCommandTest + */ +public class AddModuleCommandTest { + @Test + public void constructor_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddModuleCommand(null)); + } + + @Test + public void execute_stubAlreadyContainsModule_throwsCommandException() { + Module module = new ModuleBuilder().build(); + AddModuleCommand addModuleCommand = new AddModuleCommand(module); + StubAlreadyContainsModule modStub = new StubAlreadyContainsModule(module); + assertThrows(CommandException.class, () -> addModuleCommand.execute(modStub)); + } + + @Test + public void execute_stubCanTakeInModule_success() throws CommandException { + StubCanTakeInModule stubCanTakeInModule = new StubCanTakeInModule(); + Module moduleBeingAdded = new ModuleBuilder().build(); + CommandResult commandResult = new AddModuleCommand(moduleBeingAdded) + .execute(stubCanTakeInModule); + assertEquals(AddModuleCommand.MODULE_ADDED_SUCCESS, commandResult.getFeedbackToUser()); + assertEquals(List.of(moduleBeingAdded), stubCanTakeInModule.modules); + } + + @Test + public void testEquals() { + Module module = new ModuleBuilder().build(); + Module secondModule = new ModuleBuilder().withModuleCode("CS1101S").build(); + AddModuleCommand addModuleCommand = new AddModuleCommand(module); + AddModuleCommand addModuleCommandCopy = new AddModuleCommand(module); + AddModuleCommand differentAddModuleCommand = new AddModuleCommand(secondModule); + + //Same AddModuleCommand object (Itself) + assertTrue(addModuleCommand.equals(addModuleCommand)); + + //Different AddModuleCommand but same module code + assertTrue(addModuleCommand.equals(addModuleCommandCopy)); + + //Different AddModuleCommand objects but different module code + assertFalse(addModuleCommand.equals(differentAddModuleCommand)); + + //Different Object types + assertFalse(addModuleCommand.equals(182817)); + + //Null value + assertFalse(addModuleCommand.equals(null)); + } + + private class ModuleModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This should never be called"); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This should never be called"); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This should never be called"); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This should never be called"); + } + + @Override + public Path getAddressBookFilePath() { + throw new AssertionError("This should never be called"); + } + + @Override + public void setAddressBookFilePath(Path addressBookFilePath) { + throw new AssertionError("This should never be called"); + } + + @Override + public void setAddressBook(ReadOnlyAddressBook addressBook) { + throw new AssertionError("This should never be called"); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + throw new AssertionError("This should never be called"); + } + + @Override + public void addModule(Module module) { + throw new AssertionError("This should never be called"); + } + + @Override + public ObservableList getFilteredModuleList() { + throw new AssertionError("This should never be called"); + } + + @Override + public boolean hasTask(Task task) { + throw new AssertionError("This should never be called"); + } + + @Override + public boolean hasTaskWithModule(Module module) { + throw new AssertionError("This should never be called"); + } + + @Override + public void addTask(Task task) { + throw new AssertionError("This should never be called"); + } + + @Override + public void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException { + throw new AssertionError("This should never be called"); + } + + @Override + public ObservableList getFilteredTaskList() { + throw new AssertionError("This should never be called"); + } + + @Override + public boolean hasModule(Module module) { + throw new AssertionError("This should never be called"); + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + throw new AssertionError("This should never be called"); + } + + @Override + public void deleteTask(Task target) { + throw new AssertionError("This should never be called"); + } + + @Override + public void deleteModule(Module target) { + throw new AssertionError("This should never be called"); + } + + @Override + public void replaceModule(Module target, Module editedModule) { + throw new AssertionError("This should never be called"); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + throw new AssertionError("This should never be called"); + } + + @Override + public void sortTaskList(Criteria criteria) { + throw new AssertionError("This should never be called"); + } + + @Override + public void unlinkTasksFromExam(Exam exam) { + throw new AssertionError("This should never be called"); + } + + @Override + public boolean hasExam(Exam exam) { + throw new AssertionError("This should never be called"); + } + + @Override + public void deleteExam(Exam target) { + throw new AssertionError("This should never be called"); + } + + @Override + public boolean hasExamWithModule(Module module) { + throw new AssertionError("This should never be called"); + } + + @Override + public void addExam(Exam exam) { + throw new AssertionError("This should never be called"); + } + + @Override + public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException { + throw new AssertionError("This should never be called"); + } + + @Override + public ObservableList getFilteredExamList() { + throw new AssertionError("This should never be called"); + } + + @Override + public void updateFilteredExamList(Predicate predicate) { + throw new AssertionError("This should never be called"); + } + + @Override + public void updateExamFieldForTask(Exam previousExam, Exam newExam) { + throw new AssertionError("This should never be called"); + } + + @Override + public boolean isExamLinkedToTask(Exam examToEdit) { + throw new AssertionError("This should never be called"); + } + + @Override + public void updateModuleFieldForTask(Module previousModule, Module newModule) { + throw new AssertionError("This should never be called"); + } + + @Override + public void updateModuleFieldForExam(Module previousModule, Module newModule) { + throw new AssertionError("This should never be called"); + } + + @Override + public void deleteTasksWithModule(Module module) { + throw new AssertionError("This should never be called"); + } + + @Override + public void deleteExamsWithModule(Module module) { + throw new AssertionError("This should never be called"); + } + } + + private class StubAlreadyContainsModule extends ModuleModelStub { + private final Module stubModule; + public StubAlreadyContainsModule(Module module) { + requireNonNull(module); + stubModule = module; + } + + @Override + public boolean hasModule(Module module) { + requireNonNull(module); + return stubModule.isSameModule(module); + } + } + + private class StubCanTakeInModule extends ModuleModelStub { + private final ArrayList modules = new ArrayList<>(); + + @Override + public boolean hasModule(Module module) { + requireNonNull(module); + return modules.stream().anyMatch(module::isSameModule); + } + + @Override + public void addModule(Module moduleAdded) { + requireNonNull(moduleAdded); + modules.add(moduleAdded); + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java b/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java new file mode 100644 index 00000000000..a631ea9371a --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java @@ -0,0 +1,182 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_DEADLINE_TAG_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_LAST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_PRIORITY_TAG_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.tag.exceptions.BothTagsCannotBeNullException; +import seedu.address.model.task.Task; +import seedu.address.testutil.DeadlineTagBuilder; +import seedu.address.testutil.PriorityTagBuilder; +import seedu.address.testutil.TaskBuilder; + +/** + * Integration test for AddTagCommandTest together with some unit testing + */ +public class AddTagCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void constructor_nullIndex_throwsNullPointerException() { + PriorityTag priorityTag = new PriorityTagBuilder().build(); + assertThrows(NullPointerException.class, () -> new AddTagCommand(priorityTag, null, null)); + } + + @Test + public void constructor_bothTagsNull_throwsBothTagsCannotBeNullException() { + assertThrows(BothTagsCannotBeNullException.class, () -> new AddTagCommand(null, null, + INDEX_FIRST_TASK)); + } + + @Test + public void execute_validIndexWithValidPriorityTagUnfilteredList_success() { + //Choose lower bound of partition for [1..size of list] + Task task = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + PriorityTag priorityTag = new PriorityTagBuilder().build(); + assertFalse(task.hasPriorityTag()); + AddTagCommand addTagCommand = new AddTagCommand(priorityTag, null, INDEX_FIRST_TASK); + String expectedSuccessMessage = AddTagCommand.TAG_ADDED_SUCCESS; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithPriorityTag = new TaskBuilder(task).withPriorityTag(priorityTag).build(); + expectedModel.replaceTask(task, taskWithPriorityTag, true); + + assertCommandSuccess(addTagCommand, model, expectedSuccessMessage, expectedModel); + + } + + @Test + public void execute_validIndexUpperBoundWithValidDeadlineTagUnfilteredList_success() { + //Choose upper bound of partition for [1..size of list] + Task task = model.getFilteredTaskList().get(INDEX_LAST_TASK.getZeroBased()); + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + assertFalse(task.hasDeadlineTag()); + AddTagCommand addTagCommand = new AddTagCommand(null, deadlineTag, INDEX_LAST_TASK); + String expectedSuccessMessage = AddTagCommand.TAG_ADDED_SUCCESS; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithDeadlineTag = new TaskBuilder(task).withDeadlineTag(deadlineTag).build(); + expectedModel.replaceTask(task, taskWithDeadlineTag, true); + + assertCommandSuccess(addTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_invalidIndexWithValidPriorityTagUnfilteredList_throwsCommandException() { + //Choose lower bound of partition [size of list + 1...INT_MAX] + Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + PriorityTag priorityTag = new PriorityTagBuilder().build(); + AddTagCommand addTagCommand = new AddTagCommand(priorityTag, null, outOfBoundsIndex); + String expectedMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(addTagCommand, model, expectedMessage); + } + + @Test + public void execute_priorityTagAlreadyExists_throwsCommandException() { + PriorityTag priorityTag = new PriorityTagBuilder().build(); + AddTagCommand addTagCommand = new AddTagCommand(priorityTag, null, INDEX_PRIORITY_TAG_TASK); + String expectedMessage = AddTagCommand.PRIORITY_TAG_ALREADY_EXIST; + assertCommandFailure(addTagCommand, model, expectedMessage); + } + + @Test + public void execute_deadlineTagAlreadyExists_throwsCommandException() { + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + AddTagCommand addTagCommand = new AddTagCommand(null, deadlineTag, INDEX_DEADLINE_TAG_TASK); + String expectedMessage = AddTagCommand.DEADLINE_TAG_ALREADY_EXIST; + assertCommandFailure(addTagCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexWithValidDeadlineTagFilteredList_success() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + Task task = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + //Ensures that task does not have deadline tag before adding it + assertFalse(task.hasDeadlineTag()); + AddTagCommand addTagCommand = new AddTagCommand(null, deadlineTag, INDEX_FIRST_TASK); + String expectedSuccessMessage = AddTagCommand.TAG_ADDED_SUCCESS; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithDeadlineTag = new TaskBuilder(task).withDeadlineTag(deadlineTag).build(); + expectedModel.replaceTask(task, taskWithDeadlineTag, true); + assertCommandSuccess(addTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_invalidIndexWithValidPriorityTagFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + //Since model's filtered list only has 1 task, index "2" is out of bounds + Index outOfBoundsIndex = INDEX_SECOND_TASK; + //Ensures that out of bounds index is still within range of address book task list + assertTrue(outOfBoundsIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + PriorityTag priorityTag = new PriorityTagBuilder().build(); + AddTagCommand addTagCommand = new AddTagCommand(priorityTag, null, outOfBoundsIndex); + String expectedMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(addTagCommand, model, expectedMessage); + } + + @Test + public void testEquals() { + PriorityTag firstPriorityTag = new PriorityTagBuilder().build(); + DeadlineTag firstDeadlineTag = new DeadlineTagBuilder().build(); + Index firstIndex = Index.fromOneBased(1); + Index secondIndex = Index.fromOneBased(2); + PriorityTag secondPriorityTag = new PriorityTagBuilder().withStatus("LOW").build(); + LocalDate date = LocalDate.parse("25-11-2024", DateTimeFormatter.ofPattern("dd-MM-yyyy")); + DeadlineTag secondDeadlineTag = new DeadlineTagBuilder().withDeadline(date).build(); + + AddTagCommand firstAddTagCommand = new AddTagCommand(firstPriorityTag, firstDeadlineTag, firstIndex); + AddTagCommand firstAddTagCommandCopy = new AddTagCommand(firstPriorityTag, firstDeadlineTag, firstIndex); + AddTagCommand secondAddTagCommand = new AddTagCommand(secondPriorityTag, firstDeadlineTag, firstIndex); + AddTagCommand thirdAddTagCommand = new AddTagCommand(firstPriorityTag, secondDeadlineTag, firstIndex); + AddTagCommand fourthAddTagCommand = new AddTagCommand(firstPriorityTag, firstDeadlineTag, secondIndex); + + //Equals to itself + assertTrue(firstAddTagCommand.equals(firstAddTagCommand)); + + //Equals to another AddTagCommand with the same fields + assertTrue(firstAddTagCommand.equals(firstAddTagCommandCopy)); + + //Not equals to another AddTagCommand with different priority tag + assertFalse(firstAddTagCommand.equals(secondAddTagCommand)); + + //Not equals to another AddTagCommand with different deadline tag + assertFalse(firstAddTagCommand.equals(thirdAddTagCommand)); + + //Not equal to another AddTagCommand with different index + assertFalse(firstAddTagCommand.equals(fourthAddTagCommand)); + + //Not equal to null + assertFalse(firstAddTagCommand.equals(null)); + + //Not equal to different object types + assertFalse(firstAddTagCommand.equals(219129)); + + } + +} + diff --git a/src/test/java/seedu/address/logic/commands/AddTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/AddTaskCommandTest.java new file mode 100644 index 00000000000..aae5417ea2e --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddTaskCommandTest.java @@ -0,0 +1,324 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_TASK; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND; +import static seedu.address.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Criteria; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; +import seedu.address.testutil.TaskBuilder; + +public class AddTaskCommandTest { + @Test + public void constructor_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddTaskCommand(null)); + } + + @Test + public void execute_taskAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingTaskAdded modelstub = new ModelStubAcceptingTaskAdded(); + Task validTask = new TaskBuilder().build(); + CommandResult commandResult = new AddTaskCommand(validTask).execute(modelstub); + assertEquals(String.format(AddTaskCommand.MESSAGE_SUCCESS, validTask), commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validTask), modelstub.tasksAdded); + } + + @Test + public void execute_duplicateTask_throwsCommandException() { + Task validTask = new TaskBuilder().build(); + AddTaskCommand addTaskCommand = new AddTaskCommand(validTask); + ModelStub modelStub = new ModelStubWithTask(validTask); + assertThrows(CommandException.class, MESSAGE_DUPLICATE_TASK, () -> addTaskCommand.execute(modelStub)); + } + + @Test + public void execute_moduleNotFound_throwsCommandException() { + Task validTask = new TaskBuilder().build(); + AddTaskCommand addTaskCommand = new AddTaskCommand(validTask); + ModelStubWithNoModules modelStub = new ModelStubWithNoModules(); + assertThrows(CommandException.class, MESSAGE_MODULE_NOT_FOUND, () -> addTaskCommand.execute(modelStub)); + } + + @Test + public void equals() { + Task task1 = new TaskBuilder().withTaskDescription("Task 1").build(); + Task task2 = new TaskBuilder().withTaskDescription("Task 2").build(); + AddTaskCommand addTask1Command = new AddTaskCommand(task1); + AddTaskCommand addTask2Command = new AddTaskCommand(task2); + //same object -> returns true + assertTrue(addTask1Command.equals(addTask1Command)); + //same values -> returns true + AddTaskCommand addTask1CommandCopy = new AddTaskCommand(task1); + assertTrue(addTask1Command.equals(addTask1CommandCopy)); + // different types -> returns false + assertFalse(addTask1Command.equals(1)); + // null -> returns false + assertFalse(addTask1Command.equals(null)); + // different task -> returns false + assertFalse(addTask1Command.equals(addTask2Command)); + } + + /** + * A default model stub that have all the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + @Override + public Path getAddressBookFilePath() { + throw new AssertionError("This method should not be called."); + } + @Override + public void setAddressBookFilePath(Path addressBookFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addModule(Module module) { + throw new AssertionError("This method should not even be called."); + } + @Override + public void setAddressBook(ReadOnlyAddressBook newData) { + throw new AssertionError("This method should not be called."); + } + @Override + public ReadOnlyAddressBook getAddressBook() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasModule(Module module) { + throw new AssertionError("This method should not be called"); + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void sortTaskList(Criteria criteria) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void unlinkTasksFromExam(Exam exam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasExam(Exam exam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteExam(Exam target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasExamWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addExam(Exam exam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredExamList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredExamList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateExamFieldForTask(Exam previousExam, Exam newExam) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean isExamLinkedToTask(Exam examToEdit) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateModuleFieldForTask(Module previousModule, Module newModule) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateModuleFieldForExam(Module previousModule, Module newModule) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTasksWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteExamsWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredModuleList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTaskList() { + throw new AssertionError("This method should not be called."); + } + + + @Override + public boolean hasTask(Task task) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTask(Task task) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTask(Task target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteModule(Module target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTaskWithModule(Module module) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void replaceTask(Task task, Task editedTask, boolean isSameTask) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void replaceModule(Module module, Module editedModule) { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single task. + */ + private class ModelStubWithTask extends ModelStub { + private final Task task; + + ModelStubWithTask(Task task) { + requireNonNull(task); + this.task = task; + } + + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return this.task.isSameTask(task); + } + + @Override + public boolean hasModule(Module module) { + return true; + } + } + + /** + * A Model stub that always accept the task being added. + */ + private class ModelStubAcceptingTaskAdded extends ModelStub { + final ArrayList tasksAdded = new ArrayList<>(); + + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return tasksAdded.stream().anyMatch(task::isSameTask); + } + + @Override + public boolean hasModule(Module module) { + return true; + } + + @Override + public void addTask(Task task) { + requireNonNull(task); + tasksAdded.add(task); + } + } + + /** + * A Model stub with no modules. + */ + private class ModelStubWithNoModules extends ModelStub { + final ArrayList tasksAdded = new ArrayList<>(); + + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return tasksAdded.stream().anyMatch(task::isSameTask); + } + + @Override + public boolean hasModule(Module module) { + return false; + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearAllCommandTest.java similarity index 54% rename from src/test/java/seedu/address/logic/commands/ClearCommandTest.java rename to src/test/java/seedu/address/logic/commands/ClearAllCommandTest.java index 80d9110c03a..33c5963ef6a 100644 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ClearAllCommandTest.java @@ -1,23 +1,24 @@ package seedu.address.logic.commands; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; import org.junit.jupiter.api.Test; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; -public class ClearCommandTest { - +public class ClearAllCommandTest { @Test - public void execute_emptyAddressBook_success() { + public void execute_emptyAddressBook_throwsCommandException() { Model model = new ModelManager(); - Model expectedModel = new ModelManager(); - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + assertThrows(CommandException.class, ClearAllCommand.MESSAGE_ALL_LISTS_ALREADY_EMPTY, () -> + new ClearAllCommand().execute(model)); } @Test @@ -26,7 +27,6 @@ public void execute_nonEmptyAddressBook_success() { Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); expectedModel.setAddressBook(new AddressBook()); - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + assertCommandSuccess(new ClearAllCommand(), model, ClearAllCommand.MESSAGE_SUCCESS, expectedModel); } - } diff --git a/src/test/java/seedu/address/logic/commands/ClearTasksCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearTasksCommandTest.java new file mode 100644 index 00000000000..bb9399bcc7d --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ClearTasksCommandTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.util.ArrayList; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class ClearTasksCommandTest { + @Test + public void execute_emptyTaskList_throwsCommandException() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + model.updateFilteredTaskList(t -> false); + + assertThrows(CommandException.class, ClearTasksCommand.MESSAGE_TASK_LIST_ALREADY_EMPTY, () -> + new ClearTasksCommand().execute(model)); + } + + @Test + public void execute_nonEmptyTaskList_success() { + AddressBook addressBook = getTypicalAddressBook(); + Model model = new ModelManager(addressBook, new UserPrefs()); + + addressBook.setTasks(new ArrayList<>()); + addressBook.resetAllTaskCount(); + Model expectedModel = new ModelManager(addressBook, new UserPrefs()); + + assertCommandSuccess(new ClearTasksCommand(), model, ClearTasksCommand.MESSAGE_SUCCESS, expectedModel); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..148ee8c4198 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -2,71 +2,151 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_COMPLETE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_LINKED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME; import static seedu.address.testutil.Assert.assertThrows; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javafx.collections.ObservableList; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; +import seedu.address.model.task.Task; +import seedu.address.testutil.EditExamDescriptorBuilder; +import seedu.address.testutil.EditModuleDescriptorBuilder; +import seedu.address.testutil.EditTaskDescriptorBuilder; /** * Contains helper methods for testing commands. */ public class CommandTestUtil { - public static final String VALID_NAME_AMY = "Amy Bee"; - public static final String VALID_NAME_BOB = "Bob Choo"; - public static final String VALID_PHONE_AMY = "11111111"; - public static final String VALID_PHONE_BOB = "22222222"; - public static final String VALID_EMAIL_AMY = "amy@example.com"; - public static final String VALID_EMAIL_BOB = "bob@example.com"; - public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; - public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; - public static final String VALID_TAG_HUSBAND = "husband"; - public static final String VALID_TAG_FRIEND = "friend"; - - public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; - public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; - public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY; - public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB; - public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY; - public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB; - public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; - public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; - public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; - public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; - - public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names - public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones - public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol - public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses - public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String VALID_DESCRIPTION_EXAMONE = "Exam one"; + public static final String VALID_DESCRIPTION_EXAMTWO = "Exam two"; + public static final String VALID_MODULE_EXAMONE = "CS2030S"; + public static final String VALID_MODULE_EXAMTWO = "CS2040S"; + public static final String VALID_DATE_EXAMONE = "20-08-2023"; + public static final String VALID_DATE_EXAMTWO = "20-10-2023"; + + + public static final String VALID_MODULE_CODE_ONE = "CS2030S"; + public static final String VALID_MODULE_CODE_TWO = "CS3217"; + public static final String VALID_MODULE_NAME_ONE = "Module One"; + public static final String VALID_MODULE_NAME_TWO = "Module Two"; + public static final int VALID_MODULE_CREDIT_ONE = 4; + public static final int VALID_MODULE_CREDIT_TWO = 5; + + public static final String MODULE_CREDIT_ONE = " " + PREFIX_MOD_CREDIT + VALID_MODULE_CREDIT_ONE; + public static final String MODULE_CREDIT_TWO = " " + PREFIX_MOD_CREDIT + VALID_MODULE_CREDIT_TWO; + public static final String MODULE_NAME_ONE = " " + PREFIX_MOD_NAME + VALID_MODULE_NAME_ONE; + public static final String MODULE_NAME_TWO = " " + PREFIX_MOD_NAME + VALID_MODULE_NAME_TWO; + public static final String MODULE_CODE_ONE = " " + PREFIX_MOD_CODE + VALID_MODULE_CODE_ONE; + public static final String MODULE_CODE_TWO = " " + PREFIX_MOD_CODE + VALID_MODULE_CODE_TWO; + + + public static final String INVALID_MODULE = " " + PREFIX_MODULE + "2001"; + + public static final String INVALID_MODULE_CODE = " " + PREFIX_MOD_CODE + "mod"; + public static final String INVALID_MODULE_NAME = " " + PREFIX_MOD_NAME + " "; + public static final String INVALID_MODULE_CREDIT = " " + PREFIX_MOD_CREDIT + "55"; + + public static final String INVALID_EXAM_DESCRIPTION = " " + PREFIX_EXAM_DESCRIPTION + " "; + public static final String INVALID_FORMAT_EXAM_DATEONE = " " + PREFIX_EXAM_DATE + "2022-08-20"; + public static final String INVALID_FORMAT_EXAM_DATETWO = " " + PREFIX_EXAM_DATE + "20-008-2024"; + public static final String INVALID_FORMAT_EXAM_DATETHREE = " " + PREFIX_EXAM_DATE + "20/08/2024"; + public static final String INVALID_FORMAT_EXAM_DATEFOUR = " " + PREFIX_EXAM_DATE + "20-13-2024"; + public static final String INVALID_FORMAT_EXAM_DATEFIVE = " " + PREFIX_EXAM_DATE + "32-01-2024"; + public static final String INVALID_FORMAT_EXAM_DATESIX = " " + PREFIX_EXAM_DATE + "00-01-2024"; + public static final String INVALID_FORMAT_EXAM_DATESEVEN = " " + PREFIX_EXAM_DATE + "aa-bb-cccc"; + + public static final String INVALID_EXAM_DATEONE = " " + PREFIX_EXAM_DATE + "29-02-2023"; + public static final String INVALID_EXAM_DATETWO = " " + PREFIX_EXAM_DATE + "31-11-2024"; + + public static final String INVALID_PAST_EXAMDATEONE = " " + PREFIX_EXAM_DATE + "20-01-2021"; + public static final String INVALID_PAST_EXAMDATETWO = " " + PREFIX_EXAM_DATE + "29-10-2022"; + + + + + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; - public static final EditCommand.EditPersonDescriptor DESC_AMY; - public static final EditCommand.EditPersonDescriptor DESC_BOB; + + public static final EditExamCommand.EditExamDescriptor DESC_EXAMONE; + public static final EditExamCommand.EditExamDescriptor DESC_EXAMTWO; + public static final EditModuleCommand.EditModuleDescriptor DESC_MODULEONE; + public static final EditModuleCommand.EditModuleDescriptor DESC_MODULETWO; static { - DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_FRIEND).build(); - DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + DESC_EXAMONE = new EditExamDescriptorBuilder().withModule(VALID_MODULE_EXAMONE) + .withDescription(VALID_DESCRIPTION_EXAMONE).withDate(VALID_DATE_EXAMONE).build(); + DESC_EXAMTWO = new EditExamDescriptorBuilder().withModule(VALID_MODULE_EXAMTWO) + .withDescription(VALID_DESCRIPTION_EXAMTWO).withDate(VALID_DATE_EXAMTWO).build(); + DESC_MODULEONE = new EditModuleDescriptorBuilder().withModuleCode(VALID_MODULE_CODE_ONE) + .withModuleName(VALID_MODULE_NAME_ONE).withModuleCredit(VALID_MODULE_CREDIT_ONE).build(); + DESC_MODULETWO = new EditModuleDescriptorBuilder().withModuleCode(VALID_MODULE_CODE_TWO) + .withModuleName(VALID_MODULE_NAME_TWO).withModuleCredit(VALID_MODULE_CREDIT_TWO).build(); + + } + + public static final String VALID_MODULE_CS2030 = "cs2030"; + public static final String VALID_MODULE_CS2040 = "cs2040"; + public static final String VALID_DESCRIPTION_DO_TUTORIAL = "do tutorial"; + public static final String VALID_DESCRIPTION_WATCH_LECTURE = "watch lecture"; + public static final String VALID_STATUS_YES = "y"; + public static final String VALID_STATUS_NO = "n"; + public static final String INVALID_STATUS_TRUE = "true"; + public static final String INVALID_MODULE_ABSENT_GEA1000 = "gea1000"; + + public static final String MODULE_DESC_CS2030 = " " + PREFIX_MODULE + "cs2030"; + public static final String MODULE_DESC_CS2040 = " " + PREFIX_MODULE + "cs2040"; + public static final String DESCRIPTION_DESC_DO_TUTORIAL = " " + PREFIX_DESCRIPTION + "do tutorial"; + public static final String DESCRIPTION_DESC_WATCH_LECTURE = " " + PREFIX_DESCRIPTION + "watch lecture"; + public static final String COMPLETION_STATUS_DESC_YES = " " + PREFIX_IS_COMPLETE + VALID_STATUS_YES; + public static final String COMPLETION_STATUS_DESC_NO = " " + PREFIX_IS_COMPLETE + VALID_STATUS_NO; + public static final String INVALID_COMPLETION_STATUS_DESC = " " + PREFIX_IS_COMPLETE + INVALID_STATUS_TRUE; + public static final String LINK_STATUS_DESC_YES = " " + PREFIX_IS_LINKED + VALID_STATUS_YES; + public static final String LINK_STATUS_DESC_NO = " " + PREFIX_IS_LINKED + VALID_STATUS_NO; + public static final String INVALID_LINK_STATUS_DESC = " " + PREFIX_IS_LINKED + INVALID_STATUS_TRUE; + public static final String DESCRIPTION_DESC_TASK_A = " " + PREFIX_DESCRIPTION + "Task A"; + + // module codes should be at least 6 characters long + public static final String INVALID_MODULE_DESC = " " + PREFIX_MODULE + "cs"; + // empty string not allowed for task descriptions + public static final String INVALID_TASK_DESCRIPTION_DESC = " " + PREFIX_DESCRIPTION + " "; + + public static final String EXAMMODULEONE = " " + PREFIX_MODULE + VALID_MODULE_EXAMONE; + public static final String EXAMMODULETWO = " " + PREFIX_MODULE + VALID_MODULE_EXAMTWO; + public static final String EXAMDESCRIPTIONONE = " " + PREFIX_EXAM_DESCRIPTION + VALID_DESCRIPTION_EXAMONE; + public static final String EXAMDESCRIPTIONTWO = " " + PREFIX_EXAM_DESCRIPTION + VALID_DESCRIPTION_EXAMTWO; + public static final String EXAMDATEONE = " " + PREFIX_EXAM_DATE + VALID_DATE_EXAMONE; + public static final String EXAMDATETWO = " " + PREFIX_EXAM_DATE + VALID_DATE_EXAMTWO; + + public static final EditTaskCommand.EditTaskDescriptor DESC_TUTORIAL; + public static final EditTaskCommand.EditTaskDescriptor DESC_LECTURE; + + static { + DESC_TUTORIAL = new EditTaskDescriptorBuilder().withModule(VALID_MODULE_CS2030) + .withDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + DESC_LECTURE = new EditTaskDescriptorBuilder().withModule(VALID_MODULE_CS2040) + .withDescription(VALID_DESCRIPTION_WATCH_LECTURE).build(); } /** @@ -75,7 +155,7 @@ public class CommandTestUtil { * - the {@code actualModel} matches {@code expectedModel} */ public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, - Model expectedModel) { + Model expectedModel) { try { CommandResult result = command.execute(actualModel); assertEquals(expectedCommandResult, result); @@ -90,11 +170,29 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm * that takes a string {@code expectedMessage}. */ public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, - Model expectedModel) { + Model expectedModel) { CommandResult expectedCommandResult = new CommandResult(expectedMessage); assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); } + + //must do after model itself executes. + /** + * Returns true when the tasks in the model has the same exams as the tasks in expected model + */ + public static void assertTasksHaveSameExamSuccess(Model actualModel, Model expectedModel) { + ObservableList filteredList = expectedModel.getFilteredTaskList(); + for (int i = 0; i < filteredList.size(); i++) { + Task expectedModelTask = expectedModel.getFilteredTaskList().get(i); + Task modelTask = actualModel.getFilteredTaskList().get(i); + assertTrue(expectedModelTask.isLinked() == modelTask.isLinked()); + if (expectedModelTask.isLinked()) { + assertTrue(expectedModelTask.getExam().equals(modelTask.getExam())); + } + } + } + + /** * Executes the given {@code command}, confirms that
* - a {@code CommandException} is thrown
@@ -105,24 +203,43 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri // we are unable to defensively copy the model for comparison later, so we can // only do so by copying its components. AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); - List expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList()); - + List expectedFilteredListForExams = new ArrayList<>(actualModel.getFilteredExamList()); + List expectedFilteredListForTasks = new ArrayList<>(actualModel.getFilteredTaskList()); + List expectedFilteredListForModules = new ArrayList<>(actualModel.getFilteredModuleList()); assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); assertEquals(expectedAddressBook, actualModel.getAddressBook()); - assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); + assertEquals(expectedFilteredListForExams, actualModel.getFilteredExamList()); + assertEquals(expectedFilteredListForTasks, actualModel.getFilteredTaskList()); + assertEquals(expectedFilteredListForModules, actualModel.getFilteredModuleList()); + } + + /** + * Updates {@code model}'s filtered list to show only the task at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showTaskAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredTaskList().size()); + + Task task = model.getFilteredTaskList().get(targetIndex.getZeroBased()); + final String[] description = {task.getDescription().description.toLowerCase()}; + model.updateFilteredTaskList(new DescriptionContainsKeywordsPredicate(Arrays.asList(description))); + + assertEquals(1, model.getFilteredTaskList().size()); + } + /** - * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the + * Updates {@code model}'s filtered module list to show only the module at the given {@code targetIndex} in the * {@code model}'s address book. */ - public static void showPersonAtIndex(Model model, Index targetIndex) { - assertTrue(targetIndex.getZeroBased() < model.getFilteredPersonList().size()); + public static void showModuleAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredModuleList().size()); - Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); - final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + Module module = model.getFilteredModuleList().get(targetIndex.getZeroBased()); + final String[] moduleCode = {module.getModuleCode().moduleCode.toLowerCase()}; + model.updateFilteredModuleList(new ModuleCodeContainsKeywordsPredicate(Arrays.asList(moduleCode))); - assertEquals(1, model.getFilteredPersonList().size()); + assertEquals(1, model.getFilteredModuleList().size()); } } diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java deleted file mode 100644 index 45a8c910ba1..00000000000 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; - -/** - * Contains integration tests (interaction with the Model) and unit tests for - * {@code DeleteCommand}. - */ -public class DeleteCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_validIndexUnfilteredList_success() { - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); - - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); - - ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.deletePerson(personToDelete); - - assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_invalidIndexUnfilteredList_throwsCommandException() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void execute_validIndexFilteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); - - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); - - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.deletePerson(personToDelete); - showNoPerson(expectedModel); - - assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_invalidIndexFilteredList_throwsCommandException() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); - - // same object -> returns true - assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); - - // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); - assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); - - // different types -> returns false - assertFalse(deleteFirstCommand.equals(1)); - - // null -> returns false - assertFalse(deleteFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); - } - - /** - * Updates {@code model}'s filtered list to show no one. - */ - private void showNoPerson(Model model) { - model.updateFilteredPersonList(p -> false); - - assertTrue(model.getFilteredPersonList().isEmpty()); - } -} diff --git a/src/test/java/seedu/address/logic/commands/DeleteExamCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteExamCommandTest.java new file mode 100644 index 00000000000..f31721206c8 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteExamCommandTest.java @@ -0,0 +1,83 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.FIRST_INDEX; +import static seedu.address.testutil.TypicalIndexes.SECOND_INDEX; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.task.Task; + +/** + * Contains integration tests (interaction with the Model) and unit tests for {@code DeleteExamCommand}. + */ +public class DeleteExamCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Exam examToDelete = model.getFilteredExamList().get(FIRST_INDEX.getZeroBased()); + Task taskToUnlink = model.getFilteredTaskList().get(SECOND_INDEX.getZeroBased()).linkTask(examToDelete); + assertTrue(taskToUnlink.isLinked()); + assertTrue(taskToUnlink.getExam().equals(examToDelete)); + + DeleteExamCommand deleteExamCommand = new DeleteExamCommand(FIRST_INDEX); + + String expectedMessage = String.format(DeleteExamCommand.MESSAGE_DELETE_EXAM_SUCCESS, examToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteExam(examToDelete); + + assertCommandSuccess(deleteExamCommand, model, expectedMessage, expectedModel); + assertFalse(model.getFilteredTaskList().get(SECOND_INDEX.getZeroBased()).isLinked()); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredExamList().size() + 1); + DeleteExamCommand deleteExamCommand = new DeleteExamCommand(outOfBoundIndex); + + assertCommandFailure(deleteExamCommand, model, String.format( + Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, model.getFilteredExamList().size() + 1)); + } + + @Test + public void execute_noExamInList_throwsCommandException() { + model.updateFilteredExamList(e -> false); + DeleteExamCommand deleteExamCommand = new DeleteExamCommand(FIRST_INDEX); + + assertCommandFailure(deleteExamCommand, model, DeleteExamCommand.MESSAGE_NO_EXAM_IN_LIST); + } + + @Test + public void equals() { + DeleteExamCommand deleteFirstExamCommand = new DeleteExamCommand(FIRST_INDEX); + DeleteExamCommand deleteSecondExamCommand = new DeleteExamCommand(SECOND_INDEX); + + // same object -> returns true + assertTrue(deleteFirstExamCommand.equals(deleteFirstExamCommand)); + + // same values -> returns true + DeleteExamCommand deleteFirstExamCommandCopy = new DeleteExamCommand(FIRST_INDEX); + assertTrue(deleteFirstExamCommand.equals(deleteFirstExamCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstExamCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstExamCommand.equals(null)); + + // different exam -> returns false + assertFalse(deleteFirstExamCommand.equals(deleteSecondExamCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/DeleteModuleCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteModuleCommandTest.java new file mode 100644 index 00000000000..656c3da2d87 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteModuleCommandTest.java @@ -0,0 +1,174 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showModuleAtIndex; +import static seedu.address.logic.commands.DeleteModuleCommand.MESSAGE_DELETE_MODULE_SUCCESS; +import static seedu.address.logic.commands.DeleteModuleCommand.MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_MODULE_RELATED_TO_EXAMS_AND_TASKS; +import static seedu.address.testutil.TypicalIndexes.INDEX_MODULE_RELATED_TO_EXAMS_NOT_TASKS; +import static seedu.address.testutil.TypicalIndexes.INDEX_MODULE_RELATED_TO_TASKS_NOT_EXAMS; +import static seedu.address.testutil.TypicalIndexes.INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_MODULE; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.module.Module; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code DeleteModuleCommand}. + */ +public class DeleteModuleCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredListWithModuleUnrelatedToAnyTaskOrExam_success() { + Module moduleToDelete = model.getFilteredModuleList() + .get(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM.getZeroBased()); + DeleteModuleCommand deleteModuleCommand = + new DeleteModuleCommand(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM); + + String expectedMessage = String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteModule(moduleToDelete); + + assertCommandSuccess(deleteModuleCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredListWithModuleUnrelatedToAnyTaskOrExam_throwsCommandException() { + int moduleListSizePlusOne = model.getFilteredModuleList().size() + 1; + Index outOfBoundIndex = Index.fromOneBased(moduleListSizePlusOne); + DeleteModuleCommand deleteModuleCommand = new DeleteModuleCommand(outOfBoundIndex); + String expectedMessage = + String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, moduleListSizePlusOne); + + assertCommandFailure(deleteModuleCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexFilteredListWithModuleUnrelatedToAnyTaskOrExam_success() { + showModuleAtIndex(model, INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM); + + Module moduleToDelete = + model.getFilteredModuleList().get(INDEX_FIRST_MODULE.getZeroBased()); + DeleteModuleCommand deleteModuleCommand = new DeleteModuleCommand(INDEX_FIRST_MODULE); + + String expectedMessage = String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteModule(moduleToDelete); + showNoModule(expectedModel); + + assertCommandSuccess(deleteModuleCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredListWithModuleUnrelatedToAnyTaskOrExam_throwsCommandException() { + showModuleAtIndex(model, INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM); + + Index outOfBoundIndex = INDEX_SECOND_MODULE; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getModuleList().size()); + + DeleteModuleCommand deleteModuleCommand = new DeleteModuleCommand(outOfBoundIndex); + + String expectedMessage = + String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, + model.getFilteredModuleList().size() + 1); + + assertCommandFailure(deleteModuleCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexUnfilteredListWithModuleRelatedToTasksNotExams_success() { + Module moduleToDelete = model.getFilteredModuleList() + .get(INDEX_MODULE_RELATED_TO_TASKS_NOT_EXAMS.getZeroBased()); + DeleteModuleCommand deleteModuleCommand = + new DeleteModuleCommand(INDEX_MODULE_RELATED_TO_TASKS_NOT_EXAMS); + + String expectedMessage = String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete) + + MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE; + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTasksWithModule(moduleToDelete); + expectedModel.deleteModule(moduleToDelete); + + assertCommandSuccess(deleteModuleCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredListWithModuleRelatedToExamsNotTasks_success() { + Module moduleToDelete = model.getFilteredModuleList() + .get(INDEX_MODULE_RELATED_TO_EXAMS_NOT_TASKS.getZeroBased()); + DeleteModuleCommand deleteModuleCommand = + new DeleteModuleCommand(INDEX_MODULE_RELATED_TO_EXAMS_NOT_TASKS); + + String expectedMessage = String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete) + + MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE; + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteExamsWithModule(moduleToDelete); + expectedModel.deleteModule(moduleToDelete); + + assertCommandSuccess(deleteModuleCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredListWithModuleRelatedToExamsAndTasks_success() { + Module moduleToDelete = model.getFilteredModuleList() + .get(INDEX_MODULE_RELATED_TO_EXAMS_AND_TASKS.getZeroBased()); + DeleteModuleCommand deleteModuleCommand = + new DeleteModuleCommand(INDEX_MODULE_RELATED_TO_EXAMS_AND_TASKS); + + String expectedMessage = String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete) + + MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE; + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTasksWithModule(moduleToDelete); + expectedModel.deleteExamsWithModule(moduleToDelete); + expectedModel.deleteModule(moduleToDelete); + + assertCommandSuccess(deleteModuleCommand, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + DeleteModuleCommand deleteModuleFirstCommand = new DeleteModuleCommand(INDEX_FIRST_MODULE); + DeleteModuleCommand deleteModuleSecondCommand = new DeleteModuleCommand(INDEX_SECOND_MODULE); + + // same object -> returns true + assertTrue(deleteModuleFirstCommand.equals(deleteModuleFirstCommand)); + + // same values -> returns true + DeleteModuleCommand deleteModuleFirstCommandCopy = new DeleteModuleCommand(INDEX_FIRST_MODULE); + assertTrue(deleteModuleFirstCommand.equals(deleteModuleFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteModuleFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteModuleFirstCommand.equals(null)); + + // different module index -> returns false + assertFalse(deleteModuleFirstCommand.equals(deleteModuleSecondCommand)); + } + + private void showNoModule(Model model) { + model.updateFilteredModuleList(m -> false); + + assertTrue(model.getFilteredModuleList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java new file mode 100644 index 00000000000..e7fb39711f4 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java @@ -0,0 +1,164 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_PRIORITY_TAG_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.Task; +import seedu.address.testutil.DeadlineTagBuilder; +import seedu.address.testutil.PriorityTagBuilder; +import seedu.address.testutil.TaskBuilder; + + + +/** + * Integration testing for DeleteTagCommandTest together with some unit testing conducted + */ +public class DeleteTagCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Set priorityKeyword = Set.of("priority"); + private final Set deadlineKeyword = Set.of("deadline"); + private final Set priorityAndDeadlineKeyword = Set.of("priority", "deadline"); + + @Test + public void constructor_nullIndex_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new DeleteTagCommand(null, priorityKeyword)); + } + + @Test + public void constructor_nullKeywordSet_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new DeleteTagCommand(INDEX_FIRST_TASK, + null)); + } + + @Test + public void execute_validIndexValidKeywordUnFilteredList_success() { + Task task = model.getFilteredTaskList().get(INDEX_PRIORITY_TAG_TASK.getZeroBased()); + //Checks that task contains priority keyword + assertTrue(task.hasPriorityTag()); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(INDEX_PRIORITY_TAG_TASK, priorityKeyword); + String expectedSuccessMessage = DeleteTagCommand.TAG_DELETED_SUCCESSFULLY; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithDeletedPriorityTag = new TaskBuilder(task).withPriorityTag(null).build(); + expectedModel.replaceTask(task, taskWithDeletedPriorityTag, true); + + assertCommandSuccess(deleteTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_invalidIndexValidKeywordUnfilteredList_throwsCommandException() { + Index outOfBoundsIndex = Index.fromOneBased(model.getAddressBook().getTaskList().size() + 1); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(outOfBoundsIndex, priorityKeyword); + String expectedFailureMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(deleteTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_noPriorityTagPresentUnfilteredList_throwsCommandException() { + Task firstTask = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + //Checks that first task has no priority tag + assertFalse(firstTask.hasPriorityTag()); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(INDEX_FIRST_TASK, priorityKeyword); + String expectedFailureMessage = DeleteTagCommand.NO_PRIORITY_TAG_TO_DELETE; + assertCommandFailure(deleteTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_noDeadlineTagPresentUnfilteredList_throwsCommandException() { + Task secondTask = model.getFilteredTaskList().get(INDEX_SECOND_TASK.getZeroBased()); + //Checks tat second task has no deadline tag + assertFalse(secondTask.hasDeadlineTag()); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(INDEX_SECOND_TASK, deadlineKeyword); + String expectedFailureMessage = DeleteTagCommand.NO_DEADLINE_TAG_TO_DELETE; + assertCommandFailure(deleteTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_validIndexValidKeywordFilteredList_success() { + showTaskAtIndex(model, INDEX_PRIORITY_TAG_TASK); + Task priorityTask = model.getFilteredTaskList().get(0); + //checks whether priority task has priority tag + assertTrue(priorityTask.hasPriorityTag()); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(INDEX_FIRST_TASK, priorityKeyword); + String expectedSuccessMessage = DeleteTagCommand.TAG_DELETED_SUCCESSFULLY; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithDeletedPriorityTag = new TaskBuilder(priorityTask).withPriorityTag(null).build(); + expectedModel.replaceTask(priorityTask, taskWithDeletedPriorityTag, true); + + assertCommandSuccess(deleteTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_invalidIndexValidKeywordFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_PRIORITY_TAG_TASK); + Index outOfBoundsIndex = INDEX_PRIORITY_TAG_TASK; + //Checks whether outOfBounds index is still within range of task list in address book + assertTrue(outOfBoundsIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(outOfBoundsIndex, priorityAndDeadlineKeyword); + String expectedFailureMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(deleteTagCommand, model, expectedFailureMessage); + } + + @Test + public void testEquals() { + PriorityTag firstPriorityTag = new PriorityTagBuilder().build(); + DeadlineTag firstDeadlineTag = new DeadlineTagBuilder().build(); + Index firstIndex = Index.fromOneBased(1); + Index secondIndex = Index.fromOneBased(2); + PriorityTag secondPriorityTag = new PriorityTagBuilder().withStatus("LOW").build(); + LocalDate date = LocalDate.parse("25-11-2024", DateTimeFormatter.ofPattern("dd-MM-yyyy")); + DeadlineTag secondDeadlineTag = new DeadlineTagBuilder().withDeadline(date).build(); + + EditTagCommand firstEditTagCommand = new EditTagCommand(firstIndex, firstPriorityTag, firstDeadlineTag); + EditTagCommand firstEditTagCommandCopy = new EditTagCommand(firstIndex, firstPriorityTag, firstDeadlineTag); + EditTagCommand secondEditTagCommand = new EditTagCommand(firstIndex, secondPriorityTag, firstDeadlineTag); + EditTagCommand thirdEditTagCommand = new EditTagCommand(firstIndex, firstPriorityTag, secondDeadlineTag); + EditTagCommand fourthEditTagCommand = new EditTagCommand(secondIndex, firstPriorityTag, firstDeadlineTag); + + //Equals to itself + assertTrue(firstEditTagCommand.equals(firstEditTagCommand)); + + //Equals to another EditTagCommand with the same fields + assertTrue(firstEditTagCommand.equals(firstEditTagCommandCopy)); + + //Not equals to another EditTagCommand with different priority tag + assertFalse(firstEditTagCommand.equals(secondEditTagCommand)); + + //Not equals to another EditTagCommand with different deadline tag + assertFalse(firstEditTagCommand.equals(thirdEditTagCommand)); + + //Not equal to another EditTagCommand with different index + assertFalse(firstEditTagCommand.equals(fourthEditTagCommand)); + + //Not equal to null + assertFalse(firstEditTagCommand.equals(null)); + + //Not equal to different object types + assertFalse(firstEditTagCommand.equals(219129)); + + } +} diff --git a/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java new file mode 100644 index 00000000000..eda1cf032c5 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java @@ -0,0 +1,131 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.logic.commands.DeleteTaskCommand.MESSAGE_DELETE_TASK_SUCCESS; +import static seedu.address.logic.commands.DeleteTaskCommand.MESSAGE_EXAM_LINK_DROPPED; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_LINKED_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code DeleteTaskCommand}. + */ +public class DeleteTaskCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(INDEX_FIRST_TASK); + + String expectedMessage = String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + + assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + int taskListSizePlusOne = model.getFilteredTaskList().size() + 1; + Index outOfBoundIndex = Index.fromOneBased(taskListSizePlusOne); + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(outOfBoundIndex); + String expectedMessage = + String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, taskListSizePlusOne); + + assertCommandFailure(deleteTaskCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexFilteredList_success() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteCommand = new DeleteTaskCommand(INDEX_FIRST_TASK); + + String expectedMessage = String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + showNoTask(expectedModel); + + assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(outOfBoundIndex); + + String expectedMessage = + String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(deleteTaskCommand, model, expectedMessage); + } + + @Test + public void execute_linkedTaskUnfilteredList_success() { + Task taskToDelete = model.getFilteredTaskList().get(INDEX_LINKED_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(INDEX_LINKED_TASK); + + String expectedMessage = String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete) + + MESSAGE_EXAM_LINK_DROPPED; + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + + assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + DeleteTaskCommand deleteTaskFirstCommand = new DeleteTaskCommand(INDEX_FIRST_TASK); + DeleteTaskCommand deleteTaskSecondCommand = new DeleteTaskCommand(INDEX_SECOND_TASK); + + // same object -> returns true + assertTrue(deleteTaskFirstCommand.equals(deleteTaskFirstCommand)); + + // same values -> returns true + DeleteTaskCommand deleteTaskFirstCommandCopy = new DeleteTaskCommand(INDEX_FIRST_TASK); + assertTrue(deleteTaskFirstCommand.equals(deleteTaskFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteTaskFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteTaskFirstCommand.equals(null)); + + // different task index -> returns false + assertFalse(deleteTaskFirstCommand.equals(deleteTaskSecondCommand)); + } + + + + private void showNoTask(Model model) { + model.updateFilteredTaskList(t -> false); + + assertTrue(model.getFilteredTaskList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java deleted file mode 100644 index 214c6c2507b..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) and unit tests for EditCommand. - */ -public class EditCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_someFieldsSpecifiedUnfilteredList_success() { - Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); - Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased()); - - PersonBuilder personInList = new PersonBuilder(lastPerson); - Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withTags(VALID_TAG_HUSBAND).build(); - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); - EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(lastPerson, editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_filteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_duplicatePersonUnfilteredList_failure() { - Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder(personInList).build()); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_invalidPersonIndexUnfilteredList_failure() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - /** - * Edit filtered list where index is larger than size of filtered list, - * but smaller than size of address book - */ - @Test - public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - EditCommand editCommand = new EditCommand(outOfBoundIndex, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY); - - // same values -> returns true - EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor); - assertTrue(standardCommand.equals(commandWithSameValues)); - - // same object -> returns true - assertTrue(standardCommand.equals(standardCommand)); - - // null -> returns false - assertFalse(standardCommand.equals(null)); - - // different types -> returns false - assertFalse(standardCommand.equals(new ClearCommand())); - - // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); - - // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/EditExamCommandTest.java b/src/test/java/seedu/address/logic/commands/EditExamCommandTest.java new file mode 100644 index 00000000000..5ec659d6e36 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditExamCommandTest.java @@ -0,0 +1,246 @@ +package seedu.address.logic.commands; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EXAM; +import static seedu.address.logic.commands.CommandTestUtil.DESC_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertTasksHaveSameExamSuccess; +import static seedu.address.logic.commands.EditExamCommand.MESSAGE_EXAM_NOT_EDITED; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_FORTH_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_FOURTEENTH_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRTEENTH_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_TWELVE_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.testutil.EditExamDescriptorBuilder; +import seedu.address.testutil.ExamBuilder; + + + + +/** + * Contains integration tests (interaction with the Model) and unit tests for EditExamCommand. + */ +public class EditExamCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredListWithoutAnyTaskLinked_success() { + Exam editedExam = new ExamBuilder(new Exam(new Module(new ModuleCode("CS2030S")), + new ExamDescription("Final Exam"), new ExamDate("01-11-2023"))).build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(editedExam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_FORTH_EXAM, descriptor); + + String expectedMessage = String.format(EditExamCommand.MESSAGE_EDIT_EXAM_SUCCESS, editedExam); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceExam(model.getFilteredExamList().get(3), editedExam, false); + assertCommandSuccess(editExamCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredListWithoutAnyTaskLinked_success() { + Exam editedExam = new Exam(new Module(new ModuleCode("CS2030S")), + new ExamDescription("CA PAPER"), model.getFilteredExamList().get(3).getExamDate()); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(editedExam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_FORTH_EXAM, descriptor); + String expectedMessage = String.format(EditExamCommand.MESSAGE_EDIT_EXAM_SUCCESS, editedExam); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceExam(model.getFilteredExamList().get(3), editedExam, false); + assertCommandSuccess(editExamCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + public void linkHelper(Model model, Index examIndex, Index taskIndex) throws CommandException { + LinkExamCommand linkExamCommand = new LinkExamCommand(examIndex, taskIndex); + linkExamCommand.execute(model); + } + + @Test + public void execute_allFieldsSpecifiedUnfilteredListWithoutTaskLinkedToTheSpecificExam_success() + throws CommandException { + Exam editedExam = new ExamBuilder(new Exam(new Module(new ModuleCode("CS2030S")), + new ExamDescription("Final Exam"), new ExamDate("01-11-2023"))).build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(editedExam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_FORTH_EXAM, descriptor); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + String expectedMessage = String.format(EditExamCommand.MESSAGE_EDIT_EXAM_SUCCESS, editedExam); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + Exam exam = model.getFilteredExamList().get(3); + expectedModel.replaceExam(exam, editedExam, false); + assertCommandSuccess(editExamCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + //tasks link to that exam but exam field edits only the description and date, module remains same. + @Test + public void execute_allFieldsSpecifiedUnfilteredListWithTasksLinkedToTheSpecificExam_success() + throws CommandException { + Exam editedExam = new ExamBuilder(new Exam(new Module(new ModuleCode("CS2030S")), + new ExamDescription("Final Exam"), new ExamDate("01-11-2023"))).build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(editedExam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_THIRD_EXAM, descriptor); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_FOURTEENTH_TASK); + String expectedMessage = String.format(EditExamCommand.MESSAGE_EDIT_EXAM_SUCCESS, editedExam); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_FOURTEENTH_TASK); + Exam exam = model.getFilteredExamList().get(2); + expectedModel.replaceExam(exam, editedExam, false); + expectedModel.updateExamFieldForTask(exam, editedExam); + assertCommandSuccess(editExamCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + + @Test + public void execute_changeModuleWithTasksLinkToExam_success() throws CommandException { + Exam editedExam = new ExamBuilder(new Exam(new Module(new ModuleCode("CS2040s")), + new ExamDescription("Exam one"), new ExamDate("20-08-2023"))).build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(editedExam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_THIRD_EXAM, descriptor); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_FOURTEENTH_TASK); + linkHelper(model, INDEX_FORTH_EXAM, INDEX_THIRTEENTH_TASK); + String expectedMessage = String.format(EditExamCommand.MESSAGE_EDIT_EXAM_SUCCESS + "\n" + + "Warning! All the tasks previously linked to this exam are now unlinked.", editedExam); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_FOURTEENTH_TASK); + linkHelper(expectedModel, INDEX_FORTH_EXAM, INDEX_THIRTEENTH_TASK); + Exam exam = model.getFilteredExamList().get(2); + expectedModel.replaceExam(exam, editedExam, false); + expectedModel.unlinkTasksFromExam(exam); + expectedModel.updateExamFieldForTask(exam, editedExam); + assertCommandSuccess(editExamCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_allFieldsChangeWithTasksLinkToExam_success() throws CommandException { + Exam editedExam = new ExamBuilder(new Exam(new Module(new ModuleCode("CS2040s")), + new ExamDescription("Midterm Paper"), new ExamDate("28-12-2023"))).build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(editedExam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_THIRD_EXAM, descriptor); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_FOURTEENTH_TASK); + linkHelper(model, INDEX_FORTH_EXAM, INDEX_THIRTEENTH_TASK); + String expectedMessage = String.format(EditExamCommand.MESSAGE_EDIT_EXAM_SUCCESS + "\n" + + "Warning! All the tasks previously linked to this exam are now unlinked.", editedExam); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_FOURTEENTH_TASK); + linkHelper(expectedModel, INDEX_FORTH_EXAM, INDEX_THIRTEENTH_TASK); + Exam exam = model.getFilteredExamList().get(2); + expectedModel.replaceExam(exam, editedExam, false); + expectedModel.unlinkTasksFromExam(exam); + expectedModel.updateExamFieldForTask(exam, editedExam); + assertCommandSuccess(editExamCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + //no tasks link to the exam but exam changed to be duplicate. + @Test + public void execute_duplicateExamUnfilteredList_failure() { + Exam exam = model.getFilteredExamList().get(INDEX_THIRD_EXAM.getZeroBased()); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(exam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_FORTH_EXAM, descriptor); + assertCommandFailure(editExamCommand, model, MESSAGE_DUPLICATE_EXAM); + } + + @Test + public void execute_duplicateExamUnfilteredListWithTasksLinked_failure() throws CommandException { + linkHelper(model, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + Exam exam = model.getFilteredExamList().get(INDEX_THIRD_EXAM.getZeroBased()); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(exam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_FORTH_EXAM, descriptor); + assertCommandFailure(editExamCommand, model, MESSAGE_DUPLICATE_EXAM); + assertTrue(model.getFilteredTaskList().get(11).getExam().isSameExam(exam)); + } + + @Test + public void execute_editExamToANonExistingModule_failure() throws CommandException { + Exam exam = new ExamBuilder(new Exam(new Module(new ModuleCode("XX2000s")), + new ExamDescription("Midterm Paper"), new ExamDate("28-12-2023"))).build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(exam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_THIRD_EXAM, descriptor); + linkHelper(model, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + linkHelper(expectedModel, INDEX_THIRD_EXAM, INDEX_TWELVE_TASK); + String expectedMessage = "This module does not exist"; + assertThrows(CommandException.class, expectedMessage, () -> editExamCommand.execute(model)); + assertEquals(expectedModel, model); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + //no fields change + @Test + public void execute_noFieldChangeUnfilteredList_failure() { + Exam exam = model.getFilteredExamList().get(INDEX_THIRD_EXAM.getZeroBased()); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(exam).build(); + EditExamCommand editExamCommand = new EditExamCommand(INDEX_THIRD_EXAM, descriptor); + assertCommandFailure(editExamCommand, model, MESSAGE_EXAM_NOT_EDITED); + } + + @Test + public void execute_invalidExamIndexUnfilteredList_failure() { + int index = model.getFilteredExamList().size() + 1; + Index outOfBoundIndex = Index.fromOneBased(index); + EditExamCommand.EditExamDescriptor descriptor = + new EditExamDescriptorBuilder().withDescription("Final exam paper").build(); + EditExamCommand editExamCommand = new EditExamCommand(outOfBoundIndex, descriptor); + assertCommandFailure(editExamCommand, model, + String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, index)); + } + + @Test + public void equals() { + final EditExamCommand standardCommand = + new EditExamCommand(INDEX_FIRST_EXAM, DESC_EXAMONE); + + // same values -> returns true + EditExamCommand.EditExamDescriptor copyDescriptor = + new EditExamCommand.EditExamDescriptor(DESC_EXAMONE); + EditExamCommand commandWithSameValues = + new EditExamCommand(INDEX_FIRST_EXAM, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearAllCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditExamCommand(INDEX_SECOND_EXAM, DESC_EXAMONE))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditExamCommand(INDEX_FIRST_EXAM, DESC_EXAMTWO))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditExamDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditExamDescriptorTest.java new file mode 100644 index 00000000000..bf9bd3a5e9c --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditExamDescriptorTest.java @@ -0,0 +1,53 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_EXAMTWO; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.EditExamDescriptorBuilder; + + + + +public class EditExamDescriptorTest { + @Test + public void equals() { + // same values -> returns true + EditExamCommand.EditExamDescriptor descriptorWithSameValues = + new EditExamCommand.EditExamDescriptor(DESC_EXAMONE); + assertTrue(DESC_EXAMONE.equals(descriptorWithSameValues)); + + // same object -> returns true + assertTrue(DESC_EXAMONE.equals(DESC_EXAMONE)); + + // null -> returns false + assertFalse(DESC_EXAMONE.equals(null)); + + // different types -> returns false + assertFalse(DESC_EXAMONE.equals(5)); + + // different values -> returns false + assertFalse(DESC_EXAMONE.equals(DESC_EXAMTWO)); + + // different description -> returns false + EditExamCommand.EditExamDescriptor editedExamOne = + new EditExamDescriptorBuilder(DESC_EXAMONE) + .withDescription(VALID_DESCRIPTION_EXAMTWO).build(); + assertFalse(DESC_EXAMONE.equals(editedExamOne)); + + // different module -> returns false + editedExamOne = new EditExamDescriptorBuilder(DESC_EXAMONE).withModule(VALID_MODULE_EXAMTWO).build(); + assertFalse(DESC_EXAMONE.equals(editedExamOne)); + + // different date -> returns false + editedExamOne = new EditExamDescriptorBuilder(DESC_EXAMONE).withDate(VALID_DATE_EXAMTWO).build(); + assertFalse(DESC_EXAMONE.equals(editedExamOne)); + + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditModuleCommandTest.java b/src/test/java/seedu/address/logic/commands/EditModuleCommandTest.java new file mode 100644 index 00000000000..c5974f0c1e8 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditModuleCommandTest.java @@ -0,0 +1,313 @@ +package seedu.address.logic.commands; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_MODULE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_MODULEONE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_MODULETWO; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertTasksHaveSameExamSuccess; +import static seedu.address.logic.commands.EditModuleCommand.MESSAGE_MODULE_NOT_EDITED; +import static seedu.address.logic.commands.EditModuleCommand.MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIFTH_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_FOURTH_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_SIXTH_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_MODULE; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; +import seedu.address.testutil.EditModuleDescriptorBuilder; +import seedu.address.testutil.ModuleBuilder; + + +/** + * Contains integration tests (interaction with the Model) and unit tests for EditModuleCommand. + */ +public class EditModuleCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredListWithModuleUnrelatedToAnyTaskAndExam_success() { + Module editedModule = new ModuleBuilder(new Module(new ModuleCode("CS3213"), + new ModuleName("Final Examinations"), new ModuleCredit(4))).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = + new EditModuleCommand(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(model.getFilteredModuleList() + .get(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM.getZeroBased()), editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredListWithModuleUnrelatedToAnyTaskAndExam_success() { + Module editedModule = new ModuleBuilder().withModuleCode("CS3213").withModuleName("Final Examinations").build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = + new EditModuleCommand(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(model.getFilteredModuleList() + .get(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM.getZeroBased()), editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + public void linkHelper(Model model, Index examIndex, Index taskIndex) throws CommandException { + LinkExamCommand linkExamCommand = new LinkExamCommand(examIndex, taskIndex); + linkExamCommand.execute(model); + } + + @Test + public void execute_allFieldsSpecifiedWithModuleRelatedToTasksNotExams_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_FOURTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleCode("CS3213") + .withModuleName("Programming").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FOURTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForTask(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedWithModuleRelatedToTasksNotExams_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_FOURTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleCode("CS3213") + .withModuleName("Programming").build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FOURTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForTask(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_moduleCodeFieldUnspecifiedWithModuleRelatedToTasksNotExams_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_FOURTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleName("Programming").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FOURTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_allFieldsSpecifiedWithModuleRelatedToExamsNotTasks_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_FIFTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleCode("CS3213") + .withModuleName("Programming").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FIFTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForExam(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedWithModuleRelatedToExamsNotTasks_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_FIFTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleCode("CS3213") + .withModuleName("Programming").build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FIFTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForExam(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_moduleCodeUnspecifiedWithModuleRelatedToExamsNotTasks_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_FIFTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleName("Programming").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FIFTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_allFieldsSpecifiedWithModuleRelatedToTasksAndExams_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_SIXTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleCode("CS3213") + .withModuleName("Programming").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_SIXTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForTask(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForExam(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedWithModuleRelatedToTasksAndExams_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_SIXTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleCode("CS3213").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_SIXTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()) + + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED; + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForTask(moduleToEdit, editedModule); + expectedModel.updateModuleFieldForExam(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + @Test + public void execute_moduleCodeFieldUnspecifiedWithModuleRelatedToTasksAndExams_success() { + Module moduleToEdit = model.getFilteredModuleList().get(INDEX_SIXTH_MODULE.getZeroBased()); + Module editedModule = new ModuleBuilder(moduleToEdit).withModuleName("Programming").withModuleCredit(5).build(); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(editedModule).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_SIXTH_MODULE, descriptor); + + String expectedMessage = String.format(EditModuleCommand.MESSAGE_EDIT_MODULE_SUCCESS, + editedModule.getModuleCode(), editedModule.getModuleName(), editedModule.getModuleCredit()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel.replaceModule(moduleToEdit, editedModule); + + assertCommandSuccess(editModuleCommand, model, expectedMessage, expectedModel); + assertTasksHaveSameExamSuccess(model, expectedModel); + } + + // edited module is a module in the module list unrelated to any tasks and exams + @Test + public void execute_duplicateModuleUnrelatedToAnyTasksAndExams_failure() { + Module module = model.getFilteredModuleList().get(INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM.getZeroBased()); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(module).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FIRST_MODULE, descriptor); + assertCommandFailure(editModuleCommand, model, MESSAGE_DUPLICATE_MODULE); + } + + // edited module is a module in the module list related to tasks and exams + @Test + public void execute_duplicateModuleRelatedToTasksAndExams_failure() { + Module module = model.getFilteredModuleList().get(INDEX_SIXTH_MODULE.getZeroBased()); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(module).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FIRST_MODULE, descriptor); + assertCommandFailure(editModuleCommand, model, MESSAGE_DUPLICATE_MODULE); + } + + + // no fields change + @Test + public void execute_noFieldChangeUnfilteredList_failure() { + Module module = model.getFilteredModuleList().get(INDEX_FIRST_MODULE.getZeroBased()); + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder(module).build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(INDEX_FIRST_MODULE, descriptor); + assertCommandFailure(editModuleCommand, model, MESSAGE_MODULE_NOT_EDITED); + } + + @Test + public void execute_invalidModuleIndexUnfilteredList_failure() { + int index = model.getFilteredModuleList().size() + 1; + Index outOfBoundIndex = Index.fromOneBased(index); + EditModuleCommand.EditModuleDescriptor descriptor = + new EditModuleDescriptorBuilder().withModuleName("Programming Methodology I").build(); + EditModuleCommand editModuleCommand = new EditModuleCommand(outOfBoundIndex, descriptor); + assertCommandFailure(editModuleCommand, model, + String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, index)); + } + + @Test + public void equals() { + final EditModuleCommand command = + new EditModuleCommand(INDEX_SECOND_MODULE, DESC_MODULEONE); + + // same values -> returns true + EditModuleCommand.EditModuleDescriptor copyDescriptor = + new EditModuleCommand.EditModuleDescriptor(DESC_MODULEONE); + EditModuleCommand commandWithSameValues = + new EditModuleCommand(INDEX_SECOND_MODULE, copyDescriptor); + assertTrue(command.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(command.equals(command)); + + // null -> returns false + assertFalse(command.equals(null)); + + // different types -> returns false + assertFalse(command.equals(10)); + + // different index -> returns false + assertFalse(command.equals(new EditModuleCommand(INDEX_THIRD_MODULE, DESC_MODULEONE))); + + // different descriptor -> returns false + assertFalse(command.equals(new EditModuleCommand(INDEX_SECOND_MODULE, DESC_MODULETWO))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditModuleDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditModuleDescriptorTest.java new file mode 100644 index 00000000000..a236cb96669 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditModuleDescriptorTest.java @@ -0,0 +1,52 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_MODULEONE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_MODULETWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CODE_TWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CREDIT_TWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_NAME_TWO; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.EditModuleDescriptorBuilder; + + +public class EditModuleDescriptorTest { + @Test + public void equals() { + // same values -> returns true + EditModuleCommand.EditModuleDescriptor descriptorWithSameValues = + new EditModuleCommand.EditModuleDescriptor(DESC_MODULEONE); + assertTrue(DESC_MODULEONE.equals(descriptorWithSameValues)); + + // same object -> returns true + assertTrue(DESC_MODULEONE.equals(DESC_MODULEONE)); + + // null -> returns false + assertFalse(DESC_MODULEONE.equals(null)); + + // different types -> returns false + assertFalse(DESC_MODULEONE.equals(3)); + + // different values -> returns false + assertFalse(DESC_MODULEONE.equals(DESC_MODULETWO)); + + // different description -> returns false + EditModuleCommand.EditModuleDescriptor editedModuleOne = + new EditModuleDescriptorBuilder(DESC_MODULEONE) + .withModuleCode(VALID_MODULE_CODE_TWO).build(); + assertFalse(DESC_MODULEONE.equals(editedModuleOne)); + + // different module name -> returns false + editedModuleOne = new EditModuleDescriptorBuilder(DESC_MODULEONE).withModuleName(VALID_MODULE_NAME_TWO).build(); + assertFalse(DESC_MODULEONE.equals(editedModuleOne)); + + // different module credit -> returns false + editedModuleOne = new EditModuleDescriptorBuilder(DESC_MODULEONE) + .withModuleCredit(VALID_MODULE_CREDIT_TWO).build(); + assertFalse(DESC_MODULEONE.equals(editedModuleOne)); + + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java deleted file mode 100644 index e0288792e72..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.testutil.EditPersonDescriptorBuilder; - -public class EditPersonDescriptorTest { - - @Test - public void equals() { - // same values -> returns true - EditPersonDescriptor descriptorWithSameValues = new EditPersonDescriptor(DESC_AMY); - assertTrue(DESC_AMY.equals(descriptorWithSameValues)); - - // same object -> returns true - assertTrue(DESC_AMY.equals(DESC_AMY)); - - // null -> returns false - assertFalse(DESC_AMY.equals(null)); - - // different types -> returns false - assertFalse(DESC_AMY.equals(5)); - - // different values -> returns false - assertFalse(DESC_AMY.equals(DESC_BOB)); - - // different name -> returns false - EditPersonDescriptor editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // different phone -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // different email -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // different address -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // different tags -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - } -} diff --git a/src/test/java/seedu/address/logic/commands/EditTagCommandTest.java b/src/test/java/seedu/address/logic/commands/EditTagCommandTest.java new file mode 100644 index 00000000000..f2d30c7e71f --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditTagCommandTest.java @@ -0,0 +1,236 @@ +package seedu.address.logic.commands; + +import static java.lang.Integer.MAX_VALUE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_DEADLINE_TAG_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_PRIORITY_TAG_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.tag.exceptions.BothTagsCannotBeNullException; +import seedu.address.model.task.Task; +import seedu.address.testutil.DeadlineTagBuilder; +import seedu.address.testutil.PriorityTagBuilder; +import seedu.address.testutil.TaskBuilder; + +/** + * Integration test for EditTagCommandTest together with some unit testing + */ +public class EditTagCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void constructor_nullIndex_throwsNullPointerException() { + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + assertThrows(NullPointerException.class, () -> new EditTagCommand(null, null, deadlineTag)); + } + + @Test + public void constructor_bothTagsNull_throwsBothTagsCannotBeNullException() { + assertThrows(BothTagsCannotBeNullException.class, () -> new EditTagCommand(INDEX_FIRST_TASK, + null, null)); + } + + @Test + public void execute_validIndexWithPriorityTagUnfilteredList_success() { + Task task = model.getFilteredTaskList().get(INDEX_PRIORITY_TAG_TASK.getZeroBased()); + PriorityTag priorityTag = new PriorityTagBuilder(getDefaultEditedPriority()).build(); + //Verifies that task must have priority tag + assertTrue(task.hasPriorityTag()); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_PRIORITY_TAG_TASK, priorityTag, null); + String expectedSuccessMessage = EditTagCommand.TAG_EDITED_SUCCESSFULLY; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithEditedPriorityTag = new TaskBuilder(task).withPriorityTag(priorityTag).build(); + expectedModel.replaceTask(task, taskWithEditedPriorityTag, true); + + assertCommandSuccess(editTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_validIndexWithDeadlineTagUnfilteredList_success() { + Task task = model.getFilteredTaskList().get(INDEX_DEADLINE_TAG_TASK.getZeroBased()); + DeadlineTag deadlineTag = new DeadlineTagBuilder(getDefaultEditedDeadline()).build(); + //Verifies that task has deadline tag + assertTrue(task.hasDeadlineTag()); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_DEADLINE_TAG_TASK, null, deadlineTag); + String expectedSuccessMessage = EditTagCommand.TAG_EDITED_SUCCESSFULLY; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithEditedDeadlineTag = new TaskBuilder(task).withDeadlineTag(deadlineTag).build(); + expectedModel.replaceTask(task, taskWithEditedDeadlineTag, true); + + assertCommandSuccess(editTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_invalidIndexWithPriorityTagUnfilteredList_success() { + //Choose Lower bound of partition for [size of list + 1...MAX_INT] + Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + PriorityTag priorityTag = new PriorityTagBuilder().build(); + EditTagCommand editTagCommand = new EditTagCommand(outOfBoundsIndex, priorityTag, null); + String expectedFailureMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_invalidIndexWithDeadlineTagUnfilteredList_success() { + //Choose upper bound of partition for [size of list + 1...MAX_INT] + Index outOfBoundsIndex = Index.fromOneBased(MAX_VALUE); + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + EditTagCommand editTagCommand = new EditTagCommand(outOfBoundsIndex, null, deadlineTag); + String expectedFailureMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_noPriorityTagPresent_throwsCommandException() { + PriorityTag priorityTag = new PriorityTagBuilder().build(); + Task task = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + //Checks that task does not have priority tag + assertFalse(task.hasPriorityTag()); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_FIRST_TASK, priorityTag, null); + String expectedFailureMessage = EditTagCommand.PRIORITY_TAG_DOES_NOT_EXIST; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_noDeadlineTagPresent_throwsCommandException() { + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + Task task = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + //Checks that task does not have deadline tag + assertFalse(task.hasDeadlineTag()); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_FIRST_TASK, null, deadlineTag); + String expectedFailureMessage = EditTagCommand.DEADLINE_TAG_DOES_NOT_EXIST; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_samePriorityTagUsed_throwsCommandException() { + PriorityTag priorityTag = new PriorityTagBuilder().build(); + Task task = model.getFilteredTaskList().get(INDEX_PRIORITY_TAG_TASK.getZeroBased()); + //Checks that task has priority tag + assertTrue(task.hasPriorityTag()); + //Checks that priority tag in task is same as priority tag created in this method + assertEquals(0, task.getPriorityTag().compareTo(priorityTag)); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_PRIORITY_TAG_TASK, priorityTag, null); + String expectedFailureMessage = EditTagCommand.PRIORITY_TAG_UNCHANGED; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_sameDeadlineTagUsed_throwsCommandException() { + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + Task task = model.getFilteredTaskList().get(INDEX_DEADLINE_TAG_TASK.getZeroBased()); + //Checks that task has deadline tag + assertTrue(task.hasDeadlineTag()); + //Checks that deadline tag in task is same as deadline tag created in this method + assertEquals(0, task.getDeadlineTag().compareTo(deadlineTag)); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_DEADLINE_TAG_TASK, null, deadlineTag); + String expectedFailureMessage = EditTagCommand.DEADLINE_TAG_UNCHANGED; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + @Test + public void execute_validIndexWithPriorityTagFilteredList_success() { + showTaskAtIndex(model, INDEX_PRIORITY_TAG_TASK); + Task task = model.getFilteredTaskList().get(0); + PriorityTag priorityTag = new PriorityTagBuilder(getDefaultEditedPriority()).build(); + //Checks that task has priority tag + assertTrue(task.hasPriorityTag()); + //Checks that task's priority tag is different from priority tag created in method + assertNotEquals(0, task.getPriorityTag().compareTo(priorityTag)); + EditTagCommand editTagCommand = new EditTagCommand(INDEX_FIRST_TASK, priorityTag, null); + String expectedSuccessMessage = EditTagCommand.TAG_EDITED_SUCCESSFULLY; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task taskWithEditedPriorityTag = new TaskBuilder(task).withPriorityTag(priorityTag).build(); + expectedModel.replaceTask(task, taskWithEditedPriorityTag, true); + assertCommandSuccess(editTagCommand, model, expectedSuccessMessage, expectedModel); + } + + @Test + public void execute_invalidIndexWithDeadlineTagFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_DEADLINE_TAG_TASK); + Task task = model.getFilteredTaskList().get(0); + DeadlineTag deadlineTag = new DeadlineTagBuilder(getDefaultEditedDeadline()).build(); + //Checks that task has deadline tag + assertTrue(task.hasDeadlineTag()); + //Checks that task's deadline tag is different from deadline tag created in method + assertNotEquals(0, task.getDeadlineTag().compareTo(deadlineTag)); + Index outOfBoundsIndex = INDEX_SECOND_TASK; + //Ensures outOfBoundsIndex still within range of address book + assertTrue(outOfBoundsIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + EditTagCommand editTagCommand = new EditTagCommand(outOfBoundsIndex, null, deadlineTag); + String expectedFailureMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(editTagCommand, model, expectedFailureMessage); + } + + private DeadlineTag getDefaultEditedDeadline() { + final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-uuuu"); + final LocalDate deadline = LocalDate.parse("12-12-2022", dateTimeFormatter); + return new DeadlineTagBuilder().withDeadline(deadline).build(); + } + + private PriorityTag getDefaultEditedPriority() { + return new PriorityTagBuilder().withStatus("MEDIUM").build(); + } + + @Test + public void testEquals() { + Index firstIndex = Index.fromOneBased(1); + Index secondIndex = Index.fromOneBased(2); + Set firstKeyword = Set.of("priority"); + Set secondKeyword = Set.of("deadline"); + + + DeleteTagCommand firstDeleteTagCommand = new DeleteTagCommand(firstIndex, firstKeyword); + DeleteTagCommand firstDeleteTagCommandCopy = new DeleteTagCommand(firstIndex, firstKeyword); + DeleteTagCommand secondDeleteTagCommand = new DeleteTagCommand(firstIndex, secondKeyword); + DeleteTagCommand thirdDeleteTagCommand = new DeleteTagCommand(secondIndex, firstKeyword); + + + + //Equals to itself + assertTrue(firstDeleteTagCommand.equals(firstDeleteTagCommand)); + + //Equals to another DeleteTagCommand with the same fields + assertTrue(firstDeleteTagCommand.equals(firstDeleteTagCommandCopy)); + + //Not equals to another DeleteTagCommand with different index + assertFalse(firstDeleteTagCommand.equals(secondDeleteTagCommand)); + + //Not equals to another DeleteTagCommand with different keyword + assertFalse(firstDeleteTagCommand.equals(thirdDeleteTagCommand)); + + //Not equal to null + assertFalse(firstDeleteTagCommand.equals(null)); + + //Not equal to different object types + assertFalse(firstDeleteTagCommand.equals(219129)); + + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/EditTaskCommandTest.java new file mode 100644 index 00000000000..d3548867934 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditTaskCommandTest.java @@ -0,0 +1,236 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_LECTURE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_ABSENT_GEA1000; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2030; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2040; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.logic.commands.EditTaskCommand.MESSAGE_DUPLICATE_TASK; +import static seedu.address.logic.commands.EditTaskCommand.MESSAGE_EXAM_UNLINKED; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_LINKED_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; +import seedu.address.testutil.EditTaskDescriptorBuilder; +import seedu.address.testutil.TaskBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code EditTaskCommand}. + */ +public class EditTaskCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_sameFieldsSpecified_failure() { + // same module and description specified + Task taskToEdit = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder(taskToEdit).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor); + + String expectedMessage = String.format(EditTaskCommand.MESSAGE_SAME_FIELDS_PROVIDED, taskToEdit); + + assertCommandFailure(editTaskCommand, model, expectedMessage); + + // same module specified + taskToEdit = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + descriptor = new EditTaskDescriptorBuilder() + .withModule(taskToEdit.getModule().getModuleCode().moduleCode).build(); + editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor); + + expectedMessage = String.format(EditTaskCommand.MESSAGE_SAME_FIELDS_PROVIDED, taskToEdit); + + assertCommandFailure(editTaskCommand, model, expectedMessage); + + // same description specified + taskToEdit = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + descriptor = new EditTaskDescriptorBuilder() + .withDescription(taskToEdit.getDescription().description).build(); + editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor); + + expectedMessage = String.format(EditTaskCommand.MESSAGE_SAME_FIELDS_PROVIDED, taskToEdit); + + assertCommandFailure(editTaskCommand, model, expectedMessage); + } + + @Test + public void execute_descriptionChanged_success() { + Task taskToEdit = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToEdit).withTaskDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + expectedModel.replaceTask(expectedModel.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()), + editedTask, false); + String expectedMessage = String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask); + + assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_moduleChangedForUnlinkedTask_success() { + Task taskToEdit = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withModule(VALID_MODULE_CS2040).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToEdit).withModule(VALID_MODULE_CS2040).build(); + expectedModel.replaceTask(expectedModel.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()), + editedTask, false); + String expectedMessage = String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask); + + assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_moduleChangedForLinkedTask_success() { + Task taskToEdit = model.getFilteredTaskList().get(INDEX_LINKED_TASK.getZeroBased()); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withModule(VALID_MODULE_CS2030).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_LINKED_TASK, descriptor); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToEdit).withExam(null).withModule(VALID_MODULE_CS2030).build(); + expectedModel.replaceTask(expectedModel.getFilteredTaskList().get(INDEX_LINKED_TASK.getZeroBased()), + editedTask, false); + + String expectedMessage = MESSAGE_EXAM_UNLINKED + + String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask); + + assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidModuleSpecified_failure() { + // module does not exist -> throws error + Index indexLastTask = Index.fromOneBased(model.getFilteredTaskList().size()); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withModule(INVALID_MODULE_ABSENT_GEA1000).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(indexLastTask, descriptor); + + assertCommandFailure(editTaskCommand, model, Messages.MESSAGE_MODULE_NOT_FOUND); + + + } + + @Test + public void execute_duplicateTask_failure() { + Task secondTask = model.getFilteredTaskList().get(INDEX_SECOND_TASK.getZeroBased()); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withDescription(secondTask.getDescription().description).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor); + + assertCommandFailure(editTaskCommand, model, MESSAGE_DUPLICATE_TASK); + } + + @Test + public void execute_filteredList_success() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Task taskInFilteredList = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, + new EditTaskDescriptorBuilder().withDescription(VALID_DESCRIPTION_DO_TUTORIAL).build()); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskInFilteredList) + .withTaskDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + expectedModel.replaceTask(expectedModel.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()), + editedTask, false); + String expectedMessage = String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask); + + assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicateTaskFilteredList_failure() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + // edit task in filtered list into a duplicate in address book + Task taskInList = model.getAddressBook().getTaskList().get(INDEX_SECOND_TASK.getZeroBased()); + EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, + new EditTaskDescriptorBuilder(taskInList).build()); + + assertCommandFailure(editTaskCommand, model, MESSAGE_DUPLICATE_TASK); + } + + @Test + public void execute_invalidIndexUnfilteredList_failure() { + // index "size of list + 1" chosen as boundary value for partition [size of list + 1...INT_MAX] + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withDescription(VALID_MODULE_CS2040).build(); + EditTaskCommand editTaskCommand = new EditTaskCommand(outOfBoundIndex, descriptor); + + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(editTaskCommand, model, expectedMessage); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidIndexFilteredList_failure() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + EditTaskCommand editTaskCommand = new EditTaskCommand(outOfBoundIndex, + new EditTaskDescriptorBuilder().withDescription(VALID_MODULE_CS2040).build()); + + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(editTaskCommand, model, expectedMessage); + } + + @Test + public void equals() { + final EditTaskCommand standardCommand = new EditTaskCommand(INDEX_FIRST_TASK, DESC_TUTORIAL); + + // same values -> returns true + EditTaskCommand.EditTaskDescriptor copyDescriptor = new EditTaskCommand.EditTaskDescriptor(DESC_TUTORIAL); + EditTaskCommand commandWithSameValues = new EditTaskCommand(INDEX_FIRST_TASK, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + //@@author tlx02 + // different types -> returns false + assertFalse(standardCommand.equals(new ClearTasksCommand())); + //@@author + + // different index -> returns false + assertFalse(standardCommand.equals(new EditTaskCommand(INDEX_SECOND_TASK, DESC_TUTORIAL))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditTaskCommand(INDEX_FIRST_TASK, DESC_LECTURE))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditTaskDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditTaskDescriptorTest.java new file mode 100644 index 00000000000..303c8b1bd9f --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditTaskDescriptorTest.java @@ -0,0 +1,48 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_LECTURE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_WATCH_LECTURE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2040; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.EditTaskDescriptorBuilder; + +/** + * Contains unit tests for {@code EditTaskDescriptor}. + */ +public class EditTaskDescriptorTest { + + @Test + public void equals() { + // same values -> returns true + EditTaskCommand.EditTaskDescriptor descriptorWithSameValues = + new EditTaskCommand.EditTaskDescriptor(DESC_TUTORIAL); + assertTrue(DESC_TUTORIAL.equals(descriptorWithSameValues)); + + // same object -> returns true + assertTrue(DESC_TUTORIAL.equals(DESC_TUTORIAL)); + + // null -> returns false + assertFalse(DESC_TUTORIAL.equals(null)); + + // different types -> returns false + assertFalse(DESC_TUTORIAL.equals(5)); + + // different values -> returns false + assertFalse(DESC_TUTORIAL.equals(DESC_LECTURE)); + + // different module -> returns false + EditTaskCommand.EditTaskDescriptor editedTask = new EditTaskDescriptorBuilder(DESC_TUTORIAL) + .withModule(VALID_MODULE_CS2040).build(); + assertFalse(DESC_TUTORIAL.equals(editedTask)); + + // different description -> returns false + editedTask = new EditTaskDescriptorBuilder(DESC_TUTORIAL) + .withDescription(VALID_DESCRIPTION_WATCH_LECTURE).build(); + assertFalse(DESC_TUTORIAL.equals(editedTask)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java index 9533c473875..b5d14d8b272 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java @@ -12,9 +12,11 @@ public class ExitCommandTest { private Model model = new ModelManager(); private Model expectedModel = new ModelManager(); + @Test public void execute_exit_success() { CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel); } + } diff --git a/src/test/java/seedu/address/logic/commands/FilterTasksCommandTest.java b/src/test/java/seedu/address/logic/commands/FilterTasksCommandTest.java new file mode 100644 index 00000000000..1a7de947023 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FilterTasksCommandTest.java @@ -0,0 +1,124 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.FilterPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FilterTasksCommand}. + */ +public class FilterTasksCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void executeValidModuleTasksFound_success() throws Exception { + FilterPredicate predicate = new FilterPredicate(Optional.of(CS2030), Optional.empty(), + Optional.empty()); + String expectedMessage = String.format(FilterTasksCommand.MESSAGE_SUCCESS, predicate); + + FilterTasksCommand filterTasksCommand = new FilterTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + filterTasksCommand.execute(model); + assertCommandSuccess(filterTasksCommand, model, expectedMessage, expectedModel); + } + + @Test + public void executeValidCompletionStatusTasksFound_success() throws Exception { + FilterPredicate predicate = new FilterPredicate(Optional.empty(), Optional.of(true), + Optional.empty()); + String expectedMessage = String.format(FilterTasksCommand.MESSAGE_SUCCESS, predicate); + + FilterTasksCommand filterTasksCommand = new FilterTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + filterTasksCommand.execute(model); + assertCommandSuccess(filterTasksCommand, model, expectedMessage, expectedModel); + } + + @Test + public void executeValidLinkStatusTasksFound_success() throws Exception { + FilterPredicate predicate = new FilterPredicate(Optional.empty(), Optional.empty(), + Optional.of(true)); + String expectedMessage = String.format(FilterTasksCommand.MESSAGE_SUCCESS, predicate); + + FilterTasksCommand filterTasksCommand = new FilterTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + filterTasksCommand.execute(model); + assertCommandSuccess(filterTasksCommand, model, expectedMessage, expectedModel); + } + + @Test + public void executeAllConditionsPresentTasksFound_success() throws Exception { + FilterPredicate predicate = new FilterPredicate(Optional.of(CS2030), Optional.of(false), + Optional.of(false)); + String expectedMessage = String.format(FilterTasksCommand.MESSAGE_SUCCESS, predicate); + + FilterTasksCommand filterTasksCommand = new FilterTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + filterTasksCommand.execute(model); + assertCommandSuccess(filterTasksCommand, model, expectedMessage, expectedModel); + } + + @Test + public void executeNoTasksFound_success() throws Exception { + FilterPredicate predicate = new FilterPredicate(Optional.of(CS2030), Optional.of(false), + Optional.of(true)); + String expectedMessage = String.format(FilterTasksCommand.MESSAGE_NO_RESULTS, predicate); + + FilterTasksCommand filterTasksCommand = new FilterTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + filterTasksCommand.execute(model); + assertCommandSuccess(filterTasksCommand, model, expectedMessage, expectedModel); + } + + @Test + public void executeInvalidModule_throwsCommandException() { + FilterPredicate predicate = new FilterPredicate(Optional.of(new Module(new ModuleCode("CS2000"))), + Optional.of(false), Optional.of(true)); + FilterTasksCommand filterTasksCommand = new FilterTasksCommand(predicate); + assertCommandFailure(filterTasksCommand, model, Messages.MESSAGE_MODULE_NOT_FOUND); + } + + @Test + public void equals() { + FilterPredicate firstPredicate = new FilterPredicate(Optional.of(CS2030), Optional.empty(), + Optional.empty()); + FilterPredicate secondPredicate = new FilterPredicate(Optional.of(CS2040), Optional.empty(), + Optional.empty()); + + FilterTasksCommand firstFilterTasksCommand = new FilterTasksCommand(firstPredicate); + FilterTasksCommand secondFilterTasksCommand = new FilterTasksCommand(secondPredicate); + + // same object -> returns true + assertTrue(firstFilterTasksCommand.equals(firstFilterTasksCommand)); + + // same values -> returns true + FilterTasksCommand firstFilterTasksCommandCopy = new FilterTasksCommand(firstPredicate); + assertTrue(firstFilterTasksCommand.equals(firstFilterTasksCommandCopy)); + + // different types -> returns false + assertFalse(firstFilterTasksCommand.equals(1)); + + // null -> returns false + assertFalse(firstFilterTasksCommand.equals(null)); + + // different predicate -> returns false + assertFalse(firstFilterTasksCommand.equals(secondFilterTasksCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java deleted file mode 100644 index 9b15db28bbb..00000000000 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Contains integration tests (interaction with the Model) for {@code FindCommand}. - */ -public class FindCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); - - FindCommand findFirstCommand = new FindCommand(firstPredicate); - FindCommand findSecondCommand = new FindCommand(secondPredicate); - - // same object -> returns true - assertTrue(findFirstCommand.equals(findFirstCommand)); - - // same values -> returns true - FindCommand findFirstCommandCopy = new FindCommand(firstPredicate); - assertTrue(findFirstCommand.equals(findFirstCommandCopy)); - - // different types -> returns false - assertFalse(findFirstCommand.equals(1)); - - // null -> returns false - assertFalse(findFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(findFirstCommand.equals(findSecondCommand)); - } - - @Test - public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredPersonList()); - } - - @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); - } - - /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. - */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); - } -} diff --git a/src/test/java/seedu/address/logic/commands/FindModulesCommandTest.java b/src/test/java/seedu/address/logic/commands/FindModulesCommandTest.java new file mode 100644 index 00000000000..fdca7a0113e --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindModulesCommandTest.java @@ -0,0 +1,142 @@ +//package seedu.address.logic.commands; +// +//import org.junit.jupiter.api.Test; +//import seedu.address.model.Model; +//import seedu.address.model.ModelManager; +//import seedu.address.model.UserPrefs; +//import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; +// +//import static org.junit.jupiter.api.Assertions.assertFalse; +//import static org.junit.jupiter.api.Assertions.assertTrue; +//import static seedu.address.commons.core.Messages.MESSAGE_MODULES_LISTED_OVERVIEW; +//import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +//import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; +// +//import java.util.Arrays; +//import java.util.Collections; +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_MODULES_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; + + +public class FindModulesCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + @Test + public void equals() { + ModuleCodeContainsKeywordsPredicate firstPredicate = + new ModuleCodeContainsKeywordsPredicate(Collections.singletonList("first")); + ModuleCodeContainsKeywordsPredicate secondPredicate = + new ModuleCodeContainsKeywordsPredicate(Collections.singletonList("second")); + + FindModulesCommand findModulesCommand = new FindModulesCommand(firstPredicate); + FindModulesCommand findModulesSecondCommand = new FindModulesCommand(secondPredicate); + + // same object -> returns true + assertTrue(findModulesCommand.equals(findModulesCommand)); + + // same values -> returns true + FindModulesCommand findModulesFirstCommandCopy = new FindModulesCommand(firstPredicate); + assertTrue(findModulesCommand.equals(findModulesFirstCommandCopy)); + + // different types -> returns false + assertFalse(findModulesCommand.equals(1)); + + // null -> returns false + assertFalse(findModulesCommand.equals(null)); + + assertFalse(findModulesCommand.equals(findModulesSecondCommand)); + } + + @Test + public void executeFullWord_moduleFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 1); + + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("CS2030S"); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executePartialStartingWord_modulesFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 4); + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("CS20"); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executePartialMiddleWord_modulesFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 2); + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("210 "); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executeModuleWithKeywordMixCase_modulesFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 2); + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("cS203"); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executeModulesWithAllUpperCase_moduleFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 1); + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("ma1521"); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + @Test + public void executeModulesWithAllUpperCaseWithKeywordMixCase_moduleFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 1); + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("Ma1521"); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executeNoMatchingModule_noModuleFound() { + String expectedMessage = String.format(MESSAGE_MODULES_LISTED_OVERVIEW, 0); + ModuleCodeContainsKeywordsPredicate predicate = preparePredicate("ZX2022"); + FindModulesCommand command = new FindModulesCommand(predicate); + expectedModel.updateFilteredModuleList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + + /** + * Parses {@code userInput} into a {@code ModuleCodeContainsKeywordsPredicate}. + */ + private ModuleCodeContainsKeywordsPredicate preparePredicate(String userInput) { + return new ModuleCodeContainsKeywordsPredicate(Arrays.asList(userInput.trim().toLowerCase())); + } +} diff --git a/src/test/java/seedu/address/logic/commands/FindTasksCommandTest.java b/src/test/java/seedu/address/logic/commands/FindTasksCommandTest.java new file mode 100644 index 00000000000..5548e07af43 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindTasksCommandTest.java @@ -0,0 +1,148 @@ +package seedu.address.logic.commands; + +//import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_TASKS_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; +//import static seedu.address.testutil.TypicalTasks.*; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; + + +/** + * Contains integration tests (interaction with the Model) for {@code FindTaskCommand}. + */ +public class FindTasksCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + @Test + public void equals() { + + DescriptionContainsKeywordsPredicate firstPredicate = + new DescriptionContainsKeywordsPredicate(Collections.singletonList("first")); + DescriptionContainsKeywordsPredicate secondPredicate = + new DescriptionContainsKeywordsPredicate(Collections.singletonList("second")); + + FindTasksCommand findTaskFirstCommand = new FindTasksCommand(firstPredicate); + FindTasksCommand findTaskSecondCommand = new FindTasksCommand(secondPredicate); + + // same object -> returns true + assertTrue(findTaskFirstCommand.equals(findTaskFirstCommand)); + + // same values -> returns true + FindTasksCommand findTaskFirstCommandCopy = new FindTasksCommand(firstPredicate); + assertTrue(findTaskFirstCommand.equals(findTaskFirstCommandCopy)); + + // different types -> returns false + assertFalse(findTaskFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findTaskFirstCommand.equals(null)); + + // different task -> returns false + assertFalse(findTaskFirstCommand.equals(findTaskSecondCommand)); + } + + + @Test + public void executeFullWord_taskFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 1); + + DescriptionContainsKeywordsPredicate predicate = preparePredicate("task one"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + + } + + @Test + public void executePartialStartingWord_tasksFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 11); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("Tas"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executePartialMiddleWord_tasksFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 12); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("as "); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executeTaskWithAllLowerCase_taskFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 1); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("HoMEWorK"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executeTaskWithAllUpperCase_taskFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 1); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("Past YeaR"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void executeTaskWithAllUpperCase_taskFound2() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 1); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("past year"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + @Test + public void executeNoMatchingTask_noTaskFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 0); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("ZXZ"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + + @Test + public void executePartialWordWithSpacing_taskFound() { + String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 1); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("SK on"); + FindTasksCommand command = new FindTasksCommand(predicate); + expectedModel.updateFilteredTaskList(predicate); + command.execute(model); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + + /** + * Parses {@code userInput} into a {@code DescriptionContainsKeywordsPredicate}. + */ + private DescriptionContainsKeywordsPredicate preparePredicate(String userInput) { + return new DescriptionContainsKeywordsPredicate(Arrays.asList(userInput.trim().toLowerCase())); + } +} + + diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 4904fc4352e..24f1081fb64 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -12,9 +12,11 @@ public class HelpCommandTest { private Model model = new ModelManager(); private Model expectedModel = new ModelManager(); + @Test public void execute_help_success() { CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false); assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel); } + } diff --git a/src/test/java/seedu/address/logic/commands/LinkExamCommandTest.java b/src/test/java/seedu/address/logic/commands/LinkExamCommandTest.java new file mode 100644 index 00000000000..8fa9fa1a815 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/LinkExamCommandTest.java @@ -0,0 +1,150 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_CS2040_FINAL; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_LINKED_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.task.Task; + +public class LinkExamCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void constructor_nullExamIndex_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new LinkExamCommand(null, + INDEX_FIRST_TASK)); + } + + @Test + public void constructor_nullTaskIndex_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new LinkExamCommand(INDEX_FIRST_EXAM, + null)); + } + + @Test + public void execute_validTaskIndexValidExamIndexUnfilteredList_success() { + Task task = model.getFilteredTaskList().get(INDEX_THIRD_TASK.getZeroBased()); + Exam exam = model.getFilteredExamList().get(INDEX_FIRST_EXAM.getZeroBased()); + + LinkExamCommand linkExamCommand = new LinkExamCommand(INDEX_FIRST_EXAM, INDEX_THIRD_TASK); + String expectedMessage = LinkExamCommand.EXAM_LINKED_SUCCESS; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task linkedTask = task.linkTask(exam); + expectedModel.replaceTask(task, linkedTask, true); + + assertCommandSuccess(linkExamCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidTaskIndexValidExamIndexUnfilteredList_throwsCommandException() { + Index outOfBoundsTaskIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + LinkExamCommand linkExamCommand = new LinkExamCommand(INDEX_FIRST_EXAM, outOfBoundsTaskIndex); + String expectedFailureMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(linkExamCommand, model, expectedFailureMessage); + } + + @Test + public void execute_validTaskIndexInvalidExamIndexUnfilteredList_throwsCommandException() { + Index outOfBoundsExamIndex = Index.fromOneBased(model.getFilteredExamList().size() + 1); + LinkExamCommand linkExamCommand = new LinkExamCommand(outOfBoundsExamIndex, INDEX_FIRST_TASK); + String expectedFailureMessage = Messages.MESSAGE_INVALID_EXAM_DISPLAYED_INDEX; + assertCommandFailure(linkExamCommand, model, expectedFailureMessage); + } + + @Test + public void execute_taskAlreadyLinked_throwsCommandException() { + String expectedMessage = LinkExamCommand.TASK_ALREADY_LINKED; + LinkExamCommand linkExamCommand = new LinkExamCommand(INDEX_CS2040_FINAL, INDEX_LINKED_TASK); + assertCommandFailure(linkExamCommand, model, expectedMessage); + } + + @Test + public void execute_taskDifferentModuleCodeFromExam_throwsCommandException() { + String expectedMessage = LinkExamCommand.DIFFERENT_MODULE_CODE; + LinkExamCommand linkExamCommand = new LinkExamCommand(INDEX_CS2040_FINAL, INDEX_FIRST_TASK); + assertCommandFailure(linkExamCommand, model, expectedMessage); + } + + @Test + public void execute_validTaskIndexValidExamIndexFilteredList_success() { + showTaskAtIndex(model, INDEX_THIRD_TASK); + Task task = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + Exam exam = model.getFilteredExamList().get(INDEX_FIRST_EXAM.getZeroBased()); + + LinkExamCommand linkExamCommand = new LinkExamCommand(INDEX_FIRST_EXAM, INDEX_FIRST_TASK); + String expectedMessage = LinkExamCommand.EXAM_LINKED_SUCCESS; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task linkedTask = task.linkTask(exam); + expectedModel.replaceTask(task, linkedTask, true); + + assertCommandSuccess(linkExamCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidTaskIndexValidExamIndexFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_THIRD_TASK); + Index outOfBoundsTaskIndex = INDEX_SECOND_TASK; + //Checks that out of bounds index is still within the range of the address book task list + assertTrue(outOfBoundsTaskIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + LinkExamCommand linkExamCommand = new LinkExamCommand(INDEX_FIRST_EXAM, outOfBoundsTaskIndex); + String expectedMessage = Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + assertCommandFailure(linkExamCommand, model, expectedMessage); + } + + @Test + public void testEquals() { + Index examIndex = INDEX_FIRST_EXAM; + Index taskIndex = INDEX_FIRST_TASK; + Index examIndexClone = Index.fromOneBased(examIndex.getOneBased()); + Index taskIndexClone = Index.fromOneBased(taskIndex.getOneBased()); + + LinkExamCommand linkExamCommand = new LinkExamCommand(examIndex, taskIndex); + LinkExamCommand linkExamCommandClone = new LinkExamCommand(examIndexClone, taskIndexClone); + LinkExamCommand linkExamCommandDifferentExamIndex = new LinkExamCommand(INDEX_SECOND_EXAM, INDEX_FIRST_TASK); + LinkExamCommand linkExamCommandDifferentTaskIndex = new LinkExamCommand(INDEX_FIRST_EXAM, INDEX_SECOND_TASK); + + //Same LinkExamCommand object + assertTrue(linkExamCommand.equals(linkExamCommand)); + + //Different LinkExamCommand object with same task and exam index + assertTrue(linkExamCommand.equals(linkExamCommandClone)); + + //Null value + assertFalse(linkExamCommand.equals(null)); + + //Different LinkExamCommand object with different exam index + assertFalse(linkExamCommand.equals(linkExamCommandDifferentExamIndex)); + + //Different LinkExamCommand object with different task index + assertFalse(linkExamCommand.equals(linkExamCommandDifferentTaskIndex)); + + //Different object type + assertFalse(linkExamCommand.equals(7382)); + } + + +} diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java deleted file mode 100644 index 435ff1f7275..00000000000 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - -/** - * Contains integration tests (interaction with the Model) and unit tests for ListCommand. - */ -public class ListCommandTest { - - private Model model; - private Model expectedModel; - - @BeforeEach - public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - } - - @Test - public void execute_listIsNotFiltered_showsSameList() { - assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); - } - - @Test - public void execute_listIsFiltered_showsEverything() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); - } -} diff --git a/src/test/java/seedu/address/logic/commands/ListExamTasksCommandTest.java b/src/test/java/seedu/address/logic/commands/ListExamTasksCommandTest.java new file mode 100644 index 00000000000..0b11276a95b --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListExamTasksCommandTest.java @@ -0,0 +1,104 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.FIRST_INDEX; +import static seedu.address.testutil.TypicalIndexes.SECOND_INDEX; +import static seedu.address.testutil.TypicalTasks.TASK_D; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.exam.Exam; +import seedu.address.model.task.TaskLinkedToExamPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code ListExamTasksCommand}. + */ +public class ListExamTasksCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredListMultipleTasksFound_success() { + Exam examToListTasks = model.getFilteredExamList().get(FIRST_INDEX.getZeroBased()); + String expectedMessage = String.format(ListExamTasksCommand.MESSAGE_SUCCESS, examToListTasks); + TaskLinkedToExamPredicate predicate = preparePredicate(examToListTasks); + + ListExamTasksCommand command = new ListExamTasksCommand(FIRST_INDEX); + expectedModel.updateFilteredTaskList(predicate); + + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(TASK_D), model.getFilteredTaskList()); + } + + @Test + public void execute_validIndexUnfilteredListNoTasksFound_success() { + Exam examToListTasks = model.getFilteredExamList().get(SECOND_INDEX.getZeroBased()); + String expectedMessage = String.format(ListExamTasksCommand.MESSAGE_NO_RESULTS, examToListTasks); + TaskLinkedToExamPredicate predicate = preparePredicate(examToListTasks); + + ListExamTasksCommand command = new ListExamTasksCommand(SECOND_INDEX); + expectedModel.updateFilteredTaskList(predicate); + + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.EMPTY_LIST, model.getFilteredTaskList()); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredExamList().size() + 1); + ListExamTasksCommand listExamTasksCommand = new ListExamTasksCommand(outOfBoundIndex); + + assertCommandFailure(listExamTasksCommand, model, String.format( + Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, model.getFilteredExamList().size() + 1)); + } + + @Test + public void execute_noExamInList_throwsCommandException() { + model.updateFilteredExamList(e -> false); + ListExamTasksCommand listExamTasksCommand = new ListExamTasksCommand(FIRST_INDEX); + + assertCommandFailure(listExamTasksCommand, model, ListExamTasksCommand.MESSAGE_NO_EXAM_IN_LIST); + } + + @Test + public void equals() { + ListExamTasksCommand listFirstExamTasksCommand = new ListExamTasksCommand(FIRST_INDEX); + ListExamTasksCommand listSecondExamTasksCommand = new ListExamTasksCommand(SECOND_INDEX); + + // same object -> returns true + assertTrue(listFirstExamTasksCommand.equals(listFirstExamTasksCommand)); + + // same values -> returns true + ListExamTasksCommand listFirstExamTasksCommandCopy = new ListExamTasksCommand(FIRST_INDEX); + assertTrue(listFirstExamTasksCommand.equals(listFirstExamTasksCommandCopy)); + + // different types -> returns false + assertFalse(listFirstExamTasksCommand.equals(1)); + + // null -> returns false + assertFalse(listFirstExamTasksCommand.equals(null)); + + // different exam -> returns false + assertFalse(listFirstExamTasksCommand.equals(listSecondExamTasksCommand)); + } + + /** + * Parses {@code userInput} into a {@code TaskLinkedToExamPredicate}. + */ + private TaskLinkedToExamPredicate preparePredicate(Exam exam) { + return new TaskLinkedToExamPredicate(exam); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ListModulesCommandTest.java b/src/test/java/seedu/address/logic/commands/ListModulesCommandTest.java new file mode 100644 index 00000000000..b5388e5127b --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListModulesCommandTest.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showModuleAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_MODULE; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.module.Module; + + + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListModulesCommand. + */ +public class ListModulesCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + class EmptyModelStub extends ModelManager { + @Override + public ObservableList getFilteredModuleList() { + return FXCollections.emptyObservableList(); + } + } + + @Test + public void execute_listModuleIsNotFiltered_showsSameList() { + assertCommandSuccess(new ListModulesCommand(), model, + ListModulesCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_emptyList() { + EmptyModelStub emptymodelStub = new EmptyModelStub(); + assertEquals(new ListModulesCommand().execute(emptymodelStub), + new CommandResult(ListModulesCommand.EMPTY_LIST)); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showModuleAtIndex(model, INDEX_THIRD_MODULE); + assertCommandSuccess(new ListModulesCommand(), model, ListModulesCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ListTasksCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTasksCommandTest.java new file mode 100644 index 00000000000..030821852d1 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListTasksCommandTest.java @@ -0,0 +1,56 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; + + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListTaskCommand. + */ +public class ListTasksCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + class EmptyModelStub extends ModelManager { + @Override + public ObservableList getFilteredTaskList() { + return FXCollections.emptyObservableList(); + } + } + + @Test + public void execute_listTaskIsNotFiltered_showsSameList() { + assertCommandSuccess(new ListTasksCommand(), model, ListTasksCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_emptyList() { + EmptyModelStub emptymodelStub = new EmptyModelStub(); + assertEquals(new ListTasksCommand().execute(emptymodelStub), new CommandResult(ListTasksCommand.EMPTY_LIST)); + } + @Test + public void execute_listIsFiltered_showsEverything() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + assertCommandSuccess(new ListTasksCommand(), model, ListTasksCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/MarkCommandTest.java b/src/test/java/seedu/address/logic/commands/MarkCommandTest.java new file mode 100644 index 00000000000..870b6003ed5 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/MarkCommandTest.java @@ -0,0 +1,125 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_MARKED_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; +import seedu.address.testutil.TaskBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code MarkCommand}. + */ +class MarkCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + // index "1" chosen as boundary value for the partition [1...size of list] + Task taskToMark = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + // ensures that the task is not marked + assertFalse(taskToMark.isComplete()); + + MarkCommand markCommand = new MarkCommand(INDEX_FIRST_TASK); + String expectedMessage = String.format(MarkCommand.MESSAGE_MARK_TASK_SUCCESS, taskToMark); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToMark).withStatus("complete").build(); + expectedModel.replaceTask(model.getFilteredTaskList().get(0), editedTask, true); + + assertCommandSuccess(markCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + // index "size of list + 1" chosen as boundary value for partition [size of list + 1...INT_MAX] + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + MarkCommand markCommand = new MarkCommand(outOfBoundIndex); + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(markCommand, model, expectedMessage); + } + + @Test + public void execute_invalidTaskUnfilteredList_throwsCommandException() { + // marked task -> throws error + MarkCommand markCommand = new MarkCommand(INDEX_MARKED_TASK); + String expectedMessage = MarkCommand.MESSAGE_TASK_ALREADY_MARKED; + + assertCommandFailure(markCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexFilteredList_success() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Task taskToMark = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + // ensures that the task is not marked + assertFalse(taskToMark.isComplete()); + + MarkCommand markCommand = new MarkCommand(INDEX_FIRST_TASK); + String expectedMessage = String.format(MarkCommand.MESSAGE_MARK_TASK_SUCCESS, taskToMark); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToMark).withStatus("complete").build(); + expectedModel.replaceTask(model.getFilteredTaskList().get(0), editedTask, true); + + assertCommandSuccess(markCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + // index "2" chosen as boundary value for partition [size of list + 1...INT_MAX] + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + MarkCommand markCommand = new MarkCommand(outOfBoundIndex); + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(markCommand, model, expectedMessage); + } + + @Test + public void equals() { + MarkCommand markFirstCommand = new MarkCommand(INDEX_FIRST_TASK); + MarkCommand markSecondCommand = new MarkCommand(INDEX_SECOND_TASK); + + // same object -> returns true + assertTrue(markFirstCommand.equals(markFirstCommand)); + + // same values -> returns true + MarkCommand markFirstCommandCopy = new MarkCommand(INDEX_FIRST_TASK); + assertTrue(markFirstCommand.equals(markFirstCommandCopy)); + + // different types -> returns false + assertFalse(markFirstCommand.equals(1)); + + // null -> returns false + assertFalse(markFirstCommand.equals(null)); + + // different index -> returns false + assertFalse(markFirstCommand.equals(markSecondCommand)); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/SortTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/SortTaskCommandTest.java new file mode 100644 index 00000000000..3fc8b159cd8 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/SortTaskCommandTest.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Criteria; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.testutil.CriteriaBuilder; + +/** + * Integration testing of SortTaskCommand together with some unit testing + */ +public class SortTaskCommandTest { + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new SortTaskCommand(null)); + } + + @Test + public void execute_validCriteriaUnFilteredList_success() { + Criteria criteria = new CriteriaBuilder().build(); + expectedModel.sortTaskList(criteria); + assertCommandSuccess(new SortTaskCommand(criteria), model, + SortTaskCommand.TASK_SORTED_SUCCESSFULLY, expectedModel); + } + + @Test + public void execute_validCriteriaFilteredList_success() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + showTaskAtIndex(expectedModel, INDEX_FIRST_TASK); + Criteria criteria = new CriteriaBuilder().withCriteria("description").build(); + expectedModel.sortTaskList(criteria); + + //Checks both have same number of filtered items + assertEquals(expectedModel.getFilteredTaskList().size(), model.getFilteredTaskList().size()); + //The model should sort what is shown + assertCommandSuccess(new SortTaskCommand(criteria), model, + SortTaskCommand.TASK_SORTED_SUCCESSFULLY, expectedModel); + } + + @Test + public void execute_validCriteriaEmptyList_throwsCommandException() { + Criteria criteria = new CriteriaBuilder().build(); + model = new ModelManager(); + String expectedMessage = SortTaskCommand.NO_TASK_TO_SORT; + assertCommandFailure(new SortTaskCommand(criteria), model, SortTaskCommand.NO_TASK_TO_SORT); + } + + @Test + public void testEquals() { + Criteria priorityCriteria = new CriteriaBuilder().build(); + Criteria deadlineCriteria = new CriteriaBuilder().withCriteria("deadline").build(); + SortTaskCommand sortTaskCommandPriority = new SortTaskCommand(priorityCriteria); + SortTaskCommand sortTaskCommandPriorityCopy = new SortTaskCommand(priorityCriteria); + SortTaskCommand sortTaskCommandDeadline = new SortTaskCommand(deadlineCriteria); + + //Checks that SortTaskCommand is same as itself + assertTrue(sortTaskCommandPriority.equals(sortTaskCommandPriority)); + + //Checks that SortTaskCommand is equal with another SortTaskCommand with same criteria + assertTrue(sortTaskCommandPriority.equals(sortTaskCommandPriorityCopy)); + + //Checks that SortTaskCommand is not equal to null + assertFalse(sortTaskCommandPriority.equals(null)); + + //Checks that SortTaskCommand is not equal to another SortTaskCommand with different criteria + assertFalse(sortTaskCommandPriority.equals(sortTaskCommandDeadline)); + + //Checks that SortTaskCommand is not equal to another object type + assertFalse(sortTaskCommandPriority.equals(5)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/UnlinkExamCommandTest.java b/src/test/java/seedu/address/logic/commands/UnlinkExamCommandTest.java new file mode 100644 index 00000000000..6529cc4a2d5 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/UnlinkExamCommandTest.java @@ -0,0 +1,133 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.TypicalIndexes.FIRST_INDEX; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_LINKED_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_UNLINKED_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; +import seedu.address.testutil.TaskBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code UnlinkExamCommand}. + */ +public class UnlinkExamCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Task taskToUnlink = model.getFilteredTaskList().get(INDEX_LINKED_TASK.getZeroBased()); + // ensures that the task is linked + assertTrue(taskToUnlink.isLinked()); + + UnlinkExamCommand unlinkExamCommand = new UnlinkExamCommand(INDEX_LINKED_TASK); + String expectedMessage = String.format(unlinkExamCommand.MESSAGE_EXAM_UNLINKED_SUCCESS, taskToUnlink); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToUnlink).withNoExam().build(); + expectedModel.replaceTask(model.getFilteredTaskList().get(INDEX_LINKED_TASK.getZeroBased()), editedTask, + true); + + assertCommandSuccess(unlinkExamCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + // index "size of list + 1" chosen as boundary value for partition [size of list + 1...INT_MAX] + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + UnlinkExamCommand unlinkExamCommand = new UnlinkExamCommand(outOfBoundIndex); + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(unlinkExamCommand, model, expectedMessage); + } + + @Test + public void execute_invalidTaskUnfilteredList_throwsCommandException() { + // unlinked task -> throws error + UnlinkExamCommand unlinkExamCommand = new UnlinkExamCommand(INDEX_UNLINKED_TASK); + String expectedMessage = unlinkExamCommand.MESSAGE_TASK_ALREADY_UNLINKED; + + assertCommandFailure(unlinkExamCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexFilteredList_success() { + showTaskAtIndex(model, INDEX_LINKED_TASK); + + Task taskToUnlink = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + // ensures that the task is linked + assertTrue(taskToUnlink.isLinked()); + + UnlinkExamCommand unlinkExamCommand = new UnlinkExamCommand(INDEX_FIRST_TASK); + String expectedMessage = String.format(UnlinkExamCommand.MESSAGE_EXAM_UNLINKED_SUCCESS, taskToUnlink); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToUnlink).withNoExam().build(); + expectedModel.replaceTask(model.getFilteredTaskList().get(0), editedTask, true); + + assertCommandSuccess(unlinkExamCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + // index "2" chosen as boundary value for partition [size of list + 1...INT_MAX] + showTaskAtIndex(model, INDEX_LINKED_TASK); + + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + UnlinkExamCommand unlinkExamCommand = new UnlinkExamCommand(outOfBoundIndex); + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(unlinkExamCommand, model, expectedMessage); + } + + @Test + public void execute_noTaskInList_throwsCommandException() { + model.updateFilteredTaskList(t -> false); + UnlinkExamCommand unlinkExamCommand = new UnlinkExamCommand(FIRST_INDEX); + + assertCommandFailure(unlinkExamCommand, model, UnlinkExamCommand.MESSAGE_NO_TASK_IN_LIST); + } + + @Test + public void equals() { + UnlinkExamCommand unlinkExamFirstCommand = new UnlinkExamCommand(INDEX_FIRST_TASK); + UnlinkExamCommand unlinkExamSecondCommand = new UnlinkExamCommand(INDEX_SECOND_TASK); + + // same object -> returns true + assertTrue(unlinkExamFirstCommand.equals(unlinkExamFirstCommand)); + + // same values -> returns true + UnlinkExamCommand unlinkExamFirstCommandCopy = new UnlinkExamCommand(INDEX_FIRST_TASK); + assertTrue(unlinkExamFirstCommand.equals(unlinkExamFirstCommandCopy)); + + // different types -> returns false + assertFalse(unlinkExamFirstCommand.equals(1)); + + // null -> returns false + assertFalse(unlinkExamFirstCommand.equals(null)); + + // different task -> returns false + assertFalse(unlinkExamFirstCommand.equals(unlinkExamSecondCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/UnmarkCommandTest.java b/src/test/java/seedu/address/logic/commands/UnmarkCommandTest.java new file mode 100644 index 00000000000..3328c22abed --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/UnmarkCommandTest.java @@ -0,0 +1,126 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_MARKED_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_UNMARKED_TASK; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; +import seedu.address.testutil.TaskBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code UnmarkCommand}. + */ +public class UnmarkCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Task taskToUnmark = model.getFilteredTaskList().get(INDEX_MARKED_TASK.getZeroBased()); + // ensures that the task is marked + assertTrue(taskToUnmark.isComplete()); + + UnmarkCommand unmarkCommand = new UnmarkCommand(INDEX_MARKED_TASK); + String expectedMessage = String.format(UnmarkCommand.MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToUnmark).withStatus("incomplete").build(); + expectedModel.replaceTask(model.getFilteredTaskList().get(INDEX_MARKED_TASK.getZeroBased()), + editedTask, true); + + assertCommandSuccess(unmarkCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + // index "size of list + 1" chosen as boundary value for partition [size of list + 1...INT_MAX] + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + UnmarkCommand unmarkCommand = new UnmarkCommand(outOfBoundIndex); + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(unmarkCommand, model, expectedMessage); + } + + @Test + public void execute_invalidTaskUnfilteredList_throwsCommandException() { + // unmarked task -> throws error + UnmarkCommand unmarkCommand = new UnmarkCommand(INDEX_UNMARKED_TASK); + String expectedMessage = UnmarkCommand.MESSAGE_TASK_ALREADY_UNMARKED; + + assertCommandFailure(unmarkCommand, model, expectedMessage); + } + + @Test + public void execute_validIndexFilteredList_success() { + showTaskAtIndex(model, INDEX_MARKED_TASK); + + Task taskToUnmark = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + // ensures that the task is marked + assertTrue(taskToUnmark.isComplete()); + + UnmarkCommand unmarkCommand = new UnmarkCommand(INDEX_FIRST_TASK); + String expectedMessage = String.format(UnmarkCommand.MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Task editedTask = new TaskBuilder(taskToUnmark).withStatus("incomplete").build(); + expectedModel.replaceTask(model.getFilteredTaskList().get(0), editedTask, true); + + assertCommandSuccess(unmarkCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + // index "2" chosen as boundary value for partition [size of list + 1...INT_MAX] + showTaskAtIndex(model, INDEX_MARKED_TASK); + + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + UnmarkCommand unmarkCommand = new UnmarkCommand(outOfBoundIndex); + String expectedMessage = String.format( + Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, model.getFilteredTaskList().size() + 1); + + assertCommandFailure(unmarkCommand, model, expectedMessage); + } + + @Test + public void equals() { + UnmarkCommand unmarkFirstCommand = new UnmarkCommand(INDEX_FIRST_TASK); + UnmarkCommand unmarkSecondCommand = new UnmarkCommand(INDEX_SECOND_TASK); + + // same object -> returns true + assertTrue(unmarkFirstCommand.equals(unmarkFirstCommand)); + + // same values -> returns true + UnmarkCommand unmarkFirstCommandCopy = new UnmarkCommand(INDEX_FIRST_TASK); + assertTrue(unmarkFirstCommand.equals(unmarkFirstCommandCopy)); + + // different types -> returns false + assertFalse(unmarkFirstCommand.equals(1)); + + // null -> returns false + assertFalse(unmarkFirstCommand.equals(null)); + + // different index -> returns false + assertFalse(unmarkFirstCommand.equals(unmarkSecondCommand)); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java deleted file mode 100644 index 5cf487d7ebb..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandParserTest { - private AddCommandParser parser = new AddCommandParser(); - - @Test - public void parse_allFieldsPresent_success() { - Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); - - // whitespace only preamble - assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple names - last name accepted - assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple phones - last phone accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple emails - last email accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple addresses - last address accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple tags - all accepted - Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); - } - - @Test - public void parse_optionalFieldsMissing_success() { - // zero tags - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); - assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, - new AddCommand(expectedPerson)); - } - - @Test - public void parse_compulsoryFieldMissing_failure() { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); - - // missing name prefix - assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing phone prefix - assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); - - // all prefixes missing - assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, - expectedMessage); - } - - @Test - public void parse_invalidValue_failure() { - // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS); - - // invalid phone - assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS); - - // invalid email - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS); - - // invalid address - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS); - - // invalid tag - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS); - - // two invalid values, only first invalid value reported - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, - Name.MESSAGE_CONSTRAINTS); - - // non-empty preamble - assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } -} diff --git a/src/test/java/seedu/address/logic/parser/AddExamCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddExamCommandParserTest.java new file mode 100644 index 00000000000..318069ccc0d --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddExamCommandParserTest.java @@ -0,0 +1,170 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDATEONE; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDATETWO; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDESCRIPTIONONE; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDESCRIPTIONTWO; +import static seedu.address.logic.commands.CommandTestUtil.EXAMMODULEONE; +import static seedu.address.logic.commands.CommandTestUtil.EXAMMODULETWO; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EXAM_DATEONE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EXAM_DATETWO; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EXAM_DESCRIPTION; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATEFIVE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATEFOUR; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATEONE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATESEVEN; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATESIX; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATETHREE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATETWO; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PAST_EXAMDATEONE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PAST_EXAMDATETWO; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_EXAMONE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalExams.EXAMONE; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.AddExamCommand; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.ModuleCode; +import seedu.address.testutil.ExamBuilder; + + +public class AddExamCommandParserTest { + private AddExamCommandParser parser = new AddExamCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Exam expectedExam = new ExamBuilder(EXAMONE).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + EXAMMODULEONE + + EXAMDESCRIPTIONONE + EXAMDATEONE, + new AddExamCommand(expectedExam)); + + //no white space + assertParseSuccess(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + EXAMDATEONE, + new AddExamCommand(expectedExam)); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + EXAMMODULEONE + + EXAMDESCRIPTIONONE + EXAMDATEONE + PREAMBLE_WHITESPACE, + new AddExamCommand(expectedExam)); + + // multiple modules - last module accepted + assertParseSuccess(parser, EXAMMODULETWO + EXAMMODULEONE + + EXAMDESCRIPTIONONE + EXAMDATEONE, new AddExamCommand(expectedExam)); + + // multiple descriptions - last description accepted + assertParseSuccess(parser, EXAMMODULEONE + + EXAMDESCRIPTIONTWO + EXAMDESCRIPTIONONE + EXAMDATEONE, new AddExamCommand(expectedExam)); + + //multiple dates - last date accepted + assertParseSuccess(parser, EXAMMODULEONE + EXAMDESCRIPTIONONE + + EXAMDATETWO + EXAMDATEONE, new AddExamCommand(expectedExam)); + } + + @Test + public void parse_compulsoryFieldMissingPrefix_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddExamCommand.MESSAGE_USAGE); + + // missing module prefix + assertParseFailure(parser, VALID_MODULE_EXAMONE + EXAMDESCRIPTIONONE + EXAMDATEONE, expectedMessage); + + // missing description prefix + assertParseFailure(parser, EXAMMODULEONE + VALID_DESCRIPTION_EXAMONE + EXAMDATEONE, expectedMessage); + + + //missing date prefix + assertParseFailure(parser, EXAMMODULEONE + EXAMDESCRIPTIONONE + VALID_DATE_EXAMONE, expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_MODULE_EXAMONE + + VALID_DESCRIPTION_EXAMONE + VALID_DATE_EXAMONE, expectedMessage); + } + + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddExamCommand.MESSAGE_USAGE); + // missing module + assertParseFailure(parser, EXAMDESCRIPTIONONE + EXAMDATEONE, expectedMessage); + + // missing description + assertParseFailure(parser, EXAMMODULEONE + EXAMDATEONE, expectedMessage); + + //missing date prefix + assertParseFailure(parser, EXAMMODULEONE + EXAMDESCRIPTIONONE , expectedMessage); + + // all prefixes missing + assertParseFailure(parser, "", expectedMessage); + + } + + @Test + public void parse_invalidValue_failure() { + // invalid module + assertParseFailure(parser, INVALID_MODULE + + EXAMDESCRIPTIONONE + EXAMDATEONE, ModuleCode.MODULE_CODE_CONSTRAINTS); + + // invalid description + assertParseFailure(parser, EXAMMODULEONE + INVALID_EXAM_DESCRIPTION + EXAMDATEONE, + ExamDescription.DESCRIPTION_CONSTRAINTS); + + //date not in DD-MM-YYYY format + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATEONE, + ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATETWO, + ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATETHREE, + ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATEFOUR, + ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATEFIVE, + ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATESIX, + ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATESEVEN, + ExamDate.DATE_FORMAT_CONSTRAINTS); + //date is not valid + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_EXAM_DATEONE, + ExamDate.VALID_DATE_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_EXAM_DATETWO, + ExamDate.VALID_DATE_CONSTRAINTS); + //date is a past date + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_PAST_EXAMDATEONE, + ExamDate.NOT_A_PAST_DATE_CONSTRAINTS); + assertParseFailure(parser, EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_PAST_EXAMDATETWO, + ExamDate.NOT_A_PAST_DATE_CONSTRAINTS); + + // three invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_MODULE + + INVALID_EXAM_DESCRIPTION + INVALID_EXAM_DATEONE, + ModuleCode.MODULE_CODE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + + EXAMMODULEONE + EXAMDESCRIPTIONONE + EXAMDATEONE, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddExamCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddModuleCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddModuleCommandParserTest.java new file mode 100644 index 00000000000..d1e06508d36 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddModuleCommandParserTest.java @@ -0,0 +1,86 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.AddModuleCommand; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; +import seedu.address.testutil.ModuleBuilder; + +public class AddModuleCommandParserTest { + private AddModuleCommandParser parser = new AddModuleCommandParser(); + + @Test + public void parse_nullArgs_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> parser.parse(null)); + } + + @Test + public void parse_allFieldsPresentAndValid_success() { + String correctArguments = " c/cs2105 m/networking mc/4"; + Module module = new ModuleBuilder().withModuleCode("cs2105") + .withModuleCredit(4).withModuleName("networking").build(); + assertParseSuccess(parser, correctArguments, new AddModuleCommand(module)); + } + + @Test + public void parse_missingFields_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddModuleCommand.MESSAGE_USAGE); + + //Missing module code prefix + String missingModuleCode = " cs2105 m/networking mc/4"; + assertParseFailure(parser, missingModuleCode, expectedMessage); + + //Missing module name prefix + String missingModuleName = " c/cs2105 networking mc/4"; + assertParseFailure(parser, missingModuleName, expectedMessage); + + //Missing module credit prefix + String missingModuleCredit = " c/cs2105 m/networking 4"; + assertParseFailure(parser, missingModuleCredit, expectedMessage); + } + + @Test + public void parse_invalidInputs_failure() { + //Empty module code + String emptyModuleCode = " c/ m/networking mc/4"; + assertParseFailure(parser, emptyModuleCode, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //Empty module name + String emptyModuleName = " c/cs2105 m/ mc/4"; + assertParseFailure(parser, emptyModuleName, ModuleName.MODULE_NAME_CONSTRAINTS); + + //Empty module credit + String emptyModuleCredit = " c/cs2105 m/networking mc/"; + assertParseFailure(parser, emptyModuleCredit, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + + //Module code with less than 6 characters + String moduleCodeLessThan6Char = " c/a m/networking mc/4"; + assertParseFailure(parser, moduleCodeLessThan6Char, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //Module code with invalid characters + String moduleCodeInvalidChar = " c/##@absa m/networking mc/5"; + assertParseFailure(parser, moduleCodeInvalidChar, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //Module code with invalid first two characters + String moduleCodeInvalidFirstTwoChar = " c/1234567 m/networking mc/7"; + assertParseFailure(parser, moduleCodeInvalidFirstTwoChar, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //Module Credit less than 0 + String moduleCreditLessThanZero = " c/cs2100 m/computer organisation mc/-1"; + assertParseFailure(parser, moduleCreditLessThanZero, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + + //Module Credit greater than 0 + String moduleCreditExtremelyLarge = " c/cs2100 m/computer organisation " + + "mc/9999999999999999999999999"; + assertParseFailure(parser, moduleCreditExtremelyLarge, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddTagCommandParserTest.java new file mode 100644 index 00000000000..b3569243043 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddTagCommandParserTest.java @@ -0,0 +1,126 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.testutil.DeadlineTagBuilder; +import seedu.address.testutil.PriorityTagBuilder; + + +public class AddTagCommandParserTest { + private AddTagCommandParser parser = new AddTagCommandParser(); + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-uuuu"); + + @Test + public void parse_allFieldsPresent_success() { + PriorityTag expectedPriorityTag = new PriorityTagBuilder().withStatus("HIGH").build(); + DeadlineTag expectedDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("22-11-2022", dateTimeFormatter)).build(); + Index expectedIndex = INDEX_FIRST_TASK; + String allFieldsPresentArgument = "1" + " " + PREFIX_PRIORITY_STATUS + "HIGH" + + " " + PREFIX_DEADLINE + "22-11-2022"; + String allFieldsPresentWithWhitespace = " 1" + " " + PREFIX_PRIORITY_STATUS + "\t\t" + + "HIGH" + " " + PREFIX_DEADLINE + "\t\t\t22-11-2022"; + + //Valid input with all fields present + assertParseSuccess(parser, allFieldsPresentArgument, + new AddTagCommand(expectedPriorityTag, expectedDeadlineTag, expectedIndex)); + + //Valid input with all fields present + assertParseSuccess(parser, allFieldsPresentWithWhitespace, + new AddTagCommand(expectedPriorityTag, expectedDeadlineTag, expectedIndex)); + } + + @Test + public void parse_deadlineAndIndexPresent_success() { + DeadlineTag expectedDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("23-11-2022", dateTimeFormatter)).build(); + String deadlineAndIndexPresent = "1" + " " + PREFIX_DEADLINE + "23-11-2022"; + assertParseSuccess(parser, deadlineAndIndexPresent, + new AddTagCommand(null, expectedDeadlineTag, INDEX_FIRST_TASK)); + } + + @Test + public void parse_priorityAndIndexPresent_success() { + PriorityTag expectedPriorityTag = new PriorityTagBuilder() + .withStatus("HIGH").build(); + String priorityAndIndexPresent = "1" + " " + PREFIX_PRIORITY_STATUS + "HIGH"; + assertParseSuccess(parser, priorityAndIndexPresent, + new AddTagCommand(expectedPriorityTag, null, INDEX_FIRST_TASK)); + } + + @Test + public void parse_missingFields_failure() { + String expectedMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + AddTagCommand.MESSAGE_USAGE); + + //Missing index + String missingIndex = " " + PREFIX_DEADLINE + "23-11-2022" + " " + PREFIX_PRIORITY_STATUS + "HIGH"; + assertParseFailure(parser, missingIndex, expectedMessage); + + //Missing priority prefix and deadline prefix + String missingPriorityAndDeadlinePrefix = "1" + " 23-11-2022" + " HIGH"; + assertParseFailure(parser, missingPriorityAndDeadlinePrefix, expectedMessage); + + //Missing priority and deadline fields + String missingPriorityDeadlineFields = "1"; + assertParseFailure(parser, missingPriorityDeadlineFields, expectedMessage); + + } + + @Test + public void parse_invalidInputs_failure() { + + //Empty deadline field + String emptyDeadlineField = "1 " + PREFIX_PRIORITY_STATUS + "HIGH " + PREFIX_DEADLINE; + assertParseFailure(parser, emptyDeadlineField, DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS); + + //Empty Priority field + String emptyPriorityField = "1 " + PREFIX_DEADLINE + "23-11-2022 " + PREFIX_PRIORITY_STATUS; + assertParseFailure(parser, emptyPriorityField, PriorityTag.PRIORITY_TAG_CONSTRAINTS); + + //Invalid index(Not a number) + String invalidCommandFormatMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + AddTagCommand.MESSAGE_USAGE); + assertParseFailure(parser, "abcde p/high", invalidCommandFormatMessage); + + //Invalid index(Non-positive) + String invalidIndexMessage = ParserUtil.MESSAGE_INVALID_INDEX; + assertParseFailure(parser, "-1 " + PREFIX_PRIORITY_STATUS + "HIGH", invalidIndexMessage); + + //Invalid index(Above INT_MAX_VALUE) + assertParseFailure(parser, "9999999999999999999999 " + PREFIX_PRIORITY_STATUS + "HIGH", + invalidIndexMessage); + + + //Invalid status for priority tag + assertParseFailure(parser, "1 " + PREFIX_PRIORITY_STATUS + "priority", + PriorityTag.PRIORITY_TAG_CONSTRAINTS); + + //Invalid date format for deadline tag + assertParseFailure(parser, "1 " + PREFIX_DEADLINE + "17/01/23", + DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS); + + //Invalid date chosen for deadline tag + assertParseFailure(parser, "1 " + PREFIX_DEADLINE + "30-02-2023", + DeadlineTag.DEADLINE_TAG_INVALID_DATE); + + //Date has passed for deadline tag + assertParseFailure(parser, "1 " + PREFIX_DEADLINE + "01-01-2022", + DeadlineTag.DEADLINE_TAG_DATE_HAS_PASSED); + + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddTaskCommandParserTest.java new file mode 100644 index 00000000000..c9b46b69354 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddTaskCommandParserTest.java @@ -0,0 +1,78 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_TASK_A; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TASK_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_CS2030; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_CS2040; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2030; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalTasks.TASK_A; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; +import seedu.address.testutil.TaskBuilder; + +public class AddTaskCommandParserTest { + private AddTaskCommandParser parser = new AddTaskCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Task expectedTask = new TaskBuilder(TASK_A).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + MODULE_DESC_CS2030 + DESCRIPTION_DESC_TASK_A, + new AddTaskCommand(expectedTask)); + + // multiple modules - last module accepted + assertParseSuccess(parser, MODULE_DESC_CS2040 + MODULE_DESC_CS2030 + DESCRIPTION_DESC_TASK_A, + new AddTaskCommand(expectedTask)); + + // multiple descriptions - last description accepted + assertParseSuccess(parser, MODULE_DESC_CS2030 + DESCRIPTION_DESC_DO_TUTORIAL + DESCRIPTION_DESC_TASK_A, + new AddTaskCommand(expectedTask)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE); + + // missing module prefix + assertParseFailure(parser, VALID_MODULE_CS2030 + DESCRIPTION_DESC_DO_TUTORIAL, expectedMessage); + + // missing description prefix + assertParseFailure(parser, MODULE_DESC_CS2030 + VALID_DESCRIPTION_DO_TUTORIAL, expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_MODULE_CS2030 + VALID_DESCRIPTION_DO_TUTORIAL, expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid module + assertParseFailure(parser, INVALID_MODULE_DESC + DESCRIPTION_DESC_DO_TUTORIAL, + ModuleCode.MODULE_CODE_CONSTRAINTS); + + // invalid description + assertParseFailure(parser, MODULE_DESC_CS2030 + INVALID_TASK_DESCRIPTION_DESC, + TaskDescription.DESCRIPTION_CONSTRAINTS); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_MODULE_DESC + INVALID_TASK_DESCRIPTION_DESC, + ModuleCode.MODULE_CODE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + MODULE_DESC_CS2030 + DESCRIPTION_DESC_DO_TUTORIAL, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index d9659205b57..a7e235c027d 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -3,99 +3,412 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FEATURE_TYPE_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_COMPLETE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_LINKED; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_MODULE; +import static seedu.address.testutil.TypicalModules.CS2030; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.AddExamCommand; +import seedu.address.logic.commands.AddModuleCommand; +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.logic.commands.ClearAllCommand; +import seedu.address.logic.commands.ClearTasksCommand; +import seedu.address.logic.commands.DeleteExamCommand; +import seedu.address.logic.commands.DeleteModuleCommand; +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.commands.DeleteTaskCommand; +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.logic.commands.EditTagCommand; +import seedu.address.logic.commands.EditTaskCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FilterTasksCommand; +import seedu.address.logic.commands.FindModulesCommand; +import seedu.address.logic.commands.FindTasksCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LinkExamCommand; +import seedu.address.logic.commands.ListExamTasksCommand; +import seedu.address.logic.commands.ListModulesCommand; +import seedu.address.logic.commands.ListTasksCommand; +import seedu.address.logic.commands.MarkCommand; +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.logic.commands.UnlinkExamCommand; +import seedu.address.logic.commands.UnmarkCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; -import seedu.address.testutil.PersonUtil; +import seedu.address.model.Criteria; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; +import seedu.address.model.task.FilterPredicate; +import seedu.address.model.task.Task; +import seedu.address.testutil.EditExamDescriptorBuilder; +import seedu.address.testutil.EditTaskDescriptorBuilder; +import seedu.address.testutil.ExamBuilder; +import seedu.address.testutil.ExamUtil; +import seedu.address.testutil.ModuleBuilder; +import seedu.address.testutil.TaskBuilder; +import seedu.address.testutil.TaskUtil; +import seedu.address.ui.ModuleUtil; -public class AddressBookParserTest { +public class AddressBookParserTest { private final AddressBookParser parser = new AddressBookParser(); @Test - public void parseCommand_add() throws Exception { - Person person = new PersonBuilder().build(); - AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); - assertEquals(new AddCommand(person), command); + public void parseCommand_addTask() throws Exception { + Task task = new TaskBuilder().build(); + AddTaskCommand command = (AddTaskCommand) parser.parseCommand(TaskUtil.getAddTaskCommand(task)); + assertEquals(new AddTaskCommand(task), command); } + @Test - public void parseCommand_clear() throws Exception { - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); + public void parseCommand_deleteTask() throws Exception { + DeleteTaskCommand command = (DeleteTaskCommand) parser.parseCommand("t" + " " + + DeleteTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased()); + DeleteTaskCommand commandTwo = (DeleteTaskCommand) parser.parseCommand("T" + + " " + DeleteTaskCommand.COMMAND_WORD.toUpperCase() + + " " + INDEX_FIRST_TASK.getOneBased()); + assertEquals(new DeleteTaskCommand(INDEX_FIRST_TASK), command); + assertEquals(new DeleteTaskCommand(INDEX_FIRST_TASK), commandTwo); } + @Test - public void parseCommand_delete() throws Exception { - DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); + public void parseCommand_editTask() throws Exception { + Task task = new TaskBuilder().build(); + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder(task).build(); + EditTaskCommand command = (EditTaskCommand) parser.parseCommand("t" + " " + + EditTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased() + " " + + TaskUtil.getEditTaskDescriptorDetails(descriptor)); + assertEquals(new EditTaskCommand(INDEX_FIRST_TASK, descriptor), command); } @Test - public void parseCommand_edit() throws Exception { - Person person = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); - EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + public void parseCommand_listTasks() throws Exception { + assertTrue(parser.parseCommand("t" + " " + ListTasksCommand.COMMAND_WORD) instanceof ListTasksCommand); + assertTrue(parser.parseCommand("t" + " " + ListTasksCommand.COMMAND_WORD + " 3") instanceof ListTasksCommand); } + @Test - public void parseCommand_exit() throws Exception { - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); + public void parseCommand_sortTasks() throws Exception { + SortTaskCommand sortTaskCommand = (SortTaskCommand) parser.parseCommand("t " + + SortTaskCommand.COMMAND_WORD + " " + "c/PRIORITY"); + assertEquals(new SortTaskCommand(new Criteria("PRIORITY")), sortTaskCommand); + SortTaskCommand sortTaskCommandTwo = (SortTaskCommand) parser.parseCommand("t " + + SortTaskCommand.COMMAND_WORD + " " + "c/deadline "); + assertEquals(new SortTaskCommand(new Criteria("deadline")), sortTaskCommandTwo); + SortTaskCommand sortTaskCommandThree = (SortTaskCommand) parser.parseCommand("t " + + SortTaskCommand.COMMAND_WORD + " " + "c/module "); + assertEquals(new SortTaskCommand(new Criteria("module")), sortTaskCommandThree); + SortTaskCommand sortTaskCommandFour = (SortTaskCommand) parser.parseCommand("t " + + SortTaskCommand.COMMAND_WORD + " " + "c/description "); + assertEquals(new SortTaskCommand(new Criteria("description")), sortTaskCommandFour); + + } + + @Test + public void parseCommand_filterTasks() throws Exception { + FilterTasksCommand filterTasksCommand = (FilterTasksCommand) parser.parseCommand( + "t " + FilterTasksCommand.COMMAND_WORD + " " + PREFIX_MODULE + "cs2030"); + assertEquals(new FilterTasksCommand( + new FilterPredicate(Optional.of(CS2030), + Optional.empty(), Optional.empty())), filterTasksCommand); + + FilterTasksCommand filterTasksCommandTwo = (FilterTasksCommand) parser.parseCommand( + "t " + FilterTasksCommand.COMMAND_WORD + " " + PREFIX_MODULE + "cs2030" + " " + + PREFIX_IS_COMPLETE + "y" + " " + PREFIX_IS_LINKED + "y"); + assertEquals(new FilterTasksCommand( + new FilterPredicate(Optional.of(CS2030), + Optional.of(true), Optional.of(true))), filterTasksCommandTwo); + + FilterTasksCommand filterTasksCommandThree = (FilterTasksCommand) parser.parseCommand( + "t " + FilterTasksCommand.COMMAND_WORD + " " + PREFIX_MODULE + "cs2030" + " " + + PREFIX_IS_COMPLETE + "y"); + assertEquals(new FilterTasksCommand( + new FilterPredicate(Optional.of(CS2030), + Optional.of(true), Optional.empty())), filterTasksCommandThree); + } + + @Test + public void parseCommand_findTasks() throws Exception { + FindTasksCommand command = (FindTasksCommand) parser.parseCommand("t " + + FindTasksCommand.COMMAND_WORD + " " + "homework"); + assertEquals(new FindTasksCommand(new DescriptionContainsKeywordsPredicate( + Collections.singletonList("homework"))), command); + } + + @Test + public void parseCommand_markTask() throws Exception { + MarkCommand command = (MarkCommand) parser.parseCommand("t " + + MarkCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased()); + assertEquals(new MarkCommand(INDEX_FIRST_TASK), command); + } + + @Test + public void parseCommand_unmarkTask() throws Exception { + UnmarkCommand command = (UnmarkCommand) parser.parseCommand("t " + + UnmarkCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased()); + assertEquals(new UnmarkCommand(INDEX_FIRST_TASK), command); } @Test - public void parseCommand_find() throws Exception { - List keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + public void parseCommand_clearTasks() throws Exception { + assertTrue(parser.parseCommand("t" + " " + + ClearTasksCommand.COMMAND_WORD) instanceof ClearTasksCommand); + } + + @Test + public void parseCommand_exit() throws Exception { + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD.toUpperCase()) instanceof ExitCommand); + assertTrue(parser.parseCommand("ExiT") instanceof ExitCommand); + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); } @Test public void parseCommand_help() throws Exception { assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD.toUpperCase()) instanceof HelpCommand); + assertTrue(parser.parseCommand("HelP") instanceof HelpCommand); assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); } @Test - public void parseCommand_list() throws Exception { - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); + public void parseCommand_clearAll() throws Exception { + assertTrue(parser.parseCommand(ClearAllCommand.COMMAND_WORD) instanceof ClearAllCommand); + assertTrue(parser.parseCommand(ClearAllCommand.COMMAND_WORD.toUpperCase()) instanceof ClearAllCommand); + assertTrue(parser.parseCommand("CLEARaLL") instanceof ClearAllCommand); + assertTrue(parser.parseCommand(ClearAllCommand.COMMAND_WORD + " 3") instanceof ClearAllCommand); + } + + @Test + public void parseCommand_addTag() throws Exception { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-uuuu"); + AddTagCommand command = (AddTagCommand) parser.parseCommand("t " + + AddTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_PRIORITY_STATUS + "HIGH"); + assertEquals(new AddTagCommand(new PriorityTag("High"), null, INDEX_FIRST_TASK), command); + + AddTagCommand commandTwo = (AddTagCommand) parser.parseCommand("t " + + AddTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_PRIORITY_STATUS + "low"); + assertEquals(new AddTagCommand(new PriorityTag("Low"), null, INDEX_FIRST_TASK), commandTwo); + + AddTagCommand commandThree = (AddTagCommand) parser.parseCommand("t " + + AddTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_PRIORITY_STATUS + + "HIGH " + PREFIX_DEADLINE + "20-08-2023"); + assertEquals(new AddTagCommand(new PriorityTag("High"), + new DeadlineTag(LocalDate.parse("20-08-2023", dateTimeFormatter)), + INDEX_FIRST_TASK), commandThree); + + AddTagCommand commandFour = (AddTagCommand) parser.parseCommand("t " + + AddTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_DEADLINE + "20-08-2023"); + assertEquals(new AddTagCommand(null, + new DeadlineTag(LocalDate.parse("20-08-2023", dateTimeFormatter)), + INDEX_FIRST_TASK), commandFour); + } + + @Test + public void parseCommand_editTag() throws Exception { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-uuuu"); + EditTagCommand command = (EditTagCommand) parser.parseCommand("t " + + EditTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + + PREFIX_PRIORITY_STATUS + "HIGH"); + assertEquals(new EditTagCommand(INDEX_FIRST_TASK, + new PriorityTag("High"), null), command); + + EditTagCommand commandTwo = (EditTagCommand) parser.parseCommand("t " + + EditTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + + PREFIX_PRIORITY_STATUS + "low"); + assertEquals(new EditTagCommand(INDEX_FIRST_TASK, + new PriorityTag("Low"), null), commandTwo); + + EditTagCommand commandThree = (EditTagCommand) parser.parseCommand("t " + + EditTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_PRIORITY_STATUS + + "HIGH " + PREFIX_DEADLINE + "20-08-2023"); + assertEquals(new EditTagCommand(INDEX_FIRST_TASK, new PriorityTag("High"), + new DeadlineTag(LocalDate.parse("20-08-2023", dateTimeFormatter))), commandThree); + + EditTagCommand commandFour = (EditTagCommand) parser.parseCommand("t " + + EditTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_DEADLINE + "20-08-2023"); + assertEquals(new EditTagCommand(INDEX_FIRST_TASK, null, + new DeadlineTag(LocalDate.parse("20-08-2023", dateTimeFormatter))), commandFour); + } + + @Test + public void parseCommand_deleteTag() throws Exception { + DeleteTagCommand command = (DeleteTagCommand) parser.parseCommand("t " + + DeleteTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_TAG + "deadline"); + assertEquals(new DeleteTagCommand(INDEX_FIRST_TASK, Set.of("deadline")), command); + + DeleteTagCommand commandTwo = (DeleteTagCommand) parser.parseCommand("t " + + DeleteTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_TAG + "priority"); + assertEquals(new DeleteTagCommand(INDEX_FIRST_TASK, Set.of("priority")), commandTwo); + + DeleteTagCommand commandThree = (DeleteTagCommand) parser.parseCommand("t " + + DeleteTagCommand.COMMAND_WORD + " " + + INDEX_FIRST_TASK.getOneBased() + " " + PREFIX_TAG + "priority " + "deadline"); + assertEquals(new DeleteTagCommand(INDEX_FIRST_TASK, + Set.of("deadline", "priority")), commandThree); + + } + + + @Test + public void parseCommand_addModule() throws Exception { + Module module = new ModuleBuilder().build(); + AddModuleCommand command = (AddModuleCommand) + parser.parseCommand(ModuleUtil.getAddModuleCommand(module)); + assertEquals(new AddModuleCommand(module), command); + } + + @Test + public void parseCommand_editModule() throws Exception { + Module module = new ModuleBuilder().build(); + EditModuleCommand.EditModuleDescriptor descriptor = + new EditModuleCommand.EditModuleDescriptor(); + descriptor.setModuleCode(module.getModuleCode()); + descriptor.setModuleName(module.getModuleName()); + descriptor.setModuleCredit(module.getModuleCredit()); + + EditModuleCommand command = (EditModuleCommand) parser.parseCommand("m" + " " + + EditModuleCommand.COMMAND_WORD + " " + INDEX_THIRD_MODULE.getOneBased() + " " + + ModuleUtil.getEditModuleDescriptorDetails(descriptor)); + assertEquals(new EditModuleCommand(INDEX_THIRD_MODULE, descriptor), command); + } + + + @Test + public void parseCommand_deleteModule() throws Exception { + DeleteModuleCommand command = (DeleteModuleCommand) parser.parseCommand("m" + " " + + DeleteModuleCommand.COMMAND_WORD + " " + INDEX_THIRD_MODULE.getOneBased()); + DeleteModuleCommand commandTwo = (DeleteModuleCommand) parser.parseCommand("M" + " " + + DeleteModuleCommand.COMMAND_WORD + " " + INDEX_THIRD_MODULE.getOneBased()); + assertEquals(new DeleteModuleCommand(INDEX_THIRD_MODULE), command); + assertEquals(new DeleteModuleCommand(INDEX_THIRD_MODULE), commandTwo); + } + + @Test + public void parseCommand_listModules() throws Exception { + assertTrue(parser.parseCommand("m" + " " + + ListModulesCommand.COMMAND_WORD) instanceof ListModulesCommand); + assertTrue(parser.parseCommand("m" + " " + + ListModulesCommand.COMMAND_WORD + " 3") instanceof ListModulesCommand); + } + + @Test + public void parseCommand_findModules() throws Exception { + FindModulesCommand command = (FindModulesCommand) parser.parseCommand("m " + + FindModulesCommand.COMMAND_WORD + " " + "cs2030s"); + assertEquals(new FindModulesCommand(new ModuleCodeContainsKeywordsPredicate( + Collections.singletonList("cs2030s"))), command); + } + + @Test + public void parseCommand_addExam() throws Exception { + Exam exam = new ExamBuilder().build(); + AddExamCommand command = (AddExamCommand) parser.parseCommand(ExamUtil.getAddExamCommand(exam)); + assertEquals(new AddExamCommand(exam), command); + } + + @Test + public void parseCommand_editExam() throws Exception { + Exam exam = new ExamBuilder().build(); + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder(exam).build(); + EditExamCommand command = (EditExamCommand) parser.parseCommand("e" + " " + + EditExamCommand.COMMAND_WORD + " " + INDEX_SECOND_EXAM.getOneBased() + " " + + ExamUtil.getEditExamDescriptorDetails(descriptor)); + assertEquals(new EditExamCommand(INDEX_SECOND_EXAM, descriptor), command); + } + + @Test + public void parseCommand_deleteExam() throws Exception { + DeleteExamCommand command = (DeleteExamCommand) parser.parseCommand("e " + + DeleteExamCommand.COMMAND_WORD + " " + INDEX_FIRST_EXAM.getOneBased()); + DeleteExamCommand commandTwo = (DeleteExamCommand) parser.parseCommand("E" + + " " + DeleteExamCommand.COMMAND_WORD.toUpperCase() + " " + + INDEX_FIRST_EXAM.getOneBased()); + assertEquals(new DeleteExamCommand(INDEX_FIRST_EXAM), command); + assertEquals(new DeleteExamCommand(INDEX_FIRST_EXAM), commandTwo); + } + + @Test + public void parseCommand_linkExam() throws Exception { + LinkExamCommand command = (LinkExamCommand) parser.parseCommand("e " + + LinkExamCommand.COMMAND_WORD + " " + PREFIX_EXAM_INDEX + + INDEX_FIRST_EXAM.getOneBased() + " " + + PREFIX_TASK_INDEX + INDEX_FIRST_TASK.getOneBased()); + assertEquals(new LinkExamCommand(INDEX_FIRST_EXAM, INDEX_FIRST_TASK), command); } + @Test + public void parseCommand_unlinkExam() throws Exception { + UnlinkExamCommand command = (UnlinkExamCommand) parser.parseCommand("e " + + UnlinkExamCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased()); + assertEquals(new UnlinkExamCommand(INDEX_FIRST_TASK), command); + } + + @Test + public void parseCommand_showT() throws Exception { + ListExamTasksCommand command = (ListExamTasksCommand) parser.parseCommand("e " + + ListExamTasksCommand.COMMAND_WORD + " " + INDEX_FIRST_EXAM.getOneBased()); + assertEquals(new ListExamTasksCommand(INDEX_FIRST_EXAM), command); + } + + @Test public void parseCommand_unrecognisedInput_throwsParseException() { - assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () - -> parser.parseCommand("")); + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + HelpCommand.MESSAGE_USAGE), ()-> parser.parseCommand("")); + } + + @Test + public void parseCommand_unrecognisedInputWithFeatureType_throwsParseException() { + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "exam"), () + -> parser.parseCommand("e adda")); + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "module"), () + -> parser.parseCommand("m adda")); + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "task"), () + -> parser.parseCommand("t adda")); } @Test public void parseCommand_unknownCommand_throwsParseException() { assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); } + } diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java deleted file mode 100644 index 27eaec84450..00000000000 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.DeleteCommand; - -/** - * As we are only doing white-box testing, our test cases do not cover path variations - * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the - * same path through the DeleteCommand, and therefore we test only one of them. - * The path variation for those two cases occur inside the ParserUtil, and - * therefore should be covered by the ParserUtilTest. - */ -public class DeleteCommandParserTest { - - private DeleteCommandParser parser = new DeleteCommandParser(); - - @Test - public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); - } - - @Test - public void parse_invalidArgs_throwsParseException() { - assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); - } -} diff --git a/src/test/java/seedu/address/logic/parser/DeleteExamCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteExamCommandParserTest.java new file mode 100644 index 00000000000..e62f7f1227a --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteExamCommandParserTest.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.FIRST_INDEX; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.DeleteExamCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class DeleteExamCommandParserTest { + private DeleteExamCommandParser parser = new DeleteExamCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteExamCommand() { + assertParseSuccess(parser, "1", new DeleteExamCommand(FIRST_INDEX)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // empty string + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteExamCommand.MESSAGE_USAGE)); + + // wrong type + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteExamCommand.MESSAGE_USAGE)); + + // big integer + assertParseFailure(parser, "2147483648", MESSAGE_INVALID_EXAM_INDEX); + + // negative integer + assertParseFailure(parser, "-1", MESSAGE_INVALID_EXAM_INDEX); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteModuleCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteModuleCommandParserTest.java new file mode 100644 index 00000000000..184ff4a2a74 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteModuleCommandParserTest.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_MODULE; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.DeleteModuleCommand; + +/** + * Contains unit tests for {@code DeleteModuleCommandParser}. + */ +public class DeleteModuleCommandParserTest { + + private DeleteModuleCommandParser parser = new DeleteModuleCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteModuleCommand() { + assertParseSuccess(parser, "1", new DeleteModuleCommand(INDEX_FIRST_MODULE)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // non-integer + assertParseFailure(parser, "a b", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DeleteModuleCommand.MESSAGE_USAGE)); + + // empty string + assertParseFailure(parser, "", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DeleteModuleCommand.MESSAGE_USAGE)); + + // zero index + assertParseFailure(parser, "0", MESSAGE_INVALID_MODULE_INDEX); + + // negative index + assertParseFailure(parser, "-2", MESSAGE_INVALID_MODULE_INDEX); + + // positive signed index + assertParseFailure(parser, "+3", MESSAGE_INVALID_MODULE_INDEX); + + // valid index followed by string + assertParseFailure(parser, "1 i/ string", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DeleteModuleCommand.MESSAGE_USAGE)); + + // number bigger than max integer + assertParseFailure(parser, "9999999999999999999999", MESSAGE_INVALID_MODULE_INDEX); + + } + +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java new file mode 100644 index 00000000000..4e5ca81ef39 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java @@ -0,0 +1,88 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteTagCommand; + +public class DeleteTagCommandParserTest { + + private DeleteTagCommandParser parser = new DeleteTagCommandParser(); + private final String invalidCommandFormatMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + DeleteTagCommand.MESSAGE_USAGE); + + @Test + public void parse_allFieldsPresent_success() { + Index index = INDEX_FIRST_TASK; + String keyword = "priority"; + String keywords = "priority deadline"; + String keywordsWithSpacing = "priority deadline"; + String duplicateKeywords = "priority priority"; + + //Valid input with all fields present and one keyword + assertParseSuccess(parser, "1 " + PREFIX_TAG + keyword, + new DeleteTagCommand(INDEX_FIRST_TASK, Set.of("priority"))); + + //Valid input with all fields present and two keywords + assertParseSuccess(parser, "1 " + PREFIX_TAG + keywords, + new DeleteTagCommand(INDEX_FIRST_TASK, Set.of("priority", "deadline"))); + + //Valid input with all fields present and large spacing between keywords + assertParseSuccess(parser, "1 " + PREFIX_TAG + keywordsWithSpacing, + new DeleteTagCommand(INDEX_FIRST_TASK, Set.of("priority", "deadline"))); + + //Valid input with all fields present and duplicate keywords + assertParseSuccess(parser, "1 " + PREFIX_TAG + duplicateKeywords, + new DeleteTagCommand(INDEX_FIRST_TASK, Set.of("priority"))); + } + + @Test + public void parse_missingFields_failure() { + + //Missing index + String missingIndex = PREFIX_TAG + "priority deadline"; + assertParseFailure(parser, missingIndex, invalidCommandFormatMessage); + + //Missing keywords + String missingKeywords = "1 priority deadline"; + assertParseFailure(parser, missingKeywords, invalidCommandFormatMessage); + } + + @Test + public void parse_invalidInputs_failure() { + //Empty Keyword + String emptyKeyword = "1 " + PREFIX_TAG; + String emptyKeywordMessage = ParserUtil.MESSAGE_INVALID_KEYWORDS; + assertParseFailure(parser, emptyKeyword, emptyKeywordMessage); + + //Too many keywords(3 given) + String tooManyKeywords = "1 " + PREFIX_TAG + "priority deadline description"; + String tooManyKeywordsMessage = ParserUtil.MESSAGE_INVALID_NUMBER_OF_KEYWORDS; + assertParseFailure(parser, tooManyKeywords, tooManyKeywordsMessage); + + //Invalid keywords used + String invalidKeyword = "1 " + PREFIX_TAG + "pri"; + String invalidKeywordMessage = ParserUtil.MESSAGE_INVALID_KEYWORDS; + assertParseFailure(parser, invalidKeyword, invalidKeywordMessage); + + //Invalid index(Not a Number) + assertParseFailure(parser, "abc t/priority", invalidCommandFormatMessage); + + //Invalid index(Non-positive) + String invalidIndexMessage = DeleteTagCommandParser.INVALID_INDEX_FOR_DELETE_TAG; + assertParseFailure(parser, "-1 " + PREFIX_TAG + "priority", invalidIndexMessage); + + //Invalid index(Above INT_MAX_VALUE) (Waiting for merged code for bug to check again) + assertParseFailure(parser, "9999999999999999999999 " + PREFIX_TAG + "priority", + invalidIndexMessage); + + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java new file mode 100644 index 00000000000..16677fc4df2 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.DeleteTaskCommand; + +/** + * Contains unit tests for {@code DeleteTaskCommandParser}. + */ +public class DeleteTaskCommandParserTest { + + private DeleteTaskCommandParser parser = new DeleteTaskCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteTaskCommand() { + assertParseSuccess(parser, "1", new DeleteTaskCommand(INDEX_FIRST_TASK)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // non-integer + assertParseFailure(parser, "a b", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + + // empty string + assertParseFailure(parser, "", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + + // zero index + assertParseFailure(parser, "0", MESSAGE_INVALID_TASK_INDEX); + + // negative index + assertParseFailure(parser, "-2", MESSAGE_INVALID_TASK_INDEX); + + // positive signed index + assertParseFailure(parser, "+3", MESSAGE_INVALID_TASK_INDEX); + + // valid index followed by string + assertParseFailure(parser, "1 i/ string", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + + // number bigger than max integer + assertParseFailure(parser, "9999999999999999999999", MESSAGE_INVALID_TASK_INDEX); + } +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java deleted file mode 100644 index 2ff31522486..00000000000 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ /dev/null @@ -1,211 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.EditPersonDescriptorBuilder; - -public class EditCommandParserTest { - - private static final String TAG_EMPTY = " " + PREFIX_TAG; - - private static final String MESSAGE_INVALID_FORMAT = - String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); - - private EditCommandParser parser = new EditCommandParser(); - - @Test - public void parse_missingParts_failure() { - // no index specified - assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); - - // no field specified - assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); - - // no index and no field specified - assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); - } - - @Test - public void parse_invalidPreamble_failure() { - // negative index - assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); - - // zero index - assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); - - // invalid arguments being parsed as preamble - assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); - - // invalid prefix being parsed as preamble - assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); - } - - @Test - public void parse_invalidValue_failure() { - assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name - assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone - assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email - assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address - assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag - - // invalid phone followed by valid email - assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); - - // valid phone followed by invalid phone. The test case for invalid phone followed by valid phone - // is tested at {@code parse_invalidValueFollowedByValidValue_success()} - assertParseFailure(parser, "1" + PHONE_DESC_BOB + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); - - // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited, - // parsing it together with a valid tag results in error - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - - // multiple invalid values, but only the first invalid value is captured - assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY, - Name.MESSAGE_CONSTRAINTS); - } - - @Test - public void parse_allFieldsSpecified_success() { - Index targetIndex = INDEX_SECOND_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND - + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_someFieldsSpecified_success() { - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_oneFieldSpecified_success() { - // name - Index targetIndex = INDEX_THIRD_PERSON; - String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // phone - userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // email - userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // address - userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // tags - userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND; - descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_multipleRepeatedFields_acceptsLast() { - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY - + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND - + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_invalidValueFollowedByValidValue_success() { - // no other valid values specified - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // other valid values specified - userInput = targetIndex.getOneBased() + EMAIL_DESC_BOB + INVALID_PHONE_DESC + ADDRESS_DESC_BOB - + PHONE_DESC_BOB; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_resetTags_success() { - Index targetIndex = INDEX_THIRD_PERSON; - String userInput = targetIndex.getOneBased() + TAG_EMPTY; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } -} diff --git a/src/test/java/seedu/address/logic/parser/EditExamCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditExamCommandParserTest.java new file mode 100644 index 00000000000..137b952b710 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EditExamCommandParserTest.java @@ -0,0 +1,237 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDATEONE; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDATETWO; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDESCRIPTIONONE; +import static seedu.address.logic.commands.CommandTestUtil.EXAMDESCRIPTIONTWO; +import static seedu.address.logic.commands.CommandTestUtil.EXAMMODULEONE; +import static seedu.address.logic.commands.CommandTestUtil.EXAMMODULETWO; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EXAM_DATEONE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EXAM_DATETWO; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EXAM_DESCRIPTION; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATEFIVE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATEFOUR; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATEONE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATESEVEN; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATESIX; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATETHREE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FORMAT_EXAM_DATETWO; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PAST_EXAMDATEONE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PAST_EXAMDATETWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_EXAMTWO; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_EXAM; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.ModuleCode; +import seedu.address.testutil.EditExamDescriptorBuilder; + +public class EditExamCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditExamCommand.MESSAGE_USAGE); + + private EditExamCommandParser parser = new EditExamCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, EXAMDATEONE, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditExamCommand.MESSAGE_NO_FIELDS_PROVIDED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + + //no prefix + assertParseFailure(parser, "1" + VALID_DATE_EXAMONE, MESSAGE_INVALID_FORMAT); + + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + EXAMDESCRIPTIONONE, MESSAGE_INVALID_EXAM_INDEX); + + // zero index + assertParseFailure(parser, "0" + EXAMDESCRIPTIONONE, MESSAGE_INVALID_EXAM_INDEX); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, "1" + INVALID_MODULE, ModuleCode.MODULE_CODE_CONSTRAINTS); // invalid name + assertParseFailure(parser, "1" + INVALID_EXAM_DESCRIPTION, + ExamDescription.DESCRIPTION_CONSTRAINTS); // invalid phone + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATEONE, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATETWO, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATETHREE, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATEFOUR, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATEFIVE, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATESIX, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATESEVEN, ExamDate.DATE_FORMAT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_EXAM_DATEONE, ExamDate.VALID_DATE_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_EXAM_DATETWO, ExamDate.VALID_DATE_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_PAST_EXAMDATEONE, ExamDate.NOT_A_PAST_DATE_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_PAST_EXAMDATETWO, ExamDate.NOT_A_PAST_DATE_CONSTRAINTS); + + //two fields + assertParseFailure(parser, "1" + INVALID_MODULE + + EXAMDESCRIPTIONONE, ModuleCode.MODULE_CODE_CONSTRAINTS); + assertParseFailure(parser, "1" + EXAMDESCRIPTIONONE + + INVALID_FORMAT_EXAM_DATESEVEN, ExamDate.DATE_FORMAT_CONSTRAINTS); + + //invalid module followed by valid description and date + assertParseFailure(parser, "1" + INVALID_MODULE + + EXAMDESCRIPTIONONE + EXAMDATEONE, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //invalid description followed by valid date and module + assertParseFailure(parser, "1" + INVALID_EXAM_DESCRIPTION + + EXAMMODULEONE + EXAMDATEONE, ExamDescription.DESCRIPTION_CONSTRAINTS); + + //invalid date followed by valid module and description + assertParseFailure(parser, "1" + INVALID_FORMAT_EXAM_DATESEVEN + + EXAMMODULEONE + EXAMDESCRIPTIONONE, ExamDate.DATE_FORMAT_CONSTRAINTS); + // valid description and date followed by invalid module. + assertParseFailure(parser, "1" + EXAMDESCRIPTIONONE + + EXAMDATEONE + INVALID_MODULE, ModuleCode.MODULE_CODE_CONSTRAINTS); + //valid module and date followed by invalid description + assertParseFailure(parser, "1" + EXAMMODULEONE + + EXAMDATEONE + INVALID_EXAM_DESCRIPTION , ExamDescription.DESCRIPTION_CONSTRAINTS); + //valid module and description followed by invalid date + assertParseFailure(parser, "1" + EXAMMODULEONE + + EXAMDESCRIPTIONONE + INVALID_FORMAT_EXAM_DATESEVEN, ExamDate.DATE_FORMAT_CONSTRAINTS); + //valid module, then invalid description then valid date + assertParseFailure(parser, "1" + EXAMMODULEONE + + INVALID_EXAM_DESCRIPTION + EXAMDATEONE, ExamDescription.DESCRIPTION_CONSTRAINTS); + + assertParseFailure(parser, "2147483648" + EXAMDESCRIPTIONONE, + MESSAGE_INVALID_EXAM_INDEX); + + assertParseFailure(parser, "-2" + EXAMDESCRIPTIONONE, MESSAGE_INVALID_EXAM_INDEX); + + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_EXAM; + String userInput = targetIndex.getOneBased() + EXAMDESCRIPTIONONE + EXAMDATEONE + EXAMMODULEONE; + + EditExamCommand.EditExamDescriptor descriptor = + new EditExamDescriptorBuilder().withDescription(VALID_DESCRIPTION_EXAMONE) + .withDate(VALID_DATE_EXAMONE).withModule(VALID_MODULE_EXAMONE).build(); + EditExamCommand expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_EXAM; + String userInput = targetIndex.getOneBased() + EXAMDESCRIPTIONONE + EXAMDATEONE; + + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_EXAMONE) + .withDate(VALID_DATE_EXAMONE).build(); + EditExamCommand expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + @Test + public void parse_someFieldsSpecifiedTestTwo_success() { + Index targetIndex = INDEX_SECOND_EXAM; + String userInput = targetIndex.getOneBased() + EXAMMODULEONE + EXAMDATEONE; + + EditExamCommand.EditExamDescriptor descriptor = + new EditExamDescriptorBuilder().withModule(VALID_MODULE_EXAMONE) + .withDate(VALID_DATE_EXAMONE).build(); + EditExamCommand expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + + @Test + public void parse_oneFieldSpecified_success() { + // date + Index targetIndex = INDEX_SECOND_EXAM; + String userInput = targetIndex.getOneBased() + EXAMDATEONE; + EditExamCommand.EditExamDescriptor descriptor = new EditExamDescriptorBuilder() + .withDate(VALID_DATE_EXAMONE).build(); + EditExamCommand expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // description + userInput = targetIndex.getOneBased() + EXAMDESCRIPTIONONE; + descriptor = new EditExamDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_EXAMONE).build(); + expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // module + userInput = targetIndex.getOneBased() + EXAMMODULEONE; + descriptor = new EditExamDescriptorBuilder() + .withModule(VALID_MODULE_EXAMONE).build(); + expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_SECOND_EXAM; + String userInput = targetIndex.getOneBased() + + EXAMMODULETWO + EXAMDATETWO + EXAMDESCRIPTIONTWO + + EXAMMODULEONE + EXAMDATEONE + EXAMDESCRIPTIONONE; + + + EditExamCommand.EditExamDescriptor descriptor = + new EditExamDescriptorBuilder().withModule(VALID_MODULE_EXAMONE) + .withDate(VALID_DATE_EXAMONE).withDescription(VALID_DESCRIPTION_EXAMONE).build(); + EditExamCommand expectedCommand = new EditExamCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_invalidValueFollowedByValidValue_success() { + // no other valid values specified + Index targetIndex = INDEX_FIRST_EXAM; + String userInput = targetIndex.getOneBased() + + INVALID_EXAM_DATETWO + EXAMDATETWO; + EditExamCommand.EditExamDescriptor descriptor = + new EditExamDescriptorBuilder().withDate(VALID_DATE_EXAMTWO).build(); + EditExamCommand expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // other valid values specified + userInput = targetIndex.getOneBased() + EXAMDATEONE + + INVALID_EXAM_DESCRIPTION + EXAMMODULETWO + + EXAMDESCRIPTIONTWO; + descriptor = new EditExamDescriptorBuilder().withDate(VALID_DATE_EXAMONE) + .withDescription(VALID_DESCRIPTION_EXAMTWO).withModule(VALID_MODULE_EXAMTWO).build(); + + expectedCommand = new EditExamCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + +} diff --git a/src/test/java/seedu/address/logic/parser/EditModuleCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditModuleCommandParserTest.java new file mode 100644 index 00000000000..c7f88903e5a --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EditModuleCommandParserTest.java @@ -0,0 +1,217 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_INDEX; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_CODE; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_CREDIT; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_NAME; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_CODE_ONE; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_CODE_TWO; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_CREDIT_ONE; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_CREDIT_TWO; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_NAME_ONE; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_NAME_TWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CODE_ONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CODE_TWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CREDIT_ONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CREDIT_TWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_NAME_ONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_NAME_TWO; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_MODULE; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_MODULE; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; +import seedu.address.testutil.EditModuleDescriptorBuilder; + +public class EditModuleCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditModuleCommand.MESSAGE_USAGE); + + private EditModuleCommandParser parser = new EditModuleCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, MODULE_CREDIT_ONE, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditModuleCommand.MESSAGE_NO_FIELDS_PROVIDED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + + // no prefix + assertParseFailure(parser, "1 " + VALID_MODULE_CREDIT_ONE, MESSAGE_INVALID_FORMAT); + + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-3" + MODULE_NAME_ONE, MESSAGE_INVALID_MODULE_INDEX); + + // zero index + assertParseFailure(parser, "0" + MODULE_NAME_ONE, MESSAGE_INVALID_MODULE_INDEX); + + // positive signed index + assertParseFailure(parser, "+3", MESSAGE_INVALID_MODULE_INDEX); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "2 random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "2 p/string", MESSAGE_INVALID_FORMAT); + + // number bigger than max integer + assertParseFailure(parser, "9999999999999999999999", MESSAGE_INVALID_MODULE_INDEX); + } + + @Test + public void parse_invalidValue_failure() { + + //one field + assertParseFailure(parser, "1" + INVALID_MODULE_CODE, ModuleCode.MODULE_CODE_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_MODULE_CREDIT, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_MODULE_NAME, ModuleName.MODULE_NAME_CONSTRAINTS); + + //two fields + assertParseFailure(parser, "1" + INVALID_MODULE_CODE + + MODULE_NAME_ONE, ModuleCode.MODULE_CODE_CONSTRAINTS); + assertParseFailure(parser, "1" + MODULE_NAME_ONE + + INVALID_MODULE_CREDIT, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + + //invalid module code followed by valid module credit and module name + assertParseFailure(parser, "1" + INVALID_MODULE_CODE + + MODULE_CREDIT_ONE + MODULE_NAME_ONE, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //invalid module credit followed by valid module code and module name + assertParseFailure(parser, "1" + INVALID_MODULE_CREDIT + + MODULE_CODE_ONE + MODULE_NAME_ONE, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + + // valid module name and module credit followed by invalid module code. + assertParseFailure(parser, "1" + MODULE_NAME_ONE + + MODULE_CREDIT_ONE + INVALID_MODULE_CODE, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //valid module name and module code followed by invalid module credit + assertParseFailure(parser, "1" + MODULE_NAME_ONE + + MODULE_CODE_ONE + INVALID_MODULE_CREDIT, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + + //valid module credit, then invalid module code followed by module name + assertParseFailure(parser, "1" + MODULE_CREDIT_ONE + + INVALID_MODULE_CODE + MODULE_NAME_ONE, ModuleCode.MODULE_CODE_CONSTRAINTS); + + //valid module code, then invalid module credit then module name + assertParseFailure(parser, "1" + MODULE_CODE_ONE + + INVALID_MODULE_CREDIT + MODULE_NAME_ONE, ModuleCredit.MODULE_CREDIT_CONSTRAINTS); + + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_THIRD_MODULE; + String userInput = targetIndex.getOneBased() + MODULE_CODE_ONE + MODULE_NAME_ONE + MODULE_CREDIT_ONE; + + EditModuleCommand.EditModuleDescriptor descriptor = + new EditModuleDescriptorBuilder().withModuleCode(VALID_MODULE_CODE_ONE) + .withModuleName(VALID_MODULE_NAME_ONE).withModuleCredit(VALID_MODULE_CREDIT_ONE).build(); + EditModuleCommand expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = INDEX_THIRD_MODULE; + String userInput = targetIndex.getOneBased() + MODULE_CODE_ONE + MODULE_CREDIT_ONE; + + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder() + .withModuleCode(VALID_MODULE_CODE_ONE) + .withModuleCredit(VALID_MODULE_CREDIT_ONE).build(); + EditModuleCommand expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecifiedTestTwo_success() { + Index targetIndex = INDEX_THIRD_MODULE; + String userInput = targetIndex.getOneBased() + MODULE_NAME_ONE + MODULE_CREDIT_ONE; + + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder() + .withModuleName(VALID_MODULE_NAME_ONE) + .withModuleCredit(VALID_MODULE_CREDIT_ONE).build(); + EditModuleCommand expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + + @Test + public void parse_oneFieldSpecified_success() { + // module code + Index targetIndex = INDEX_SECOND_MODULE; + String userInput = targetIndex.getOneBased() + MODULE_CODE_ONE; + EditModuleCommand.EditModuleDescriptor descriptor = new EditModuleDescriptorBuilder() + .withModuleCode(VALID_MODULE_CODE_ONE).build(); + EditModuleCommand expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // module name + userInput = targetIndex.getOneBased() + MODULE_NAME_ONE; + descriptor = new EditModuleDescriptorBuilder() + .withModuleName(VALID_MODULE_NAME_ONE).build(); + expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // module credit + userInput = targetIndex.getOneBased() + MODULE_CREDIT_ONE; + descriptor = new EditModuleDescriptorBuilder() + .withModuleCredit(VALID_MODULE_CREDIT_ONE).build(); + expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_THIRD_MODULE; + String userInput = targetIndex.getOneBased() + + MODULE_CODE_ONE + MODULE_NAME_ONE + MODULE_CREDIT_ONE + + MODULE_CODE_TWO + MODULE_NAME_TWO + MODULE_CREDIT_TWO; + + EditModuleCommand.EditModuleDescriptor descriptor = + new EditModuleDescriptorBuilder().withModuleCode(VALID_MODULE_CODE_TWO) + .withModuleName(VALID_MODULE_NAME_TWO).withModuleCredit(VALID_MODULE_CREDIT_TWO).build(); + EditModuleCommand expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_invalidValueFollowedByValidValue_success() { + // one invalid value followed by one valid value + Index targetIndex = INDEX_THIRD_MODULE; + String userInput = targetIndex.getOneBased() + + INVALID_MODULE_CODE + MODULE_CODE_ONE; + EditModuleCommand.EditModuleDescriptor descriptor = + new EditModuleDescriptorBuilder().withModuleCode(VALID_MODULE_CODE_ONE).build(); + EditModuleCommand expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // one invalid value in the middle of multiple valid values + userInput = targetIndex.getOneBased() + MODULE_CODE_ONE + + INVALID_MODULE_CREDIT + MODULE_NAME_ONE + + MODULE_CREDIT_ONE; + descriptor = new EditModuleDescriptorBuilder().withModuleCode(VALID_MODULE_CODE_ONE) + .withModuleName(VALID_MODULE_NAME_ONE).withModuleCredit(VALID_MODULE_CREDIT_ONE).build(); + + expectedCommand = new EditModuleCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + +} diff --git a/src/test/java/seedu/address/logic/parser/EditTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditTagCommandParserTest.java new file mode 100644 index 00000000000..8724c42156a --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EditTagCommandParserTest.java @@ -0,0 +1,121 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditTagCommand; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.testutil.DeadlineTagBuilder; +import seedu.address.testutil.PriorityTagBuilder; + + +public class EditTagCommandParserTest { + private EditTagCommandParser parser = new EditTagCommandParser(); + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-uuuu"); + + private final String invalidCommandFormatMessage = String + .format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + EditTagCommand.MESSAGE_USAGE); + @Test + public void parse_allFieldsPresent_success() { + PriorityTag expectedPriorityTag = new PriorityTagBuilder().withStatus("MEDIUM").build(); + DeadlineTag expectedDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("25-12-2022", dateTimeFormatter)).build(); + Index expectedIndex = INDEX_SECOND_TASK; + String allFieldsPresentArgument = "2" + " " + PREFIX_PRIORITY_STATUS + "MEDIUM" + + " " + PREFIX_DEADLINE + "25-12-2022"; + String allFieldsPresentWithWhitespace = " 2" + " " + PREFIX_PRIORITY_STATUS + "\t\t" + + "MEDIUM" + " " + PREFIX_DEADLINE + "\t\t\t25-12-2022"; + + //Valid input with all fields present + assertParseSuccess(parser, allFieldsPresentArgument, + new EditTagCommand(expectedIndex, expectedPriorityTag, expectedDeadlineTag)); + + //Valid input with all fields present + assertParseSuccess(parser, allFieldsPresentWithWhitespace, + new EditTagCommand(expectedIndex, expectedPriorityTag, expectedDeadlineTag)); + } + + @Test + public void parse_deadlineAndIndexPresent_success() { + DeadlineTag expectedDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("24-12-2022", dateTimeFormatter)).build(); + String deadlineAndIndexPresent = "2" + " " + PREFIX_DEADLINE + "24-12-2022"; + assertParseSuccess(parser, deadlineAndIndexPresent, + new EditTagCommand(INDEX_SECOND_TASK, null, expectedDeadlineTag)); + } + + @Test + public void parse_priorityAndIndexPresent_success() { + PriorityTag expectedPriorityTag = new PriorityTagBuilder() + .withStatus("MEDIUM").build(); + String priorityAndIndexPresent = "2" + " " + PREFIX_PRIORITY_STATUS + "MEDIUM"; + assertParseSuccess(parser, priorityAndIndexPresent, + new EditTagCommand(INDEX_SECOND_TASK, expectedPriorityTag, null)); + } + + @Test + public void parse_missingFields_failure() { + + //Missing index + String missingIndex = " " + PREFIX_DEADLINE + "26-12-2022" + " " + PREFIX_PRIORITY_STATUS + "HIGH"; + assertParseFailure(parser, missingIndex, invalidCommandFormatMessage); + + //Missing priority prefix and deadline prefix + String missingPriorityAndDeadlinePrefix = "4" + " 26-12-2022" + " HIGH"; + assertParseFailure(parser, missingPriorityAndDeadlinePrefix, invalidCommandFormatMessage); + } + + @Test + public void parse_invalidInputs_failure() { + + //Empty deadline field + String emptyDeadlineField = "7 " + PREFIX_PRIORITY_STATUS + "MEDIUM " + PREFIX_DEADLINE; + assertParseFailure(parser, emptyDeadlineField, DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS); + + //Empty Priority field + String emptyPriorityField = "66 " + PREFIX_DEADLINE + "29-11-2022 " + PREFIX_PRIORITY_STATUS; + assertParseFailure(parser, emptyPriorityField, PriorityTag.PRIORITY_TAG_CONSTRAINTS); + + //Invalid index(Not a number) + assertParseFailure(parser, "abc p/low", invalidCommandFormatMessage); + + //Invalid index(Non-positive) + String invalidIndexMessage = EditTagCommandParser.INVALID_INDEX_EDIT_TAG; + assertParseFailure(parser, "-50 " + PREFIX_PRIORITY_STATUS + "LOW", invalidIndexMessage); + + //Invalid index(Above INT_MAX_VALUE) + assertParseFailure(parser, "9999999999999999999999 " + PREFIX_PRIORITY_STATUS + "HIGH", + invalidIndexMessage); + + //Invalid status for priority tag + assertParseFailure(parser, "1 " + PREFIX_PRIORITY_STATUS + "status", + PriorityTag.PRIORITY_TAG_CONSTRAINTS); + + //Invalid date format for deadline tag + assertParseFailure(parser, "2 " + PREFIX_DEADLINE + "17/01/2023", + DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS); + + //Invalid date chosen for deadline tag + assertParseFailure(parser, "42 " + PREFIX_DEADLINE + "29-02-2023", + DeadlineTag.DEADLINE_TAG_INVALID_DATE); + + //Date has passed for deadline tag + assertParseFailure(parser, "5 " + PREFIX_DEADLINE + "01-01-2000", + DeadlineTag.DEADLINE_TAG_DATE_HAS_PASSED); + + } + + +} diff --git a/src/test/java/seedu/address/logic/parser/EditTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditTaskCommandParserTest.java new file mode 100644 index 00000000000..3d34f1a7ae4 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EditTaskCommandParserTest.java @@ -0,0 +1,147 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_WATCH_LECTURE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_LECTURE; +import static seedu.address.logic.commands.CommandTestUtil.DESC_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TASK_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_CS2030; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_CS2040; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_WATCH_LECTURE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2040; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.TaskDescription; +import seedu.address.testutil.EditTaskDescriptorBuilder; + +/** + * Contains unit tests for {@code EditCommandParser}. + */ +public class EditTaskCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE); + + private EditTaskCommandParser parser = new EditTaskCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, MODULE_DESC_CS2030, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditTaskCommand.MESSAGE_NO_FIELDS_PROVIDED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidPreamble_failure() { + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + + // negative index + assertParseFailure(parser, "-5" + MODULE_DESC_CS2030, MESSAGE_INVALID_TASK_INDEX); + + // zero index + assertParseFailure(parser, "0" + MODULE_DESC_CS2030, MESSAGE_INVALID_TASK_INDEX); + + // big integer + assertParseFailure(parser, "2147483648" + MODULE_DESC_CS2030, MESSAGE_INVALID_TASK_INDEX); + } + + @Test + public void parse_invalidValue_failure() { + // invalid module + assertParseFailure(parser, "1" + INVALID_MODULE_DESC, ModuleCode.MODULE_CODE_CONSTRAINTS); + + // invalid description + assertParseFailure(parser, "1" + INVALID_TASK_DESCRIPTION_DESC, + TaskDescription.DESCRIPTION_CONSTRAINTS); + + // invalid module followed by valid description + assertParseFailure(parser, "1" + INVALID_MODULE_DESC + DESCRIPTION_DESC_DO_TUTORIAL, + ModuleCode.MODULE_CODE_CONSTRAINTS); + + // valid module followed by invalid module + assertParseFailure(parser, "1" + MODULE_DESC_CS2030 + INVALID_MODULE_DESC, + ModuleCode.MODULE_CODE_CONSTRAINTS); + + // invalid module and invalid description, but only the invalid module is captured + assertParseFailure(parser, "1" + INVALID_TASK_DESCRIPTION_DESC + INVALID_MODULE_DESC, + ModuleCode.MODULE_CODE_CONSTRAINTS); + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_FIRST_TASK; + String userInput = targetIndex.getOneBased() + MODULE_DESC_CS2030 + DESCRIPTION_DESC_DO_TUTORIAL; + + EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, DESC_TUTORIAL); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_oneFieldSpecified_success() { + // only description specified + Index targetIndex = INDEX_FIRST_TASK; + String userInput = targetIndex.getOneBased() + DESCRIPTION_DESC_WATCH_LECTURE; + + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_WATCH_LECTURE).build(); + EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + + // only module specified + userInput = targetIndex.getOneBased() + MODULE_DESC_CS2040; + descriptor = new EditTaskDescriptorBuilder() + .withModule(VALID_MODULE_CS2040).build(); + expectedCommand = new EditTaskCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_FIRST_TASK; + String userInput = targetIndex.getOneBased() + MODULE_DESC_CS2030 + DESCRIPTION_DESC_DO_TUTORIAL + + MODULE_DESC_CS2040 + DESCRIPTION_DESC_WATCH_LECTURE; + + EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, DESC_LECTURE); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_invalidValueFollowedByValidValue_success() { + // no other valid values specified + Index targetIndex = INDEX_FIRST_TASK; + String userInput = targetIndex.getOneBased() + INVALID_TASK_DESCRIPTION_DESC + DESCRIPTION_DESC_DO_TUTORIAL; + EditTaskCommand.EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // other valid values specified + userInput = targetIndex.getOneBased() + MODULE_DESC_CS2030 + INVALID_TASK_DESCRIPTION_DESC + + DESCRIPTION_DESC_DO_TUTORIAL; + expectedCommand = new EditTaskCommand(targetIndex, DESC_TUTORIAL); + assertParseSuccess(parser, userInput, expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FilterTasksCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterTasksCommandParserTest.java new file mode 100644 index 00000000000..71207763249 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FilterTasksCommandParserTest.java @@ -0,0 +1,110 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.commands.CommandTestUtil.COMPLETION_STATUS_DESC_NO; +import static seedu.address.logic.commands.CommandTestUtil.COMPLETION_STATUS_DESC_YES; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_COMPLETION_STATUS_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_LINK_STATUS_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE; +import static seedu.address.logic.commands.CommandTestUtil.LINK_STATUS_DESC_NO; +import static seedu.address.logic.commands.CommandTestUtil.LINK_STATUS_DESC_YES; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_CS2030; +import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_CS2040; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalModules.CS2030; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.FilterTasksCommand; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.FilterPredicate; + +public class FilterTasksCommandParserTest { + private FilterTasksCommandParser parser = new FilterTasksCommandParser(); + + @Test + public void parse_modulePresent_success() { + FilterPredicate expectedPredicate = new FilterPredicate(Optional.of(CS2030), Optional.empty(), + Optional.empty()); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + MODULE_DESC_CS2030, + new FilterTasksCommand(expectedPredicate)); + + // multiple modules - last module accepted + assertParseSuccess(parser, MODULE_DESC_CS2040 + MODULE_DESC_CS2030, + new FilterTasksCommand(expectedPredicate)); + } + + @Test + public void parse_commpletionStatusPresent_success() { + FilterPredicate expectedPredicate = new FilterPredicate(Optional.empty(), Optional.of(true), + Optional.empty()); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + COMPLETION_STATUS_DESC_YES, + new FilterTasksCommand(expectedPredicate)); + + // multiple completion status - last completion status accepted + assertParseSuccess(parser, COMPLETION_STATUS_DESC_NO + COMPLETION_STATUS_DESC_YES, + new FilterTasksCommand(expectedPredicate)); + } + + @Test + public void parse_linkStatusPresent_success() { + FilterPredicate expectedPredicate = new FilterPredicate(Optional.empty(), Optional.empty(), + Optional.of(true)); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + LINK_STATUS_DESC_YES, + new FilterTasksCommand(expectedPredicate)); + + // multiple link status - last link status accepted + assertParseSuccess(parser, LINK_STATUS_DESC_NO + LINK_STATUS_DESC_YES, + new FilterTasksCommand(expectedPredicate)); + } + + @Test + public void parse_allFieldsPresent_success() { + FilterPredicate expectedPredicate = new FilterPredicate(Optional.of(CS2030), Optional.of(true), + Optional.of(true)); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + MODULE_DESC_CS2030 + COMPLETION_STATUS_DESC_YES + + LINK_STATUS_DESC_YES, new FilterTasksCommand(expectedPredicate)); + + // multiple modules - last module accepted + assertParseSuccess(parser, MODULE_DESC_CS2040 + MODULE_DESC_CS2030 + COMPLETION_STATUS_DESC_YES + + LINK_STATUS_DESC_YES, new FilterTasksCommand(expectedPredicate)); + + // multiple completion status - last completion status accepted + assertParseSuccess(parser, MODULE_DESC_CS2030 + COMPLETION_STATUS_DESC_NO + COMPLETION_STATUS_DESC_YES + + LINK_STATUS_DESC_YES, new FilterTasksCommand(expectedPredicate)); + + // multiple link status - last link status accepted + assertParseSuccess(parser, MODULE_DESC_CS2030 + COMPLETION_STATUS_DESC_YES + LINK_STATUS_DESC_NO + + LINK_STATUS_DESC_YES, new FilterTasksCommand(expectedPredicate)); + } + + @Test + public void parse_invalidValue_failure() { + // invalid module + assertParseFailure(parser, INVALID_MODULE + COMPLETION_STATUS_DESC_YES + LINK_STATUS_DESC_YES, + ModuleCode.MODULE_CODE_CONSTRAINTS); + + // invalid completion status + assertParseFailure(parser, MODULE_DESC_CS2030 + INVALID_COMPLETION_STATUS_DESC + LINK_STATUS_DESC_YES, + FilterTasksCommandParser.RESPONSE_CONSTRAINTS); + + // invalid link status + assertParseFailure(parser, MODULE_DESC_CS2030 + COMPLETION_STATUS_DESC_YES + INVALID_LINK_STATUS_DESC, + FilterTasksCommandParser.RESPONSE_CONSTRAINTS); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_MODULE + INVALID_COMPLETION_STATUS_DESC + LINK_STATUS_DESC_YES, + ModuleCode.MODULE_CODE_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java deleted file mode 100644 index 70f4f0e79c4..00000000000 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; - -import java.util.Arrays; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -public class FindCommandParserTest { - - private FindCommandParser parser = new FindCommandParser(); - - @Test - public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - @Test - public void parse_validArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); - } - -} diff --git a/src/test/java/seedu/address/logic/parser/FindModulesCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindModulesCommandParserTest.java new file mode 100644 index 00000000000..162e29d30fb --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindModulesCommandParserTest.java @@ -0,0 +1,58 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.FindModulesCommand; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; + +public class FindModulesCommandParserTest { + + private FindModulesCommandParser parser = new FindModulesCommandParser(); + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindModulesCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindModulesCommand() { + // no leading and trailing whitespaces + FindModulesCommand expectedFindModulesCommand = + new FindModulesCommand(new ModuleCodeContainsKeywordsPredicate(Arrays.asList("cs2030s"))); + assertParseSuccess(parser, "cs2030s", expectedFindModulesCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \t \n cs2030s\n \t \t", expectedFindModulesCommand); + } + + @Test + public void parse_validAllCapsArgsPassToPredicateAsLowerCase_returnsFindModulesCommand() { + // no leading and trailing whitespaces + FindModulesCommand expectedFindModulesCommand = + new FindModulesCommand(new ModuleCodeContainsKeywordsPredicate(Arrays.asList("cs2030s"))); + assertParseSuccess(parser, "CS2030S", expectedFindModulesCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \t \n CS2030s \n \t \t", expectedFindModulesCommand); + } + + @Test + public void parse_validMixCaseArgsPassToPredicateAsLowerCase_returnsFindCommand() { + // no leading and trailing whitespaces + FindModulesCommand expectedFindModulesCommand = + new FindModulesCommand(new ModuleCodeContainsKeywordsPredicate(Arrays.asList("cs2030s"))); + assertParseSuccess(parser, "Cs2030S", expectedFindModulesCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \t \n cS2030s \n \t \t", expectedFindModulesCommand); + } + + +} diff --git a/src/test/java/seedu/address/logic/parser/FindTasksCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindTasksCommandParserTest.java new file mode 100644 index 00000000000..7f090de4214 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindTasksCommandParserTest.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.FindTasksCommand; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; + +public class FindTasksCommandParserTest { + + private FindTaskCommandParser parser = new FindTaskCommandParser(); + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTasksCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindTasksCommand() { + // no leading and trailing whitespaces + FindTasksCommand expectedFindTasksCommand = + new FindTasksCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList("ask, on"))); + assertParseSuccess(parser, "ask, on", expectedFindTasksCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \t \n ask, on\n \t \t", expectedFindTasksCommand); + } + + @Test + public void parse_validAllCapsArgsPassToPredicateAsLowerCase_returnsFindTasksCommand() { + // no leading and trailing whitespaces + FindTasksCommand expectedFindTasksCommand = + new FindTasksCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList("ask, on"))); + assertParseSuccess(parser, "ASK, ON", expectedFindTasksCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \t \n ASK, ON\n \t \t", expectedFindTasksCommand); + } + + @Test + public void parse_validMixCaseArgsPassToPredicateAsLowerCase_returnsFindTasksCommand() { + // no leading and trailing whitespaces + FindTasksCommand expectedFindTasksCommand = + new FindTasksCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList("ask, on"))); + assertParseSuccess(parser, "AsK, On", expectedFindTasksCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \t \n AsK, On\n \t \t", expectedFindTasksCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/LinkExamCommandParserTest.java b/src/test/java/seedu/address/logic/parser/LinkExamCommandParserTest.java new file mode 100644 index 00000000000..272642fa142 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/LinkExamCommandParserTest.java @@ -0,0 +1,94 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_EXAM; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.LinkExamCommand; + +public class LinkExamCommandParserTest { + private LinkExamCommandParser parser = new LinkExamCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Index examIndex = INDEX_FIRST_EXAM; + Index taskIndex = INDEX_FIRST_TASK; + + String allFieldsPresent = " " + PREFIX_EXAM_INDEX + "1" + " " + + PREFIX_TASK_INDEX + "1"; + //Valid input with all fields present + assertParseSuccess(parser, allFieldsPresent, + new LinkExamCommand(examIndex, taskIndex)); + + //Valid input with whitespace between prefix and argument + String allFieldsPresentWithWhitespace = " " + PREFIX_EXAM_INDEX + "\t " + + "1" + " " + PREFIX_TASK_INDEX + " \t" + "1"; + assertParseSuccess(parser, allFieldsPresentWithWhitespace, + new LinkExamCommand(examIndex, taskIndex)); + } + + @Test + public void parse_missingFields_failure() { + String missingExamPrefix = " 2 " + PREFIX_TASK_INDEX + "2"; + String missingTaskPrefix = " " + PREFIX_EXAM_INDEX + "2" + + " 2"; + String missingAllPrefixes = " 2 2"; + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + LinkExamCommand.MESSAGE_USAGE); + //Missing exam prefix + assertParseFailure(parser, missingExamPrefix, expectedMessage); + + //Missing task prefix + assertParseFailure(parser, missingTaskPrefix, expectedMessage); + + //Missing all prefixes + assertParseFailure(parser, missingAllPrefixes, expectedMessage); + } + + @Test + public void parse_invalidFields_failure() { + String nonEmptyPreamble = " gibberish" + PREFIX_EXAM_INDEX + "2" + " " + + PREFIX_TASK_INDEX + "2"; + String negativeTaskIndex = " " + PREFIX_EXAM_INDEX + "2" + " " + + PREFIX_TASK_INDEX + "-1"; + String largeNumberTaskIndex = " " + PREFIX_EXAM_INDEX + "2" + " " + + PREFIX_TASK_INDEX + "99999999999999999999999"; + String nonNumberTaskIndex = " " + PREFIX_EXAM_INDEX + "2" + " " + + PREFIX_TASK_INDEX + "NOTHING"; + String negativeExamIndex = " " + PREFIX_EXAM_INDEX + "-2" + " " + + PREFIX_TASK_INDEX + "1"; + String largeNumberExamIndex = " " + PREFIX_EXAM_INDEX + "99999999999999999999999" + + " " + PREFIX_TASK_INDEX + "1"; + String nonNumberExamIndex = " " + PREFIX_EXAM_INDEX + "NOTHING" + " " + + PREFIX_TASK_INDEX + "1"; + + //Non-empty preamble + assertParseFailure(parser, nonEmptyPreamble, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + LinkExamCommand.MESSAGE_USAGE)); + + //Negative task index + assertParseFailure(parser, negativeTaskIndex, LinkExamCommandParser.TASK_INDEX_INVALID); + + //Out of bounds large number(Greater than INT_MAX_VALUE) for task index + assertParseFailure(parser, largeNumberTaskIndex, LinkExamCommandParser.TASK_INDEX_INVALID); + + //Task index is not a number + assertParseFailure(parser, nonNumberTaskIndex, LinkExamCommandParser.TASK_INDEX_INVALID); + + //Negative exam index + assertParseFailure(parser, negativeExamIndex, LinkExamCommandParser.EXAM_INDEX_INVALID); + + //Out of bounds large number(Greater than INT_MAX_VALUE) for exam index + assertParseFailure(parser, largeNumberExamIndex, LinkExamCommandParser.EXAM_INDEX_INVALID); + + //Exam index is not a number + assertParseFailure(parser, nonNumberExamIndex, LinkExamCommandParser.EXAM_INDEX_INVALID); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ListExamTasksCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ListExamTasksCommandParserTest.java new file mode 100644 index 00000000000..b5300050267 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ListExamTasksCommandParserTest.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.FIRST_INDEX; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.ListExamTasksCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the ListExamTasksCommand code. For example, inputs "1" and "1 abc" take the + * same path through the ListExamTasksCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class ListExamTasksCommandParserTest { + private ListExamTasksCommandParser parser = new ListExamTasksCommandParser(); + + @Test + public void parse_validArgs_returnsListExamTasksCommand() { + assertParseSuccess(parser, "1", new ListExamTasksCommand(FIRST_INDEX)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // empty string + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ListExamTasksCommand.MESSAGE_USAGE)); + + // wrong type + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ListExamTasksCommand.MESSAGE_USAGE)); + + // big integer + assertParseFailure(parser, "2147483648", MESSAGE_INVALID_EXAM_INDEX); + + // negative integer + assertParseFailure(parser, "-1", MESSAGE_INVALID_EXAM_INDEX); + } +} diff --git a/src/test/java/seedu/address/logic/parser/MarkCommandParserTest.java b/src/test/java/seedu/address/logic/parser/MarkCommandParserTest.java new file mode 100644 index 00000000000..bae0ed29cfe --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/MarkCommandParserTest.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.MarkCommand; + +/** + * Contains unit tests for {@code MarkCommandParser}. + */ +public class MarkCommandParserTest { + + private MarkCommandParser parser = new MarkCommandParser(); + + @Test + public void parse_validArgs_returnsMarkCommand() { + assertParseSuccess(parser, "1", new MarkCommand(INDEX_FIRST_TASK)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // empty + assertParseFailure(parser, " ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + + // non-number + assertParseFailure(parser, "a", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + + // zero index + assertParseFailure(parser, "0", MESSAGE_INVALID_TASK_INDEX); + + // big integer + assertParseFailure(parser, "2147483648", MESSAGE_INVALID_TASK_INDEX); + + // negative integer + assertParseFailure(parser, "-21", MESSAGE_INVALID_TASK_INDEX); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..1511da0346e 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -1,41 +1,69 @@ package seedu.address.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMONE; import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; +import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_KEYWORDS; +import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_NUMBER_OF_KEYWORDS; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import java.util.Arrays; -import java.util.Collections; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.HashSet; import java.util.Set; import org.junit.jupiter.api.Test; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.Criteria; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.TaskDescription; public class ParserUtilTest { - private static final String INVALID_NAME = "R@chel"; - private static final String INVALID_PHONE = "+651234"; - private static final String INVALID_ADDRESS = " "; - private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; - - private static final String VALID_NAME = "Rachel Walker"; - private static final String VALID_PHONE = "123456"; - private static final String VALID_ADDRESS = "123 Main Street #0505"; - private static final String VALID_EMAIL = "rachel@example.com"; - private static final String VALID_TAG_1 = "friend"; - private static final String VALID_TAG_2 = "neighbour"; private static final String WHITESPACE = " \t\r\n"; + private static final String INVALID_TASK_DESCRIPTION = ""; + private static final String INVALID_EXAM_DATE_WRONG_FORMAT = "29 Dec 2024"; + private static final String INVALID_EXAM_DATE_DOES_NOT_EXIST = "29-02-2023"; + private static final String INVALID_EXAM_DATE_PAST_DATE = "20-02-2010"; + + private static final String INVALID_MODULE_CODE = "2100"; + private static final String INVALID_MODULE_NAME = ""; + private static final String INVALID_MODULE_CREDIT_NOT_AN_INT = "word"; + private static final String INVALID_MODULE_CREDIT_EXCEED_LIMIT = "50"; + + private static final String INVALID_CRITERIA = "pri"; + private static final String INVALID_PRIORITY = "highlow"; + private static final String INVALID_DEADLINE_WRONG_FORMAT = "30 Dec 2024"; + private static final String INVALID_DEADLINE_INVALID_DATE = "29-02-2023"; + private static final String INVALID_DEADLINE_DATE_PASSED = "30-12-2010"; + private static final String INVALID_KEYWORDS_TOO_MANY = "priority deadline priority"; + private static final String INVALID_KEYWORDS_WRONG_KEYWORDS = "priority dl"; + + private static final String VALID_TASK_DESCRIPTION = "task 1"; + + private static final String VALID_EXAM_DATE = "30-05-2025"; + + private static final String VALID_MODULE_CODE = "cs2100"; + private static final String VALID_MODULE_NAME = "Computer Organisation"; + private static final String VALID_MODULE_CREDIT = "20"; + private static final Integer VALID_MODULE_CREDIT_INT = 20; + + private static final String VALID_CRITERIA = "priority"; + private static final String VALID_PRIORITY = "high"; + private static final String VALID_DEADLINE = "30-12-2024"; + private static final String VALID_KEYWORDS = "priority deadline"; + private static final String VALID_KEYWORDS_WITH_WHITE_SPACE = " priority deadline "; + @Test public void parseIndex_invalidInput_throwsParseException() { assertThrows(ParseException.class, () -> ParserUtil.parseIndex("10 a")); @@ -57,140 +85,260 @@ public void parseIndex_validInput_success() throws Exception { } @Test - public void parseName_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseName((String) null)); + public void parseDescription_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseDescription((String) null)); + } + + @Test + public void parseDescription_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseDescription(INVALID_TASK_DESCRIPTION)); + assertThrows(ParseException.class, () -> ParserUtil.parseDescription(WHITESPACE)); + } + + @Test + public void parseDescription_validValueWithoutWhitespace_returnsTaskDescription() throws Exception { + TaskDescription expectedTaskDescription = new TaskDescription(VALID_TASK_DESCRIPTION); + assertEquals(expectedTaskDescription, ParserUtil.parseDescription(VALID_TASK_DESCRIPTION)); + } + + @Test + public void parseDescription_validValueWithWhitespace_returnsTrimmedTaskDescription() throws Exception { + String taskDescriptionWithWhitespace = WHITESPACE + VALID_TASK_DESCRIPTION + WHITESPACE; + TaskDescription expectedTaskDescription = new TaskDescription(VALID_TASK_DESCRIPTION); + assertEquals(expectedTaskDescription, ParserUtil.parseDescription(taskDescriptionWithWhitespace)); + } + + @Test + public void parseModuleCode_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseModuleCode((String) null)); + } + + @Test + public void parseModuleCode_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseModuleCode(INVALID_MODULE_CODE)); + assertThrows(ParseException.class, () -> ParserUtil.parseModuleCode(WHITESPACE)); + } + + @Test + public void parseModuleCode_validValueWithoutWhitespace_returnsModuleCode() throws Exception { + ModuleCode expectedModuleCode = new ModuleCode(VALID_MODULE_CODE); + assertEquals(expectedModuleCode, ParserUtil.parseModuleCode(VALID_MODULE_CODE)); + } + + @Test + public void parseModuleCode_validValueWithWhitespace_returnsTrimmedModuleCode() throws Exception { + String moduleCodeWithWhitespace = WHITESPACE + VALID_MODULE_CODE + WHITESPACE; + ModuleCode expectedModuleCode = new ModuleCode(VALID_MODULE_CODE); + assertEquals(expectedModuleCode, ParserUtil.parseModuleCode(moduleCodeWithWhitespace)); + } + + @Test + public void parseModuleName_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseModuleCode((String) null)); } @Test - public void parseName_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseName(INVALID_NAME)); + public void parseModuleName_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseModuleName(INVALID_MODULE_NAME)); + assertThrows(ParseException.class, () -> ParserUtil.parseModuleName(WHITESPACE)); } @Test - public void parseName_validValueWithoutWhitespace_returnsName() throws Exception { - Name expectedName = new Name(VALID_NAME); - assertEquals(expectedName, ParserUtil.parseName(VALID_NAME)); + public void parseModuleName_validValueWithoutWhitespace_returnsModuleName() throws Exception { + ModuleName expectedModuleName = new ModuleName(VALID_MODULE_NAME); + assertEquals(expectedModuleName, ParserUtil.parseModuleName(VALID_MODULE_NAME)); } @Test - public void parseName_validValueWithWhitespace_returnsTrimmedName() throws Exception { - String nameWithWhitespace = WHITESPACE + VALID_NAME + WHITESPACE; - Name expectedName = new Name(VALID_NAME); - assertEquals(expectedName, ParserUtil.parseName(nameWithWhitespace)); + public void parseModuleName_validValueWithWhitespace_returnsTrimmedModuleName() throws Exception { + String moduleNameWithWhitespace = WHITESPACE + VALID_MODULE_NAME + WHITESPACE; + ModuleName expectedModuleName = new ModuleName(VALID_MODULE_NAME); + assertEquals(expectedModuleName, ParserUtil.parseModuleName(moduleNameWithWhitespace)); } @Test - public void parsePhone_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parsePhone((String) null)); + public void parseModuleCredit_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseModuleCredit((String) null)); } @Test - public void parsePhone_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parsePhone(INVALID_PHONE)); + public void parseModuleCredit_invalidValue_throwsParseException() { + // not an integer + assertThrows(ParseException.class, () -> ParserUtil.parseModuleCredit(INVALID_MODULE_CREDIT_NOT_AN_INT)); + + // not a valid module code + assertThrows(ParseException.class, () -> ParserUtil.parseModuleCredit(INVALID_MODULE_CREDIT_EXCEED_LIMIT)); } @Test - public void parsePhone_validValueWithoutWhitespace_returnsPhone() throws Exception { - Phone expectedPhone = new Phone(VALID_PHONE); - assertEquals(expectedPhone, ParserUtil.parsePhone(VALID_PHONE)); + public void parseModuleCredit_validValueWithoutWhitespace_returnsModuleCredit() throws Exception { + ModuleCredit expectedModuleCredit = new ModuleCredit(VALID_MODULE_CREDIT_INT); + assertEquals(expectedModuleCredit, ParserUtil.parseModuleCredit(VALID_MODULE_CREDIT)); } @Test - public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exception { - String phoneWithWhitespace = WHITESPACE + VALID_PHONE + WHITESPACE; - Phone expectedPhone = new Phone(VALID_PHONE); - assertEquals(expectedPhone, ParserUtil.parsePhone(phoneWithWhitespace)); + public void parseModuleCredit_validValueWithWhitespace_returnsTrimmedModuleCredit() throws Exception { + String moduleCreditWithWhitespace = WHITESPACE + VALID_MODULE_CREDIT + WHITESPACE; + ModuleCredit expectedModuleCredit = new ModuleCredit(VALID_MODULE_CREDIT_INT); + assertEquals(expectedModuleCredit, ParserUtil.parseModuleCredit(moduleCreditWithWhitespace)); } @Test - public void parseAddress_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null)); + public void parseCriteria_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseCriteria((String) null)); } @Test - public void parseAddress_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS)); + public void parseCriteria_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseCriteria(INVALID_CRITERIA)); } @Test - public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception { - Address expectedAddress = new Address(VALID_ADDRESS); - assertEquals(expectedAddress, ParserUtil.parseAddress(VALID_ADDRESS)); + public void parseCriteria_validValueWithoutWhitespace_returnsCriteria() throws Exception { + Criteria expectedCriteria = new Criteria(VALID_CRITERIA); + assertEquals(expectedCriteria, ParserUtil.parseCriteria(VALID_CRITERIA)); } @Test - public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws Exception { - String addressWithWhitespace = WHITESPACE + VALID_ADDRESS + WHITESPACE; - Address expectedAddress = new Address(VALID_ADDRESS); - assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace)); + public void parseCriteria_validValueWithWhitespace_returnsTrimmedCriteria() throws Exception { + String criteriaWithWhitespace = WHITESPACE + VALID_CRITERIA + WHITESPACE; + Criteria expectedCriteria = new Criteria(VALID_CRITERIA); + assertEquals(expectedCriteria, ParserUtil.parseCriteria(criteriaWithWhitespace)); } @Test - public void parseEmail_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail((String) null)); + public void parsePriorityTag_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parsePriorityTag((String) null)); } @Test - public void parseEmail_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseEmail(INVALID_EMAIL)); + public void parsePriorityTag_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parsePriorityTag(INVALID_PRIORITY)); } @Test - public void parseEmail_validValueWithoutWhitespace_returnsEmail() throws Exception { - Email expectedEmail = new Email(VALID_EMAIL); - assertEquals(expectedEmail, ParserUtil.parseEmail(VALID_EMAIL)); + public void parsePriorityTag_validValueWithoutWhitespace_returnsPriorityTag() throws Exception { + PriorityTag expectedPriorityTag = new PriorityTag(VALID_PRIORITY); + assertEquals(expectedPriorityTag, ParserUtil.parsePriorityTag(VALID_PRIORITY)); } @Test - public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exception { - String emailWithWhitespace = WHITESPACE + VALID_EMAIL + WHITESPACE; - Email expectedEmail = new Email(VALID_EMAIL); - assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace)); + public void parsePriorityTag_validValueWithWhitespace_returnsTrimmedPriorityTag() throws Exception { + String priorityTagWithWhitespace = WHITESPACE + VALID_PRIORITY + WHITESPACE; + PriorityTag expectedPriorityTag = new PriorityTag(VALID_PRIORITY); + assertEquals(expectedPriorityTag, ParserUtil.parsePriorityTag(priorityTagWithWhitespace)); } @Test - public void parseTag_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null)); + public void parseDeleteTagKeywords_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseDeleteTagKeywords((String) null)); } @Test - public void parseTag_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG)); + public void parseDeleteTagKeywords_invalidNumOfKeywords_throwsParseException() { + assertThrows(ParseException.class, MESSAGE_INVALID_NUMBER_OF_KEYWORDS, () -> + ParserUtil.parseDeleteTagKeywords(INVALID_KEYWORDS_TOO_MANY)); } @Test - public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception { - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1)); + public void parseDeleteTagKeywords_wrongKeywords_throwsParseException() { + assertThrows(ParseException.class, MESSAGE_INVALID_KEYWORDS, () -> + ParserUtil.parseDeleteTagKeywords(INVALID_KEYWORDS_WRONG_KEYWORDS)); } @Test - public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Exception { - String tagWithWhitespace = WHITESPACE + VALID_TAG_1 + WHITESPACE; - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace)); + public void parseDeleteTagKeywords_validValueWithoutWhitespace_success() throws Exception { + Set expectedKeywordSet = new HashSet<>(); + expectedKeywordSet.add("priority"); + expectedKeywordSet.add("deadline"); + assertEquals(expectedKeywordSet, ParserUtil.parseDeleteTagKeywords(VALID_KEYWORDS)); } @Test - public void parseTags_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null)); + public void parseDeleteTagKeywords_validValueWithWhitespace_success() throws Exception { + Set expectedKeywordSet = new HashSet<>(); + expectedKeywordSet.add("priority"); + expectedKeywordSet.add("deadline"); + assertEquals(expectedKeywordSet, ParserUtil.parseDeleteTagKeywords(VALID_KEYWORDS_WITH_WHITE_SPACE)); } @Test - public void parseTags_collectionWithInvalidTags_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG))); + public void parseDeadlineTag_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseDeadlineTag((String) null)); } @Test - public void parseTags_emptyCollection_returnsEmptySet() throws Exception { - assertTrue(ParserUtil.parseTags(Collections.emptyList()).isEmpty()); + public void parseDeadlineTag_invalidValue_throwsParseException() { + assertThrows(ParseException.class, DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS, () -> + ParserUtil.parseDeadlineTag(INVALID_DEADLINE_WRONG_FORMAT)); + assertThrows(ParseException.class, DeadlineTag.DEADLINE_TAG_INVALID_DATE, () -> + ParserUtil.parseDeadlineTag(INVALID_DEADLINE_INVALID_DATE)); + assertThrows(ParseException.class, DeadlineTag.DEADLINE_TAG_DATE_HAS_PASSED, () -> + ParserUtil.parseDeadlineTag(INVALID_DEADLINE_DATE_PASSED)); } @Test - public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { - Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); - Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + public void parseDeadlineTag_validValueWithoutWhitespace_returnsDeadlineTag() throws Exception { + DeadlineTag expectedDeadlineTag = new DeadlineTag( + LocalDate.parse(VALID_DEADLINE, DateTimeFormatter.ofPattern("dd-MM-yyyy"))); + assertEquals(expectedDeadlineTag, ParserUtil.parseDeadlineTag(VALID_DEADLINE)); + } + @Test + public void parseExamDate_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseExamDate((String) null)); + } - assertEquals(expectedTagSet, actualTagSet); + @Test + public void parseExamDate_invalidValue_throwsParseException() { + assertThrows(ParseException.class, + ExamDate.DATE_FORMAT_CONSTRAINTS, () -> ParserUtil.parseExamDate(INVALID_EXAM_DATE_WRONG_FORMAT)); + assertThrows(ParseException.class, + ExamDate.VALID_DATE_CONSTRAINTS, () -> ParserUtil.parseExamDate(INVALID_EXAM_DATE_DOES_NOT_EXIST)); + assertThrows(ParseException.class, + ExamDate.NOT_A_PAST_DATE_CONSTRAINTS, () -> ParserUtil.parseExamDate(INVALID_EXAM_DATE_PAST_DATE)); } + + @Test + public void parseExamDate_validValueWithoutWhitespace_returnsExamDate() throws Exception { + ExamDate expectedExamDate = new ExamDate(VALID_EXAM_DATE); + assertEquals(expectedExamDate, ParserUtil.parseExamDate(VALID_EXAM_DATE)); + } + + @Test + public void parseExamDate_validValueWithWhitespace_returnsTrimmedExamDate() throws Exception { + String criteriaWithWhitespace = WHITESPACE + VALID_EXAM_DATE + WHITESPACE; + ExamDate expectedExamDate = new ExamDate(VALID_EXAM_DATE); + assertEquals(expectedExamDate, ParserUtil.parseExamDate(criteriaWithWhitespace)); + } + + @Test + public void parseExamDate_validDateWithWhitespace_returnsTrimmedDate() throws Exception { + String examDateWithWhitespace = WHITESPACE + VALID_DATE_EXAMONE + WHITESPACE; + ExamDate examDate = new ExamDate(VALID_DATE_EXAMONE); + assertEquals(examDate, ParserUtil.parseExamDate(examDateWithWhitespace)); + } + + @Test + public void parseExamDesc_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseExamDescription((String) null)); + } + + @Test + public void parseExamDesc_invalidDesc_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseExamDescription("")); + } + + @Test + public void parseExamDesc_validDescWithoutWhitespace_returnsDesc() throws Exception { + ExamDescription examDescription = new ExamDescription(VALID_DESCRIPTION_EXAMONE); + assertEquals(examDescription, ParserUtil.parseExamDescription(VALID_DESCRIPTION_EXAMONE)); + } + + @Test + public void parseExamDesc_validDescWithWhitespace_returnsTrimmedDesc() throws Exception { + String examDateWithWhitespace = WHITESPACE + VALID_DESCRIPTION_EXAMONE + WHITESPACE; + ExamDescription examDescription = new ExamDescription(VALID_DESCRIPTION_EXAMONE); + assertEquals(examDescription, ParserUtil.parseExamDescription(examDateWithWhitespace)); + } + } diff --git a/src/test/java/seedu/address/logic/parser/SortTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SortTaskCommandParserTest.java new file mode 100644 index 00000000000..8cd4d3c0bf1 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/SortTaskCommandParserTest.java @@ -0,0 +1,56 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.model.Criteria; +import seedu.address.testutil.CriteriaBuilder; + +public class SortTaskCommandParserTest { + private SortTaskCommandParser parser = new SortTaskCommandParser(); + + @Test + public void parse_criteriaFieldPresent_success() { + Criteria criteria = new CriteriaBuilder().withCriteria("priority").build(); + String criteriaFieldPresent = " c/priority"; + String criteriaFieldPresentAllCaps = " c/PRIORITY"; + + //valid input with priority as criteria + assertParseSuccess(parser, criteriaFieldPresent, new SortTaskCommand(criteria)); + + //valid input with priority in different casing as criteria + assertParseSuccess(parser, criteriaFieldPresentAllCaps, new SortTaskCommand(criteria)); + + //valid input with deadline as criteria + criteria = new CriteriaBuilder().withCriteria("description").build(); + criteriaFieldPresent = " c/description"; + assertParseSuccess(parser, criteriaFieldPresent, new SortTaskCommand(criteria)); + } + + @Test + public void parse_missingCriteriaField_failure() { + String missingField = " priority"; + String missingWhitespaceAtStart = "c/priority"; + String expectedMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + SortTaskCommand.MESSAGE_USAGE); + + //Missing prefix + assertParseFailure(parser, missingField, expectedMessage); + + //Missing whitespace before prefix + assertParseFailure(parser, missingWhitespaceAtStart, expectedMessage); + } + + @Test + public void parse_invalidInputs_failure() { + String invalidCriteria = " c/module_name"; + + //Invalid criteria name + assertParseFailure(parser, invalidCriteria, Criteria.CRITERIA_CONSTRAINTS); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/UnlinkExamCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnlinkExamCommandParserTest.java new file mode 100644 index 00000000000..cafbf857868 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/UnlinkExamCommandParserTest.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.UnlinkExamCommand; + +/** + * Contains unit tests for {@code UnmarkCommandParser}. + */ +public class UnlinkExamCommandParserTest { + private UnlinkExamCommandParser parser = new UnlinkExamCommandParser(); + + @Test + public void parse_validArgs_returnsUnlinkExamCommand() { + assertParseSuccess(parser, "1", new UnlinkExamCommand(INDEX_FIRST_TASK)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // empty string + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UnlinkExamCommand.MESSAGE_USAGE)); + + // wrong type + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UnlinkExamCommand.MESSAGE_USAGE)); + + // big integer + assertParseFailure(parser, "2147483648", MESSAGE_INVALID_TASK_INDEX); + + // negative integer + assertParseFailure(parser, "-1", MESSAGE_INVALID_TASK_INDEX); + } +} diff --git a/src/test/java/seedu/address/logic/parser/UnmarkCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnmarkCommandParserTest.java new file mode 100644 index 00000000000..ca2de68f695 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/UnmarkCommandParserTest.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.UnmarkCommand; + +/** + * Contains unit tests for {@code UnmarkCommandParser}. + */ +public class UnmarkCommandParserTest { + + private UnmarkCommandParser parser = new UnmarkCommandParser(); + + @Test + public void parse_validArgs_returnsUnmarkCommand() { + assertParseSuccess(parser, "1", new UnmarkCommand(INDEX_FIRST_TASK)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // empty + assertParseFailure(parser, " ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + + // non-number + assertParseFailure(parser, "a b", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + + // zero index + assertParseFailure(parser, "0", MESSAGE_INVALID_TASK_INDEX); + + // big integer + assertParseFailure(parser, "2147483648", MESSAGE_INVALID_TASK_INDEX); + + // negative integer + assertParseFailure(parser, "-21", MESSAGE_INVALID_TASK_INDEX); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 87782528ecd..f0f684bb48c 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -3,32 +3,53 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMTWO; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalExams.FINAL_EXAM; +import static seedu.address.testutil.TypicalExams.MIDTERM_EXAM; +import static seedu.address.testutil.TypicalExams.getTypicalExams; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalModules.getTypicalModules; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_B; +import static seedu.address.testutil.TypicalTasks.TASK_C; +import static seedu.address.testutil.TypicalTasks.TASK_D; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; +import static seedu.address.testutil.TypicalTasks.getTypicalTasks; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.testutil.PersonBuilder; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.module.Module; +import seedu.address.model.module.exceptions.DuplicateModuleException; +import seedu.address.model.task.Task; +import seedu.address.testutil.ExamBuilder; +import seedu.address.testutil.ModuleBuilder; +import seedu.address.testutil.TaskBuilder; public class AddressBookTest { - private final AddressBook addressBook = new AddressBook(); + //@@author tlx02 @Test public void constructor() { - assertEquals(Collections.emptyList(), addressBook.getPersonList()); + assertEquals(Collections.emptyList(), addressBook.getTaskList()); + assertEquals(Collections.emptyList(), addressBook.getModuleList()); + assertEquals(Collections.emptyList(), addressBook.getExamList()); + } + //@@author + + @Test + public void constructor_withValidReadOnlyAddressBook_createsAddressBookWithData() { + AddressBook newData = getTypicalAddressBook(); + assertEquals(newData, new AddressBook(newData)); } @Test @@ -43,60 +64,505 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() { assertEquals(newData, addressBook); } + + //// module-level operation + + @Test + public void addModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.addModule(null)); + } + + @Test + public void addModule_moduleAlreadyExist_throwsDuplicateModuleException() { + addressBook.addModule(CS2030); + Module copy = new ModuleBuilder(CS2030).build(); + assertThrows(DuplicateModuleException.class, () -> addressBook.addModule(copy)); + } + + @Test + public void addModule_success() { + addressBook.addModule(CS2030); + assertEquals(CS2030, addressBook.getModuleList().get(0)); + } + + @Test + public void hasModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.hasModule(null)); + } + + @Test + public void hasModule_moduleNotInAddressBook_returnsFalse() { + assertFalse(addressBook.hasModule(CS2030)); + } + + @Test + public void hasModule_moduleInAddressBook_returnsTrue() { + addressBook.addModule(CS2030); + assertTrue(addressBook.hasModule(CS2030)); + } + + @Test + public void hasModule_moduleWithSameModuleCodeInAddressBook_returnsTrue() { + addressBook.addModule(CS2030); + Module editedModule = new ModuleBuilder(CS2030).withModuleName("edited name").build(); + assertTrue(addressBook.hasModule(editedModule)); + } + + @Test + public void removeModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.removeModule(null)); + } + + @Test + public void removeModule_success() { + addressBook.addModule(CS2030); + assertEquals(1, addressBook.getModuleList().size()); + addressBook.removeModule(new ModuleBuilder().withModuleCode("cs2030").build()); + assertEquals(0, addressBook.getModuleList().size()); + } + + @Test + public void replaceModule_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.replaceModule(null, CS2030)); + assertThrows(NullPointerException.class, () -> addressBook.replaceModule(CS2030, null)); + } + + @Test + public void replaceModule_success() { + addressBook.addModule(CS2030); + addressBook.replaceModule(CS2030, CS2040); + assertEquals(CS2040, addressBook.getModuleList().get(0)); + } + + //// exam-level operations + + @Test + public void addExam_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.addExam(null)); + } + + @Test + public void addExam_examAlreadyExist_throwsDuplicateExamException() { + addressBook.addExam(MIDTERM_EXAM); + Exam copy = new ExamBuilder(MIDTERM_EXAM).build(); + assertThrows(DuplicateExamException.class, () -> addressBook.addExam(copy)); + } + + @Test + public void addExam_success() { + addressBook.addExam(MIDTERM_EXAM); + assertEquals(MIDTERM_EXAM, addressBook.getExamList().get(0)); + } + @Test + public void hasExam_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.hasExam(null)); + } + + @Test + public void hasExam_examNotInAddressBook_returnsFalse() { + assertFalse(addressBook.hasExam(MIDTERM_EXAM)); + } + + @Test + public void hasExam_examInAddressBook_returnsTrue() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + assertTrue(addressBook.hasExam(MIDTERM_EXAM)); + } + + @Test + public void hasExamWithModule_noExamsWithModule_returnsFalse() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + assertFalse(addressBook.hasExamWithModule(CS2030)); + } + + @Test + public void hasExamWithModule_hasExamWithModule_returnsFalse() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + assertTrue(addressBook.hasExamWithModule(CS2040)); + } + + @Test + public void removeExam_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.removeExam(null)); + } + + @Test + public void removeExam_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + assertEquals(1, addressBook.getExamList().size()); + addressBook.removeExam(new ExamBuilder(MIDTERM_EXAM).build()); + assertEquals(0, addressBook.getExamList().size()); + } + + @Test + public void replaceExam_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + addressBook.replaceExam(null, MIDTERM_EXAM, false)); + assertThrows(NullPointerException.class, () -> + addressBook.replaceExam(MIDTERM_EXAM, null, false)); + } + + @Test + public void replaceExam_success() { + addressBook.addModule(CS2040); + addressBook.addExam(FINAL_EXAM); + + Exam editedExam = new ExamBuilder(FINAL_EXAM).withDescription(VALID_DESCRIPTION_EXAMTWO).build(); + addressBook.replaceExam(FINAL_EXAM, editedExam, false); + assertEquals(editedExam, addressBook.getExamList().get(0)); + } + + //// task-level operations + + @Test + public void hasTask_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.hasTask(null)); + } + + @Test + public void hasTask_taskNotInAddressBook_returnsFalse() { + assertFalse(addressBook.hasTask(TASK_A)); + } + + @Test + public void hasTask_taskInAddressBook_returnsTrue() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_A); + assertTrue(addressBook.hasTask(TASK_A)); + } + + @Test + public void hasTask_taskWithSameIdentityFieldsInAddressBook_returnsTrue() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_A); + Task editedTask = new TaskBuilder(TASK_A).withStatus("complete").build(); + assertTrue(addressBook.hasTask(editedTask)); + } + @Test - public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { - // Two persons with the same identity fields - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - List newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons); + public void hasTaskWithModule_noTasksWithModule_returnsFalse() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_A); + assertFalse(addressBook.hasTaskWithModule(CS2040)); + } - assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); + @Test + public void hasTaskWithModule_hasTasksWithModule_returnsFalse() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_A); + assertTrue(addressBook.hasTaskWithModule(CS2030)); } @Test - public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null)); + public void addTask_completedTask_success() { + Task completedTask = new TaskBuilder(TASK_A).withStatus("complete").build(); + addressBook.addModule(CS2030); + addressBook.addTask(completedTask); + + assertEquals(1, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertEquals(1, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + + List expectedTaskList = Collections.singletonList(completedTask); + assertEquals(expectedTaskList, addressBook.getTaskList()); } @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(addressBook.hasPerson(ALICE)); + public void addTask_linkedCompletedTask_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_D); + + assertEquals(1, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertEquals(1, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(1, addressBook.getExamList().get(0).getTotalNumOfTasks()); + assertEquals(1, addressBook.getExamList().get(0).getNumOfCompletedTasks()); + + List expectedTaskList = Collections.singletonList(TASK_D); + assertEquals(expectedTaskList, addressBook.getTaskList()); } @Test - public void hasPerson_personInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - assertTrue(addressBook.hasPerson(ALICE)); + public void replaceTask_null_throwsNullPointerException() { + // null target task + assertThrows(NullPointerException.class, () -> addressBook.replaceTask(null, TASK_A, false)); + + // null edited task + assertThrows(NullPointerException.class, () -> + addressBook.replaceTask(TASK_A, null, false)); } @Test - public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(addressBook.hasPerson(editedAlice)); + public void replaceTask_unlinkedTask() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_A); + int initialModuleCompletedCount = addressBook.getModuleList().get(0).getNumOfCompletedTasks(); + int initialModuleTotalCount = addressBook.getModuleList().get(0).getTotalNumOfTasks(); + + Task completedTaskA = new TaskBuilder(TASK_A).withStatus("complete").build(); + addressBook.replaceTask(TASK_A, completedTaskA, true); + assertEquals(initialModuleCompletedCount + 1, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(initialModuleTotalCount, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertTrue(addressBook.getTaskList().get(0).hasAllSameFields(completedTaskA)); } @Test - public void getPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); + public void replaceTask_taskUnlinkedFromExam() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_D); + + int initialModuleCompletedCount = addressBook.getModuleList().get(0).getNumOfCompletedTasks(); + int initialModuleTotalCount = addressBook.getModuleList().get(0).getTotalNumOfTasks(); + int initialExamCompletedCount = addressBook.getExamList().get(0).getNumOfCompletedTasks(); + int initialExamTotalCount = addressBook.getExamList().get(0).getTotalNumOfTasks(); + + Task editedTask = new TaskBuilder(TASK_D).withExam(null).build(); + addressBook.replaceTask(TASK_D, editedTask, true); + assertEquals(initialModuleCompletedCount, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(initialModuleTotalCount, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertEquals(initialExamCompletedCount - 1, addressBook.getExamList().get(0).getNumOfCompletedTasks()); + assertEquals(initialExamTotalCount - 1, addressBook.getExamList().get(0).getTotalNumOfTasks()); + assertTrue(addressBook.getTaskList().get(0).hasAllSameFields(editedTask)); } - /** - * A stub ReadOnlyAddressBook whose persons list can violate interface constraints. - */ - private static class AddressBookStub implements ReadOnlyAddressBook { - private final ObservableList persons = FXCollections.observableArrayList(); + @Test + public void replaceTask_tasklinkedToExam() { + addressBook.addModule(CS2030); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_A); + int initialModuleCompletedCount = addressBook.getModuleList().get(0).getNumOfCompletedTasks(); + int initialModuleTotalCount = addressBook.getModuleList().get(0).getTotalNumOfTasks(); + int initialExamCompletedCount = addressBook.getExamList().get(0).getNumOfCompletedTasks(); + int initialExamTotalCount = addressBook.getExamList().get(0).getTotalNumOfTasks(); + + Task editedTask = new TaskBuilder(TASK_A).withExam(MIDTERM_EXAM).build(); + addressBook.replaceTask(TASK_A, editedTask, true); + assertEquals(initialModuleCompletedCount, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(initialModuleTotalCount, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertEquals(initialExamCompletedCount, addressBook.getExamList().get(0).getNumOfCompletedTasks()); + assertEquals(initialExamTotalCount + 1, addressBook.getExamList().get(0).getTotalNumOfTasks()); + assertTrue(addressBook.getTaskList().get(0).hasAllSameFields(editedTask)); + } + + @Test + public void removeTask_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.removeTask(null)); + } + + @Test + public void removeTask_linkedTask_success() { + AddressBook ab = getTypicalAddressBook(); + int initialModuleCompletedCount = ab.getModuleList().get(1).getNumOfCompletedTasks(); + int initialModuleTotalCount = ab.getModuleList().get(1).getTotalNumOfTasks(); + int initialExamCompletedCount = ab.getExamList().get(0).getNumOfCompletedTasks(); + int initialExamTotalCount = ab.getExamList().get(0).getTotalNumOfTasks(); + + ab.removeTask(TASK_D); + assertEquals(initialModuleCompletedCount - 1, ab.getModuleList().get(1).getNumOfCompletedTasks()); + assertEquals(initialModuleTotalCount - 1, ab.getModuleList().get(1).getTotalNumOfTasks()); + assertEquals(initialExamCompletedCount - 1, ab.getExamList().get(0).getNumOfCompletedTasks()); + assertEquals(initialExamTotalCount - 1, ab.getExamList().get(0).getTotalNumOfTasks()); + } + + @Test + public void removeTask_unlinkedTask_success() { + AddressBook ab = getTypicalAddressBook(); + int initialModuleCompletedCount = ab.getModuleList().get(0).getNumOfCompletedTasks(); + int initialModuleTotalCount = ab.getModuleList().get(0).getTotalNumOfTasks(); - AddressBookStub(Collection persons) { - this.persons.setAll(persons); - } + ab.removeTask(TASK_A); + assertEquals(initialModuleCompletedCount, ab.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(initialModuleTotalCount - 1, ab.getModuleList().get(0).getTotalNumOfTasks()); + } + + + //// util methods + + @Test + public void resetAllTaskCount() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_D); + assertEquals(1, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(1, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertEquals(1, addressBook.getExamList().get(0).getNumOfCompletedTasks()); + assertEquals(1, addressBook.getExamList().get(0).getTotalNumOfTasks()); + + addressBook.resetAllTaskCount(); + assertEquals(0, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(0, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + assertEquals(0, addressBook.getExamList().get(0).getNumOfCompletedTasks()); + assertEquals(0, addressBook.getExamList().get(0).getTotalNumOfTasks()); + } + + @Test + public void sortTaskList_nullCriteria_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.sortTaskList(null)); + } + + @Test + public void sortTaskList_success() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_B); + addressBook.addTask(TASK_A); + addressBook.sortTaskList(new Criteria("description")); + List expectedList = new ArrayList<>(Arrays.asList(TASK_A, TASK_B)); + assertEquals(expectedList, addressBook.getTaskList()); + } + + @Test + public void unlinkTasksFromExam_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.unlinkTasksFromExam(null)); + } + + @Test + public void unlinkTasksFromExam_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_D); + Task anotherLinkedTask = new TaskBuilder(TASK_C).withExam(MIDTERM_EXAM).build(); + addressBook.addTask(anotherLinkedTask); + addressBook.unlinkTasksFromExam(MIDTERM_EXAM); + addressBook.getTaskList().forEach(task -> assertFalse(task.isLinked())); + } + + @Test + public void updateExamFieldForTask_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.updateExamFieldForTask(null, FINAL_EXAM)); + assertThrows(NullPointerException.class, () -> addressBook.updateExamFieldForTask(FINAL_EXAM, null)); + } - @Override - public ObservableList getPersonList() { - return persons; - } + @Test + public void updateExamFieldForTask_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_D); + Exam editedExam = new ExamBuilder(MIDTERM_EXAM).withDescription("edited exam").build(); + addressBook.updateExamFieldForTask(MIDTERM_EXAM, editedExam); + assertEquals(editedExam, addressBook.getTaskList().get(0).getExam()); + } + + @Test + public void isExamLinkedToTask_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.isExamLinkedToTask(null)); + } + + @Test + public void isExamLinkedToTask_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + assertFalse(addressBook.isExamLinkedToTask(MIDTERM_EXAM)); + addressBook.addTask(TASK_D); + assertTrue(addressBook.isExamLinkedToTask(MIDTERM_EXAM)); + } + + @Test + public void updateModuleFieldForTask_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.updateModuleFieldForTask(null, CS2030)); + assertThrows(NullPointerException.class, () -> addressBook.updateModuleFieldForTask(CS2030, null)); + } + + @Test + public void updateModuleFieldForTask_success() { + addressBook.addModule(CS2030); + addressBook.addTask(TASK_A); + + addressBook.setModules(Collections.singletonList(CS2040)); + addressBook.updateModuleFieldForTask(CS2030, CS2040); + assertEquals(CS2040, addressBook.getTaskList().get(0).getModule()); + assertEquals(0, addressBook.getModuleList().get(0).getNumOfCompletedTasks()); + assertEquals(1, addressBook.getModuleList().get(0).getTotalNumOfTasks()); + } + + @Test + public void updateModuleFieldForExam_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.updateModuleFieldForExam(null, CS2030)); + assertThrows(NullPointerException.class, () -> addressBook.updateModuleFieldForExam(CS2030, null)); + } + + @Test + public void updateModuleFieldForExam_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.addTask(TASK_D); + + addressBook.setModules(Collections.singletonList(CS2030)); + addressBook.updateModuleFieldForExam(CS2040, CS2030); + assertEquals(CS2030, addressBook.getExamList().get(0).getModule()); + assertEquals(addressBook.getExamList().get(0), addressBook.getTaskList().get(0).getExam()); + + assertEquals(1, addressBook.getExamList().get(0).getNumOfCompletedTasks()); + assertEquals(1, addressBook.getExamList().get(0).getTotalNumOfTasks()); + } + + @Test + public void isEqual() { + AddressBook ab = getTypicalAddressBook(); + + // same object -> returns true + assertTrue(ab.equals(ab)); + + // same fields -> return true + AddressBook abCopy = new AddressBook(); + abCopy.setModules(getTypicalModules()); + abCopy.setExams(getTypicalExams()); + abCopy.setTasks(getTypicalTasks()); + assertTrue(ab.equals(abCopy)); + + // different types -> returns false + assertFalse(ab.equals(5)); + + // null -> returns false + assertFalse(ab.equals(null)); + + // different task list -> returns false + AddressBook abWithDifferentTaskList = new AddressBook(); + abWithDifferentTaskList.setTasks(Collections.singletonList(TASK_D)); + assertFalse(ab.equals(abWithDifferentTaskList)); + + // different module list-> returns false + AddressBook abWithDifferentModuleList = new AddressBook(); + abWithDifferentModuleList.setModules(Collections.singletonList(CS2030)); + assertFalse(ab.equals(abWithDifferentModuleList)); + + // different exam list -> returns false + AddressBook abWithDifferentExamList = new AddressBook(); + abWithDifferentExamList.setExams(Collections.singletonList(MIDTERM_EXAM)); + assertFalse(ab.equals(abWithDifferentExamList)); + } + + @Test + public void deleteTasksWithModule_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.deleteTasksWithModule(null)); + } + + @Test + public void deleteTasksWithModule_success() { + addressBook.addModule(CS2030); + addressBook.addModule(CS2040); + addressBook.addTask(TASK_B); + addressBook.addTask(TASK_C); + addressBook.deleteTasksWithModule(CS2040); + List expectedList = Collections.singletonList(TASK_B); + assertEquals(expectedList, addressBook.getTaskList()); + } + + @Test + public void deleteExamsWithModule_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.deleteExamsWithModule(null)); + } + + @Test + public void deleteExamsWithModule_success() { + addressBook.addModule(CS2040); + addressBook.addExam(MIDTERM_EXAM); + addressBook.deleteExamsWithModule(CS2040); + List expectedList = new ArrayList<>(); + assertEquals(expectedList, addressBook.getExamList()); } } diff --git a/src/test/java/seedu/address/model/CriteriaTest.java b/src/test/java/seedu/address/model/CriteriaTest.java new file mode 100644 index 00000000000..4132fe94300 --- /dev/null +++ b/src/test/java/seedu/address/model/CriteriaTest.java @@ -0,0 +1,78 @@ +package seedu.address.model; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.CriteriaBuilder; + +/** + * Unit testing for Criteria. + */ +public class CriteriaTest { + + @Test + public void constructor_nullCriteria_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Criteria(null)); + } + + @Test + public void constructor_invalidCriteria_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new Criteria("status")); + } + + @Test + public void isValidCriteria_nullCriteria_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> Criteria.isValidCriteria(null)); + } + + @Test + public void isValidCriteria_invalidCriteria_returnsFalse() { + //Invalid word selected for criteira + assertFalse(Criteria.isValidCriteria("status")); + + //Criteria word with trailing whitespace + assertFalse(Criteria.isValidCriteria("priority ")); + } + + @Test + public void isValidCriteria_validCriteria_returnsTrue() { + //Valid criteria (priority) + assertTrue(Criteria.isValidCriteria("priority")); + + //Valid criteria with different casing + assertTrue(Criteria.isValidCriteria("PrIoRiTy")); + + //Valid criteria (deadline) + assertTrue(Criteria.isValidCriteria("deadline")); + + //Valid Criteria (module) + assertTrue(Criteria.isValidCriteria("module")); + + //Valid Criteria (description) + assertTrue(Criteria.isValidCriteria("description")); + } + + @Test + public void testEquals() { + Criteria criteria = new CriteriaBuilder().build(); + //Same object + assertTrue(criteria.equals(criteria)); + + Criteria sameCriteria = new CriteriaBuilder(criteria).build(); + //Different object but same criteria string + assertTrue(criteria.equals(sameCriteria)); + + Criteria differentCriteria = new CriteriaBuilder().withCriteria("deadline").build(); + //Different objects with different criterias + assertFalse(criteria.equals(differentCriteria)); + + //Different object type + assertFalse(criteria.equals(121212)); + + //null value returns false + assertFalse(criteria.equals(null)); + } +} diff --git a/src/test/java/seedu/address/model/DistinctTaskListTest.java b/src/test/java/seedu/address/model/DistinctTaskListTest.java new file mode 100644 index 00000000000..de4b48793a9 --- /dev/null +++ b/src/test/java/seedu/address/model/DistinctTaskListTest.java @@ -0,0 +1,123 @@ +package seedu.address.model; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalExams.EXAMONE; +import static seedu.address.testutil.TypicalExams.EXAMTWO; +//import static seedu.address.testutil.TypicalTasks.TASKONE; +//import static seedu.address.testutil.TypicalTasks.TASKTWO; +import static seedu.address.testutil.TypicalTasks.TASK_J; +import static seedu.address.testutil.TypicalTasks.TASK_K; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.task.DistinctTaskList; +import seedu.address.model.task.Task; +import seedu.address.testutil.ExamBuilder; + + +public class DistinctTaskListTest { + private final DistinctTaskList distinctTaskList = new DistinctTaskList(); + + @Test + public void isExamLinkedToTask_returnsTrue() { + distinctTaskList.addTask(TASK_J); + Task editedTask = TASK_J.linkTask(EXAMONE); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + assertTrue(distinctTaskList.isExamLinkedToTask(EXAMONE)); + } + + @Test + public void isExamLinkedToTaskForNoLinksAtAll_returnsFalse() { + assertFalse(distinctTaskList.isExamLinkedToTask(EXAMONE)); + } + @Test + public void isExamLinkedToTask_returnsFalse() { + distinctTaskList.addTask(TASK_J); + Task editedTask = TASK_J.linkTask(EXAMTWO); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + assertFalse(distinctTaskList.isExamLinkedToTask(EXAMONE)); + } + + @Test + public void isExamLinkedToTaskWithMultipleLinks_returnsTrue() { + distinctTaskList.addTask(TASK_J); + distinctTaskList.addTask(TASK_K); + Task editedTask = TASK_J.linkTask(EXAMTWO); + Task editedTask2 = TASK_K.linkTask(EXAMTWO); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + distinctTaskList.replaceTask(TASK_K, editedTask2, true); + assertTrue(distinctTaskList.isExamLinkedToTask(EXAMTWO)); + } + + @Test + public void isExamLinkedToTask_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.isExamLinkedToTask(null)); + } + + + @Test + public void updateExamFieldForTask_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.updateExamFieldForTask(null, null)); + } + + @Test + public void updateExamFieldForTaskWhereAllTasksLinkedToExam_success() { + distinctTaskList.addTask(TASK_J); + distinctTaskList.addTask(TASK_K); + Task editedTask = TASK_J.linkTask(EXAMTWO); + Task editedTask2 = TASK_K.linkTask(EXAMTWO); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + distinctTaskList.replaceTask(TASK_K, editedTask2, true); + distinctTaskList.updateExamFieldForTask(EXAMTWO, EXAMONE); + assertTrue(distinctTaskList.taskList.get(0).getExam().equals(EXAMONE)); + assertTrue(distinctTaskList.taskList.get(1).getExam().equals(EXAMONE)); + } + + @Test + public void updateExamFieldForTaskWhereSomeTasksLinkedToExam_success() { + distinctTaskList.addTask(TASK_J); + distinctTaskList.addTask(TASK_K); + Task editedTask = TASK_J.linkTask(EXAMTWO); + Task editedTask2 = TASK_K.linkTask(new ExamBuilder().build()); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + distinctTaskList.replaceTask(TASK_K, editedTask2, true); + distinctTaskList.updateExamFieldForTask(EXAMTWO, EXAMONE); + assertTrue(distinctTaskList.taskList.get(0).getExam().equals(EXAMONE)); + assertFalse(distinctTaskList.taskList.get(1).getExam().equals(EXAMONE)); + } + + @Test + public void updateExamFieldForTaskWhereSomeTasksLinkedToExam2_success() { + distinctTaskList.addTask(TASK_J); + distinctTaskList.addTask(TASK_K); + Task editedTask = TASK_J.linkTask(EXAMTWO); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + distinctTaskList.updateExamFieldForTask(EXAMTWO, EXAMONE); + assertTrue(distinctTaskList.taskList.get(0).getExam().equals(EXAMONE)); + assertFalse(distinctTaskList.taskList.get(1).isLinked()); + } + @Test + public void updateExamFieldForTaskWhereNoTasksLinkedToExam() { + distinctTaskList.addTask(TASK_J); + distinctTaskList.addTask(TASK_K); + Task editedTask = TASK_J.linkTask(new ExamBuilder().build()); + Task editedTask2 = TASK_K.linkTask(new ExamBuilder().build()); + distinctTaskList.replaceTask(TASK_J, editedTask, true); + distinctTaskList.replaceTask(TASK_K, editedTask2, true); + distinctTaskList.updateExamFieldForTask(EXAMTWO, EXAMONE); + assertFalse(distinctTaskList.taskList.get(0).getExam().equals(EXAMONE)); + assertFalse(distinctTaskList.taskList.get(1).getExam().equals(EXAMONE)); + } + + @Test + public void updateExamFieldForTaskWhereNoLinks() { + distinctTaskList.addTask(TASK_J); + distinctTaskList.addTask(TASK_K); + distinctTaskList.updateExamFieldForTask(EXAMTWO, EXAMONE); + assertFalse(distinctTaskList.taskList.get(0).isLinked()); + assertFalse(distinctTaskList.taskList.get(1).isLinked()); + } + +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..0dac55b566e 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -3,10 +3,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalExams.EXAMONE; +import static seedu.address.testutil.TypicalExams.EXAMTHREE; +import static seedu.address.testutil.TypicalExams.FINAL_EXAM; +import static seedu.address.testutil.TypicalExams.MIDTERM_EXAM; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2030S; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_B; +import static seedu.address.testutil.TypicalTasks.TASK_C; +import static seedu.address.testutil.TypicalTasks.TASK_D; +import static seedu.address.testutil.TypicalTasks.TASK_Q; import java.nio.file.Path; import java.nio.file.Paths; @@ -15,7 +24,8 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate; +import seedu.address.model.task.DescriptionContainsKeywordsPredicate; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { @@ -73,29 +83,469 @@ public void setAddressBookFilePath_validPath_setsAddressBookFilePath() { } @Test - public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> modelManager.hasPerson(null)); + public void hasTask_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasTask(null)); } @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(modelManager.hasPerson(ALICE)); + public void hasTask_taskPresent_returnsTrue() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_A); + assertTrue(modelManager.hasTask(TASK_A)); } @Test - public void hasPerson_personInAddressBook_returnsTrue() { - modelManager.addPerson(ALICE); - assertTrue(modelManager.hasPerson(ALICE)); + public void hasTask_taskNotPresent_returnsFalse() { + assertFalse(modelManager.hasTask(TASK_A)); } @Test - public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); + public void hasTaskWithModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasTaskWithModule(null)); + } + + @Test + public void hasTaskWithModule_taskPresent_returnsTrue() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_A); + assertTrue(modelManager.hasTaskWithModule(CS2030)); + } + + @Test + public void hasTaskWithModule_taskNotPresent_returnsFalse() { + modelManager.addModule(CS2030); + modelManager.addModule(CS2040); + modelManager.addTask(TASK_C); + assertFalse(modelManager.hasTaskWithModule(CS2030)); + } + + @Test + public void addTask_unfilteredList_success() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_A); + assertTrue(modelManager.hasTask(TASK_A)); + assertTrue(modelManager.getFilteredTaskList().contains(TASK_A)); + } + + @Test + public void addTask_filteredList_success() { + modelManager.addModule(CS2030); + + // set filtered list which does not include added task + modelManager.updateFilteredTaskList(prepareTaskPredicate(TASK_B.getDescription().description)); + + modelManager.addTask(TASK_A); + assertTrue(modelManager.hasTask(TASK_A)); + + // filtered list should contain added task + assertTrue(modelManager.getFilteredTaskList().contains(TASK_A)); + } + + @Test + public void replaceTask_nullFields_throwsNullPointerException() { + // null target + assertThrows(NullPointerException.class, () -> modelManager.replaceTask(null, TASK_A, true)); + + // null editedTask + assertThrows(NullPointerException.class, () -> modelManager.replaceTask(TASK_A, null, true)); + } + + @Test + public void replaceTask_validFields_success() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_A); + + // check that modelManager contains TASK_A + assertTrue(modelManager.hasTask(TASK_A)); + + modelManager.replaceTask(TASK_A, TASK_B, false); + assertFalse(modelManager.hasTask(TASK_A)); + assertTrue(modelManager.hasTask(TASK_B)); + } + + @Test + public void deleteTask_taskPresent_success() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_A); + + // check that modelManager contains TASK_A + assertTrue(modelManager.hasTask(TASK_A)); + + modelManager.deleteTask(TASK_A); + assertFalse(modelManager.hasTask(TASK_A)); + } + + @Test + public void hasExam_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasExam(null)); + } + @Test + public void hasExam_examPresent_returnsTrue() { + modelManager.addExam(EXAMONE); + assertTrue(modelManager.hasExam(EXAMONE)); + } + + @Test + public void hasExam_examNotPresent_returnsFalse() { + assertFalse(modelManager.hasExam(EXAMONE)); + } + + @Test + public void hasExamWithModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasExamWithModule(null)); + } + + @Test + public void hasExamWithModule_examPresent_returnsTrue() { + modelManager.addExam(FINAL_EXAM); + assertTrue(modelManager.hasExamWithModule(CS2030)); + } + + @Test + public void hasExamWithModule_examNotPresent_returnsFalse() { + modelManager.addExam(MIDTERM_EXAM); + assertFalse(modelManager.hasExamWithModule(CS2030)); + } + + @Test + public void addExam_unfilteredList_success() { + modelManager.addExam(MIDTERM_EXAM); + assertTrue(modelManager.hasExam(MIDTERM_EXAM)); + } + + @Test + public void replaceExam_nullFields_throwsNullPointerException() { + // null target + assertThrows(NullPointerException.class, () -> modelManager.replaceExam(null, MIDTERM_EXAM, + true)); + + // null editedExam + assertThrows(NullPointerException.class, () -> modelManager.replaceExam(MIDTERM_EXAM, null, + true)); + } + + @Test + public void replaceExam_validFields_success() { + modelManager.addExam(MIDTERM_EXAM); + + // check that modelManager contains MIDTERM_EXAM + assertTrue(modelManager.hasExam(MIDTERM_EXAM)); + + modelManager.replaceExam(MIDTERM_EXAM, FINAL_EXAM, false); + assertFalse(modelManager.hasExam(MIDTERM_EXAM)); + assertTrue(modelManager.hasExam(FINAL_EXAM)); + } + + @Test + public void deleteExam_examPresent_success() { + modelManager.addExam(MIDTERM_EXAM); + + // check that modelManager contains TASK_A + assertTrue(modelManager.hasExam(MIDTERM_EXAM)); + + modelManager.deleteExam(MIDTERM_EXAM); + assertFalse(modelManager.hasExam(MIDTERM_EXAM)); + } + + @Test + public void addModule_unfilteredList_success() { + modelManager.addModule(CS2030); + assertTrue(modelManager.hasModule(CS2030)); + assertTrue(modelManager.getFilteredModuleList().contains(CS2030)); + } + + @Test + public void addModule_filteredList_success() { + // set filtered list which does not include added module + modelManager.updateFilteredModuleList(prepareModulePredicate(CS2040.getModuleCode().moduleCode)); + + modelManager.addModule(CS2030); + assertTrue(modelManager.hasModule(CS2030)); + + // filtered list should contain added task + assertTrue(modelManager.getFilteredModuleList().contains(CS2030)); + } + + @Test + public void hasModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasModule(null)); + } + @Test + public void hasModule_modulePresent_returnsTrue() { + modelManager.addModule(CS2030); + assertTrue(modelManager.hasModule(CS2030)); + } + + @Test + public void hasModule_moduleNotPresent_returnsFalse() { + assertFalse(modelManager.hasModule(CS2030)); + } + + @Test + public void getFilteredModuleList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredModuleList().remove(0)); + } + + @Test + public void updateFilteredModuleList_nullPredicate_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.updateFilteredModuleList(null)); + } + + @Test + public void updateFilteredModuleList_validPredicate_success() { + modelManager.addModule(CS2030); + + // check that original filtered module list contains CS2030 + assertTrue(modelManager.getFilteredModuleList().contains(CS2030)); + + modelManager.updateFilteredModuleList(prepareModulePredicate(CS2040.getModuleCode().moduleCode)); + assertFalse(modelManager.getFilteredModuleList().contains(CS2030)); + } + + @Test + public void deleteModule_modulePresent_success() { + modelManager.addModule(CS2030); + + // check that modelManager contains CS2030 + assertTrue(modelManager.hasModule(CS2030)); + + modelManager.deleteModule(CS2030); + assertFalse(modelManager.hasModule(CS2030)); + } + + @Test + public void replaceModule_nullFields_throwsNullPointerException() { + // null target + assertThrows(NullPointerException.class, () -> modelManager.replaceModule(null, CS2030)); + + // null editedModule + assertThrows(NullPointerException.class, () -> modelManager.replaceModule(CS2030, null)); + } + + @Test + public void replaceModule_validFields_success() { + modelManager.addModule(CS2030); + + // check that modelManager contains CS2030 + assertTrue(modelManager.hasModule(CS2030)); + + modelManager.replaceModule(CS2030, CS2040); + assertFalse(modelManager.hasModule(CS2030)); + assertTrue(modelManager.hasModule(CS2040)); + } + + @Test + public void getFilteredTaskList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredTaskList().remove(0)); + } + + @Test + public void updateFilteredTaskList_nullPredicate_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.updateFilteredTaskList(null)); + } + + @Test + public void updateFilteredTaskList_validPredicate_success() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_A); + + // check that original filtered task list contains TASK_A + assertTrue(modelManager.getFilteredTaskList().contains(TASK_A)); + + modelManager.updateFilteredTaskList(prepareTaskPredicate(TASK_B.getDescription().description)); + assertFalse(modelManager.getFilteredTaskList().contains(TASK_A)); + } + + @Test + public void sortTaskList_nullCriteria_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.sortTaskList(null)); + } + + @Test + public void sortTaskList_validCriteria_success() { + modelManager.addModule(CS2030); + modelManager.addTask(TASK_B); + modelManager.addTask(TASK_A); + + // check initial order + assertTrue(modelManager.getFilteredTaskList().get(0).equals(TASK_B)); + assertTrue(modelManager.getFilteredTaskList().get(1).equals(TASK_A)); + + modelManager.sortTaskList(new Criteria("description")); + assertTrue(modelManager.getFilteredTaskList().get(0).equals(TASK_A)); + assertTrue(modelManager.getFilteredTaskList().get(1).equals(TASK_B)); + } + + @Test + public void unlinkTasksFromExam_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.unlinkTasksFromExam(null)); + } + + @Test + public void unlinkTasksFromExam_validExam_success() { + modelManager.addModule(CS2040); + modelManager.addModule(CS2030); + modelManager.addExam(MIDTERM_EXAM); + modelManager.addExam(FINAL_EXAM); + modelManager.addTask(TASK_D); + modelManager.addTask(TASK_Q); + + // check initial link status for both tasks + assertTrue(modelManager.getFilteredTaskList().get(0).isLinked()); + assertTrue(modelManager.getFilteredTaskList().get(1).isLinked()); + + modelManager.unlinkTasksFromExam(MIDTERM_EXAM); + + // check that TASK_D is now unlinked + assertFalse(modelManager.getFilteredTaskList().get(0).isLinked()); + + // check that TASK_Q is still linked + assertTrue(modelManager.getFilteredTaskList().get(1).isLinked()); + } + + @Test + public void updateExamFieldForTask_nullFields_throwsNullPointerException() { + // null previous exam + assertThrows(NullPointerException.class, () -> modelManager.updateExamFieldForTask(null, + MIDTERM_EXAM)); + + // null new exam + assertThrows(NullPointerException.class, () -> modelManager.updateExamFieldForTask(MIDTERM_EXAM, null)); + } + + @Test + public void updateExamFieldForTask_validFields_success() { + modelManager.addModule(CS2040); + modelManager.addExam(MIDTERM_EXAM); + modelManager.addTask(TASK_D); + + // check initial exam of TASK_D + assertTrue(modelManager.getFilteredTaskList().get(0).getExam().equals(MIDTERM_EXAM)); + + modelManager.updateExamFieldForTask(MIDTERM_EXAM, EXAMTHREE); + assertTrue(modelManager.getFilteredTaskList().get(0).getExam().equals(EXAMTHREE)); + } + + @Test + public void updateModuleFieldForTask_nullFields_throwsNullPointerException() { + // null previous module + assertThrows(NullPointerException.class, () -> modelManager.updateModuleFieldForTask(null, CS2030)); + + // null new module + assertThrows(NullPointerException.class, () -> modelManager.updateModuleFieldForTask(null, CS2030)); + } + + @Test + public void updateModuleFieldForTask_validFields_success() { + modelManager.addModule(CS2030); + modelManager.addModule(CS2030S); + modelManager.addTask(TASK_A); + + // check initial module of TASK_A + assertTrue(modelManager.getFilteredTaskList().get(0).getModule().equals(CS2030)); + + modelManager.updateModuleFieldForTask(CS2030, CS2030S); + assertTrue(modelManager.getFilteredTaskList().get(0).getModule().equals(CS2030S)); + } + + @Test + public void deleteTasksWithModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.deleteTasksWithModule(null)); + } + + @Test + public void deleteTasksWithModule_validModule_success() { + modelManager.addModule(CS2030); + modelManager.addModule(CS2040); + modelManager.addTask(TASK_A); + modelManager.addTask(TASK_C); + + // check that initial task list contains TASK_A and TASK_C + assertTrue(modelManager.hasTask(TASK_A)); + assertTrue(modelManager.hasTask(TASK_C)); + + modelManager.deleteTasksWithModule(CS2030); + assertFalse(modelManager.hasTask(TASK_A)); + assertTrue(modelManager.hasTask(TASK_C)); + } + + @Test + public void getFilteredExamList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredExamList().remove(0)); + } + + @Test + public void updateFilteredExamList_nullPredicate_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.updateFilteredExamList(null)); + } + + @Test + public void isExamLinkedToTask_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.isExamLinkedToTask(null)); + } + + @Test + public void isExamLinkedToTask_isLinked_returnsTrue() { + modelManager.addModule(CS2040); + modelManager.addExam(MIDTERM_EXAM); + modelManager.addTask(TASK_D); + assertTrue(modelManager.isExamLinkedToTask(MIDTERM_EXAM)); + } + + @Test + public void isExamLinkedToTask_isNotLinked_returnsFalse() { + modelManager.addModule(CS2040); + modelManager.addExam(MIDTERM_EXAM); + modelManager.addTask(TASK_D); + assertFalse(modelManager.isExamLinkedToTask(FINAL_EXAM)); + } + + @Test + public void updateModuleFieldForExam_nullFields_throwsNullPointerException() { + // null previous module + assertThrows(NullPointerException.class, () -> modelManager.updateModuleFieldForExam(null, CS2030)); + + // null new module + assertThrows(NullPointerException.class, () -> modelManager.updateModuleFieldForExam(CS2030, null)); + } + + @Test + public void updateModuleFieldForExam_validFields_success() { + modelManager.addModule(CS2030); + modelManager.addModule(CS2040); + modelManager.addExam(MIDTERM_EXAM); + + // check initial module of MIDTERM_EXAM + assertTrue(modelManager.getFilteredExamList().get(0).getModule().equals(CS2040)); + + modelManager.updateModuleFieldForExam(CS2040, CS2030); + assertTrue(modelManager.getFilteredExamList().get(0).getModule().equals(CS2030)); + } + + @Test + public void deleteExamsWithModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.deleteExamsWithModule(null)); + } + + @Test + public void deleteExamsWithModule_validModule_success() { + modelManager.addModule(CS2030); + modelManager.addModule(CS2040); + modelManager.addExam(MIDTERM_EXAM); + modelManager.addExam(FINAL_EXAM); + + // check that exam list contains MIDTERM_EXAM and FINAL_EXAM + assertTrue(modelManager.hasExam(MIDTERM_EXAM)); + assertTrue(modelManager.hasExam(FINAL_EXAM)); + + modelManager.deleteExamsWithModule(CS2030); + assertTrue(modelManager.hasExam(MIDTERM_EXAM)); + assertFalse(modelManager.hasExam(FINAL_EXAM)); } @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); + AddressBook addressBook = new AddressBookBuilder().withModule(CS2030).withTask(TASK_A).build(); AddressBook differentAddressBook = new AddressBook(); UserPrefs userPrefs = new UserPrefs(); @@ -115,18 +565,19 @@ public void equals() { // different addressBook -> returns false assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs))); + } - // different filteredList -> returns false - String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); - assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); - - // resets modelManager to initial state for upcoming tests - modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + /** + * Parses {@code userInput} into a {@code DescriptionContainsKeywordsPredicate}. + */ + private DescriptionContainsKeywordsPredicate prepareTaskPredicate(String userInput) { + return new DescriptionContainsKeywordsPredicate(Arrays.asList(userInput.trim().toLowerCase())); + } - // different userPrefs -> returns false - UserPrefs differentUserPrefs = new UserPrefs(); - differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath")); - assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); + /** + * Parses {@code userInput} into a {@code DescriptionContainsKeywordsPredicate}. + */ + private ModuleCodeContainsKeywordsPredicate prepareModulePredicate(String userInput) { + return new ModuleCodeContainsKeywordsPredicate(Arrays.asList(userInput.trim().toLowerCase())); } } diff --git a/src/test/java/seedu/address/model/exam/DistinctExamListTest.java b/src/test/java/seedu/address/model/exam/DistinctExamListTest.java new file mode 100644 index 00000000000..0ab8ef3307e --- /dev/null +++ b/src/test/java/seedu/address/model/exam/DistinctExamListTest.java @@ -0,0 +1,434 @@ +package seedu.address.model.exam; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMTWO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_EXAMTWO; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalExams.CS2040_FINAL; +import static seedu.address.testutil.TypicalExams.EXAMONE; +import static seedu.address.testutil.TypicalExams.EXAMTWO; +import static seedu.address.testutil.TypicalExams.FINAL_EXAM; +import static seedu.address.testutil.TypicalExams.MA1521_MIDTERM; +import static seedu.address.testutil.TypicalExams.MIDTERM_EXAM; +import static seedu.address.testutil.TypicalExams.getTypicalExams; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_D; +import static seedu.address.testutil.TypicalTasks.getTypicalTasks; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.exam.exceptions.DuplicateExamException; +import seedu.address.model.exam.exceptions.ExamIdentityModifiedException; +import seedu.address.model.exam.exceptions.ExamNotFoundException; +import seedu.address.model.module.Module; +import seedu.address.model.task.DistinctTaskList; +import seedu.address.model.task.Task; +import seedu.address.testutil.ExamBuilder; +import seedu.address.testutil.ModuleBuilder; +import seedu.address.testutil.TaskBuilder; +import seedu.address.testutil.TypicalExams; +import seedu.address.testutil.TypicalTasks; + +public class DistinctExamListTest { + + private final DistinctExamList distinctExamList = new DistinctExamList(); + + @Test + public void contains_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctExamList.contains(null)); + } + + @Test + public void contains_examNotInList_returnsFalse() { + assertFalse(distinctExamList.contains(EXAMONE)); + } + + @Test + public void contains_examInList_returnsTrue() { + distinctExamList.addExam(EXAMONE); + assertTrue(distinctExamList.contains(EXAMONE)); + } + + @Test + public void contains_examWithSameIdentityFieldsInList_returnsTrue() { + distinctExamList.addExam(EXAMONE); + Exam editedExam = new ExamBuilder(EXAMONE).withDate(VALID_DATE_EXAMTWO) + .withDescription(VALID_DESCRIPTION_EXAMTWO) + .withModule(VALID_MODULE_EXAMTWO).build(); + distinctExamList.replaceExam(distinctExamList.examList.get(0), editedExam, false); + assertTrue(distinctExamList.contains(editedExam)); + } + + @Test + public void containsModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctExamList.containsModule(null)); + } + + @Test + public void containsModule_moduleNotInList_returnsFalse() { + assertFalse(distinctExamList.containsModule(CS2030)); + } + + @Test + public void containsModule_moduleInList_returnsTrue() { + distinctExamList.addExam(EXAMONE); + Module actualModule = EXAMONE.getModule(); + assertTrue(distinctExamList.containsModule(actualModule)); + } + + @Test + public void containsModule_moduleWithSameIdentityFieldsInList_returnsTrue() { + distinctExamList.addExam(EXAMONE); + Module actualModule = new ModuleBuilder() + .withModuleCode(EXAMONE.getModule().getModuleCode().moduleCode).build(); + assertTrue(distinctExamList.containsModule(actualModule)); + } + + @Test + public void add_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctExamList.addExam(null)); + } + + @Test + public void add_duplicateExam_throwsDuplicateExamException() { + distinctExamList.addExam(EXAMONE); + assertThrows(DuplicateExamException.class, () -> distinctExamList.addExam(EXAMONE)); + } + + @Test + public void replaceExam_nullTargetExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + distinctExamList.replaceExam(null, EXAMONE, false)); + } + + @Test + public void replaceExam_nullEditedExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctExamList + .replaceExam(EXAMONE, null, false)); + } + + @Test + public void replaceExam_targetExamNotInList_throwsExamNotFoundException() { + assertThrows(ExamNotFoundException.class, () -> distinctExamList.replaceExam(EXAMONE, EXAMTWO, false)); + } + + @Test + public void replaceExam_expectedSameExam_success() { + distinctExamList.addExam(EXAMONE); + distinctExamList.replaceExam(EXAMONE, EXAMONE, true); + DistinctExamList expectedDistinctExamList = new DistinctExamList(); + expectedDistinctExamList.addExam(EXAMONE); + assertEquals(expectedDistinctExamList, distinctExamList); + } + + @Test + public void replaceExam_expectedSameExamButIsDifferentExam_throwsWrongExamModifiedException() { + distinctExamList.addExam(EXAMONE); + assertThrows(ExamIdentityModifiedException.class, () -> distinctExamList + .replaceExam(EXAMONE, EXAMTWO, true)); + } + + @Test + public void replaceExam_expectedDifferentExam_success() { + distinctExamList.addExam(EXAMONE); + distinctExamList.replaceExam(EXAMONE, EXAMTWO, false); + DistinctExamList expectedDistinctTaskList = new DistinctExamList(); + expectedDistinctTaskList.addExam(EXAMTWO); + assertEquals(expectedDistinctTaskList, distinctExamList); + } + + @Test + public void replaceExam_expectedDifferentExamButIsSameExam_throwsDuplicateExamException() { + // edited task has non-unique identity + distinctExamList.addExam(EXAMONE); + distinctExamList.addExam(EXAMTWO); + assertThrows(DuplicateExamException.class, () -> distinctExamList.replaceExam(EXAMONE, EXAMTWO, false)); + } + + @Test + public void remove_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctExamList.remove(null)); + } + + @Test + public void remove_examDoesNotExist_throwsExamNotFoundException() { + assertThrows(ExamNotFoundException.class, () -> distinctExamList.remove(EXAMONE)); + } + + @Test + public void remove_existingExam_removesExam() { + distinctExamList.addExam(EXAMONE); + distinctExamList.remove(EXAMONE); + DistinctExamList expectedDistinctExamList = new DistinctExamList(); + assertEquals(expectedDistinctExamList, distinctExamList); + } + + @Test + public void updateTotalNumOfTasks_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + distinctExamList.updateTotalNumOfTasks(null, new DistinctTaskList())); + } + + @Test + public void updateTotalNumOfTasks_nullTasks_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + distinctExamList.updateTotalNumOfTasks(EXAMONE, null)); + } + + @Test + public void updateTotalNumOfTasks_examDoesNotExist_throwsExamNotFoundException() { + assertThrows(ExamNotFoundException.class, () -> + distinctExamList.updateTotalNumOfTasks(EXAMONE, new DistinctTaskList())); + } + + @Test + public void updateTotalNumOfTasks_success() { + distinctExamList.setExams(TypicalExams.getTypicalExams()); + DistinctTaskList tasks = new DistinctTaskList(); + tasks.setTasks(TypicalTasks.getTypicalTasks()); + Task taskDCopy = new TaskBuilder(TASK_D).withTaskDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + tasks.addTask(taskDCopy); + distinctExamList.updateTotalNumOfTasks(MIDTERM_EXAM, tasks); + + // count for MIDTERM_EXAM is updated + assertTrue(distinctExamList.examList.get(0).getTotalNumOfTasks() == 2); + + // count for other exams remains the same + distinctExamList.examList.filtered(exam -> !exam.isSameExam(MIDTERM_EXAM)).forEach(exam -> { + assertTrue(exam.getTotalNumOfTasks() == 0); + }); + } + + @Test + public void updateTotalNumOfTasksForAllExams_noExams_success() { + DistinctTaskList tasks = new DistinctTaskList(); + tasks.setTasks(getTypicalTasks()); + DistinctExamList expectedExamList = new DistinctExamList(); + distinctExamList.updateTotalNumOfTasksForAllExams(tasks); + assertEquals(expectedExamList, distinctExamList); + } + + @Test + public void updateTotalNumOfTasksForAllExams_noTasks_success() { + DistinctTaskList tasks = new DistinctTaskList(); + distinctExamList.setExams(getTypicalExams()); + distinctExamList.updateTotalNumOfTasksForAllExams(tasks); + DistinctExamList expectedExamList = new DistinctExamList(); + expectedExamList.setExams(getTypicalExams()); + assertEquals(expectedExamList, distinctExamList); + } + + @Test + public void updateTotalNumOfTasksForAllExams_withExamsAndLinkedTasks_success() { + DistinctTaskList tasks = new DistinctTaskList(); + Task taskLinkedToMidtermExam = new TaskBuilder(TASK_D).build(); + tasks.addTask(taskLinkedToMidtermExam); + Task taskLinkedToFinalExam = new TaskBuilder(TASK_A).withExam(FINAL_EXAM).build(); + tasks.addTask(taskLinkedToFinalExam); + + distinctExamList.setExams(getTypicalExams()); + distinctExamList.updateTotalNumOfTasksForAllExams(tasks); + assertTrue(distinctExamList.examList.get(0).getTotalNumOfTasks() == 1); + assertTrue(distinctExamList.examList.get(1).getTotalNumOfTasks() == 1); + + Task anotherTaskLinkedToFinalExam = new TaskBuilder(taskLinkedToFinalExam).withTaskDescription("task a copy") + .build(); + tasks.addTask(anotherTaskLinkedToFinalExam); + tasks.remove(taskLinkedToMidtermExam); + distinctExamList.updateTotalNumOfTasksForAllExams(tasks); + assertTrue(distinctExamList.examList.get(0).getTotalNumOfTasks() == 0); + assertTrue(distinctExamList.examList.get(1).getTotalNumOfTasks() == 2); + } + + @Test + public void updateNumOfCompletedTasks_nullExam_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + distinctExamList.updateNumOfCompletedTasks(null, new DistinctTaskList())); + } + + @Test + public void updateNumOfCompletedTasks_nullTasks_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + distinctExamList.updateNumOfCompletedTasks(EXAMONE, null)); + } + + @Test + public void updateNumOfCompletedTasks_examDoesNotExist_throwsExamNotFoundException() { + assertThrows(ExamNotFoundException.class, () -> + distinctExamList.updateNumOfCompletedTasks(EXAMONE, new DistinctTaskList())); + } + + @Test + public void updateNumOfCompletedTasks_success() { + distinctExamList.setExams(TypicalExams.getTypicalExams()); + DistinctTaskList tasks = new DistinctTaskList(); + tasks.setTasks(TypicalTasks.getTypicalTasks()); + Task taskDCopy = new TaskBuilder(TASK_D).withTaskDescription(VALID_DESCRIPTION_DO_TUTORIAL) + .withStatus("incomplete").build(); + tasks.addTask(taskDCopy); + distinctExamList.updateNumOfCompletedTasks(MIDTERM_EXAM, tasks); + + // count for MIDTERM_EXAM is updated + Exam expectedExam = new ExamBuilder(MIDTERM_EXAM).withNumOfCompletedTasks(1).build(); + assertTrue(distinctExamList.examList.get(0).hasAllSameFields(expectedExam)); + + // count for other exams remains the same + distinctExamList.examList.filtered(exam -> !exam.isSameExam(MIDTERM_EXAM)).forEach(exam -> { + assertTrue(exam.getNumOfCompletedTasks() == 0); + }); + } + + @Test + public void updateNumOfCompletedTasksForAllExams_noExams_success() { + DistinctTaskList tasks = new DistinctTaskList(); + tasks.setTasks(getTypicalTasks()); + DistinctExamList expectedExamList = new DistinctExamList(); + distinctExamList.updateNumOfCompletedTasksForAllExams(tasks); + assertEquals(expectedExamList, distinctExamList); + } + + @Test + public void updateNumOfCompletedTasksForAllExams_noTasks_success() { + DistinctTaskList tasks = new DistinctTaskList(); + distinctExamList.setExams(getTypicalExams()); + distinctExamList.updateNumOfCompletedTasksForAllExams(tasks); + DistinctExamList expectedExamList = new DistinctExamList(); + expectedExamList.setExams(getTypicalExams()); + assertEquals(expectedExamList, distinctExamList); + } + + @Test + public void updateNumOfCompletedTasksForAllExams_withExamsAndLinkedTasks_success() { + DistinctTaskList tasks = new DistinctTaskList(); + Task completedTaskLinkedToMidtermExam = new TaskBuilder(TASK_D).build(); + tasks.addTask(completedTaskLinkedToMidtermExam); + Task incompleteTaskLinkedToFinalExam = new TaskBuilder(TASK_A).withExam(FINAL_EXAM).build(); + tasks.addTask(incompleteTaskLinkedToFinalExam); + + distinctExamList.setExams(getTypicalExams()); + distinctExamList.updateNumOfCompletedTasksForAllExams(tasks); + assertTrue(distinctExamList.examList.get(0).getNumOfCompletedTasks() == 1); + assertTrue(distinctExamList.examList.get(1).getNumOfCompletedTasks() == 0); + + Task completedTaskLinkedToFinalExam = new TaskBuilder(incompleteTaskLinkedToFinalExam) + .withTaskDescription("task a copy").withStatus("complete").build(); + tasks.addTask(completedTaskLinkedToFinalExam); + tasks.remove(completedTaskLinkedToMidtermExam); + distinctExamList.updateNumOfCompletedTasksForAllExams(tasks); + assertTrue(distinctExamList.examList.get(0).getNumOfCompletedTasks() == 0); + assertTrue(distinctExamList.examList.get(1).getNumOfCompletedTasks() == 1); + } + + @Test + public void resetAllTaskCount() { + // no exams + distinctExamList.resetAllTaskCount(); + DistinctExamList expectedList = new DistinctExamList(); + assertEquals(expectedList, distinctExamList); + + // with exams + Exam finalExamWithCount = new ExamBuilder(FINAL_EXAM) + .withNumOfCompletedTasks(2).withTotalNumOfTasks(5).build(); + Exam midtermExamWithCount = new ExamBuilder(MIDTERM_EXAM) + .withNumOfCompletedTasks(3).build(); + distinctExamList.addExam(finalExamWithCount); + distinctExamList.addExam(midtermExamWithCount); + distinctExamList.resetAllTaskCount(); + distinctExamList.examList.forEach(exam -> { + assertTrue(exam.getNumOfCompletedTasks() == 0); + assertTrue(exam.getTotalNumOfTasks() == 0); + }); + } + + @Test + public void deleteExamsWithModule_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + distinctExamList.deleteExamsWithModule(null)); + } + + @Test + public void deleteExamsWithModule_success() { + distinctExamList.setExams(getTypicalExams()); + distinctExamList.deleteExamsWithModule(CS2030); + DistinctExamList expectedList = new DistinctExamList(); + expectedList.setExams(new ArrayList<>(Arrays.asList(MIDTERM_EXAM, EXAMONE, EXAMTWO, + CS2040_FINAL, MA1521_MIDTERM))); + assertEquals(expectedList, distinctExamList); + } + + @Test + public void updateModuleFieldForExam_null_throwsNullPointerException() { + // null taskList + assertThrows(NullPointerException.class, () -> + distinctExamList.updateModuleFieldForExam(null, CS2030, CS2040)); + + // null previousModule + assertThrows(NullPointerException.class, () -> + distinctExamList.updateModuleFieldForExam(new DistinctTaskList(), CS2030, null)); + + // null newModule + assertThrows(NullPointerException.class, () -> + distinctExamList.updateModuleFieldForExam(new DistinctTaskList(), null, CS2040)); + } + + @Test + public void updateModuleFieldForExam_examWithDifferentModule() { + distinctExamList.addExam(MIDTERM_EXAM); + DistinctTaskList tasks = new DistinctTaskList(); + tasks.addTask(TASK_D); + Module editedModule = new ModuleBuilder(CS2030).withModuleCode("cs1000").build(); + distinctExamList.updateModuleFieldForExam(tasks, CS2030, editedModule); + + assertTrue(tasks.taskList.get(0).hasAllSameFields(TASK_D)); + assertTrue(distinctExamList.examList.get(0).hasAllSameFields(MIDTERM_EXAM)); + } + + @Test + public void updateModuleFieldForExam_examWithTargetModule() { + distinctExamList.addExam(MIDTERM_EXAM); + DistinctTaskList tasks = new DistinctTaskList(); + tasks.addTask(TASK_D); + Module editedModule = new ModuleBuilder(CS2040).withModuleCode("cs1000").build(); + distinctExamList.updateModuleFieldForExam(tasks, CS2040, editedModule); + + // exam is updated with new module + Exam expectedExam = new ExamBuilder(MIDTERM_EXAM).withModule("cs1000").build(); + assertTrue(expectedExam.hasAllSameFields(distinctExamList.examList.get(0))); + + // task D updated with the new exam + Task expectedTask = new TaskBuilder(TASK_D).withExam(expectedExam).build(); + assertTrue(tasks.taskList.get(0).hasAllSameFields(expectedTask)); + } + + @Test + public void setExams_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctExamList.setExams(null)); + } + + @Test + public void setTasks_list_replacesOwnListWithProvidedList() { + distinctExamList.addExam(EXAMONE); + List examList = Collections.singletonList(EXAMTWO); + distinctExamList.setExams(examList); + DistinctExamList expectedDistinctExamList = new DistinctExamList(); + expectedDistinctExamList.addExam(EXAMTWO); + assertEquals(expectedDistinctExamList, distinctExamList); + } + + @Test + public void getUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () + -> distinctExamList.getUnmodifiableExamList().remove(0)); + } +} diff --git a/src/test/java/seedu/address/model/exam/ExamDateTest.java b/src/test/java/seedu/address/model/exam/ExamDateTest.java new file mode 100644 index 00000000000..d1dda7b6038 --- /dev/null +++ b/src/test/java/seedu/address/model/exam/ExamDateTest.java @@ -0,0 +1,53 @@ +package seedu.address.model.exam; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; +public class ExamDateTest { + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ExamDate(null)); + } + + @Test + public void constructor_invalidExamDate_throwsIllegalArgumentException() { + String invalidExamDate = ""; + assertThrows(IllegalArgumentException.class, () -> new ExamDate(invalidExamDate)); + } + + @Test + public void isValidExamDate() { + // null address + assertThrows(NullPointerException.class, () -> ExamDate.isValidDate(null)); + + // invalid addresses + assertFalse(ExamDate.isValidDate("")); // empty string + assertFalse(ExamDate.isValidDate(" ")); // spaces only + + // valid addresses + //should the date include existing date may be outdated + assertTrue(ExamDate.isValidDate("20-10-2025")); + assertTrue(ExamDate.isValidDate("31-12-2025")); + assertTrue(ExamDate.isValidDate("01-01-2025")); + assertTrue(ExamDate.isValidDate("28-02-2025")); + + //Past date + assertFalse(ExamDate.isNotAPastDate("28-10-2022")); + assertFalse(ExamDate.isNotAPastDate("13-02-1999")); + + //Not in DD-MM-YYYY format + assertFalse(ExamDate.isValidDateFormat("2025-08-20")); + assertFalse(ExamDate.isValidDateFormat("20-9-2025")); + assertFalse(ExamDate.isValidDateFormat("020-08-2025")); + assertFalse(ExamDate.isValidDateFormat("ab-bc-asad")); + assertFalse(ExamDate.isValidDateFormat("20-08 -2024")); + assertFalse(ExamDate.isValidDateFormat("")); // empty string + assertFalse(ExamDate.isValidDateFormat(" ")); // spaces only + + + assertFalse(ExamDate.isValidDate("29-02-2023")); + assertFalse(ExamDate.isValidDate("31-11-2024")); + } +} diff --git a/src/test/java/seedu/address/model/exam/ExamDescriptionTest.java b/src/test/java/seedu/address/model/exam/ExamDescriptionTest.java new file mode 100644 index 00000000000..1b5af3160c4 --- /dev/null +++ b/src/test/java/seedu/address/model/exam/ExamDescriptionTest.java @@ -0,0 +1,41 @@ +package seedu.address.model.exam; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + + +public class ExamDescriptionTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ExamDescription(null)); + } + + @Test + public void constructor_invalidExamDescription_throwsIllegalArgumentException() { + String invalidExamDescription = ""; + assertThrows(IllegalArgumentException.class, () -> new ExamDescription(invalidExamDescription)); + } + + @Test + public void isValidExamDescription() { + // null name + assertThrows(NullPointerException.class, () -> ExamDescription.isValidDescription(null)); + + // invalid name + assertFalse(ExamDescription.isValidDescription("")); // empty string + + // valid name + assertTrue(ExamDescription.isValidDescription("finals")); // alphabets only + assertTrue(ExamDescription.isValidDescription(" finals ")); // alphab + assertTrue(ExamDescription.isValidDescription("Final paper")); + assertTrue(ExamDescription.isValidDescription("Final paper 1")); + assertTrue(ExamDescription.isValidDescription("MIDTERMS")); + assertTrue(ExamDescription.isValidDescription("Final paper testing on topic 1 2 3 4 5")); + + + } +} diff --git a/src/test/java/seedu/address/model/exam/ExamTest.java b/src/test/java/seedu/address/model/exam/ExamTest.java new file mode 100644 index 00000000000..b3705a057a8 --- /dev/null +++ b/src/test/java/seedu/address/model/exam/ExamTest.java @@ -0,0 +1,191 @@ +package seedu.address.model.exam; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_EXAMONE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2030; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalExams.FINAL_EXAM; +import static seedu.address.testutil.TypicalExams.MIDTERM_EXAM; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.model.exam.exceptions.NoLinkedTaskForExamException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.testutil.EditExamDescriptorBuilder; +import seedu.address.testutil.ExamBuilder; + +public class ExamTest { + + @Test + public void setNumOfCompletedTasks() { + Exam expectedExam = new ExamBuilder(FINAL_EXAM).withNumOfCompletedTasks(2).build(); + assertTrue(FINAL_EXAM.setNumOfCompletedTasks(2).hasAllSameFields(expectedExam)); + } + + @Test + public void setTotalNumOfTasks() { + Exam expectedExam = new ExamBuilder(FINAL_EXAM).withTotalNumOfTasks(2).build(); + assertTrue(FINAL_EXAM.setTotalNumOfTasks(2).hasAllSameFields(expectedExam)); + } + + @Test + public void hasTasks() { + // no tasks + assertFalse(FINAL_EXAM.hasTasks()); + + // has tasks + Exam linkedExam = new ExamBuilder(FINAL_EXAM).withTotalNumOfTasks(5).build(); + assertTrue(linkedExam.hasTasks()); + } + + @Test + public void getPercentageCompleted() { + // no linked tasks + assertThrows(NoLinkedTaskForExamException.class, () -> FINAL_EXAM.getPercentageCompleted()); + + // no completed tasks + Exam examOne = new ExamBuilder(FINAL_EXAM) + .withTotalNumOfTasks(5).build(); + assertEquals(0, examOne.getPercentageCompleted()); + + // 2 out of 5 completed tasks + Exam examTwo = new ExamBuilder(FINAL_EXAM) + .withTotalNumOfTasks(5).withNumOfCompletedTasks(2).build(); + assertEquals(0.4, examTwo.getPercentageCompleted()); + } + + @Test + public void generateProgressMessage_noTasks_success() { + String expectedMessage = Exam.MESSAGE_NO_TASKS_FOR_EXAM; + assertEquals(expectedMessage, FINAL_EXAM.generateProgressMessage()); + } + + @Test + public void generateProgressMessage_hasTasks_success() { + Exam exam = new ExamBuilder(FINAL_EXAM).withNumOfCompletedTasks(2).withTotalNumOfTasks(3).build(); + String expectedMessage = "2 / 3 task(s) completed"; + assertEquals(expectedMessage, exam.generateProgressMessage()); + } + + @Test + public void edit_withEditExamDescriptor() { + + // null descriptor + assertThrows(NullPointerException.class, () -> MIDTERM_EXAM.edit(null)); + + // different module + Exam exam = new ExamBuilder(MIDTERM_EXAM).withNumOfCompletedTasks(2).withTotalNumOfTasks(3).build(); + EditExamCommand.EditExamDescriptor descriptorModule = new EditExamDescriptorBuilder() + .withModule(VALID_MODULE_CS2030).build(); + Exam expectedExam = new ExamBuilder(exam).withModule(VALID_MODULE_CS2030) + .withNumOfCompletedTasks(0).withTotalNumOfTasks(0).build(); + assertEquals(expectedExam, exam.edit(descriptorModule)); + + // different description + EditExamCommand.EditExamDescriptor descriptorDescription = new EditExamDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_EXAMONE).build(); + Exam editedExam = new ExamBuilder(exam).withDescription(VALID_DESCRIPTION_EXAMONE).build(); + assertEquals(editedExam, exam.edit(descriptorDescription)); + } + + @Test + public void edit_withModuleDescriptionAndDate() { + + // null module, description and date + assertThrows(NullPointerException.class, () -> + FINAL_EXAM.edit(null, null, null)); + + // module edited + Module module = new Module(new ModuleCode(VALID_MODULE_CS2030)); + Exam finalExamEdited = new ExamBuilder(MIDTERM_EXAM).withModule(VALID_MODULE_CS2030).build(); + assertEquals(finalExamEdited, MIDTERM_EXAM.edit(module, null, null)); + + // description edited + ExamDescription description = new ExamDescription(VALID_DESCRIPTION_EXAMONE); + Exam finalExamDescriptionEdited = new ExamBuilder(FINAL_EXAM) + .withDescription(VALID_DESCRIPTION_EXAMONE).build(); + assertEquals(finalExamDescriptionEdited, FINAL_EXAM.edit(null, description, null)); + + // date edited + ExamDate date = new ExamDate(VALID_DATE_EXAMONE); + Exam finalExamDateEdited = new ExamBuilder(FINAL_EXAM).withDate(VALID_DATE_EXAMONE).build(); + assertEquals(finalExamDateEdited, FINAL_EXAM.edit(null, null, date)); + } + + @Test + public void hasAllSameFields() { + // same object -> returns true + assertTrue(FINAL_EXAM.hasAllSameFields(FINAL_EXAM)); + + // same fields -> return true + Exam finalExamCopy = new ExamBuilder(FINAL_EXAM).build(); + assertTrue(FINAL_EXAM.hasAllSameFields(finalExamCopy)); + + // null -> returns false + assertThrows(NullPointerException.class, () -> FINAL_EXAM.hasAllSameFields(null)); + + // different description -> returns false + Exam examWithDifferentDescription = new ExamBuilder(FINAL_EXAM).withDescription("another exam").build(); + assertFalse(FINAL_EXAM.hasAllSameFields(examWithDifferentDescription)); + + // different module -> returns false + Exam examWithDifferentModule = new ExamBuilder(FINAL_EXAM).withModule("cs2100").build(); + assertFalse(FINAL_EXAM.hasAllSameFields(examWithDifferentModule)); + + // different date -> returns false + Exam examWithDifferentDate = new ExamBuilder(FINAL_EXAM).withDate("11-11-2025").build(); + assertFalse(FINAL_EXAM.hasAllSameFields(examWithDifferentDate)); + + // different number of completed tasks -> returns false + Exam examOne = new ExamBuilder(FINAL_EXAM).withNumOfCompletedTasks(1).build(); + Exam examWithDifferentNumOfCompletedTasks = new ExamBuilder(FINAL_EXAM).withNumOfCompletedTasks(2).build(); + assertFalse(examOne.hasAllSameFields(examWithDifferentNumOfCompletedTasks)); + + // different date -> returns false + Exam examTwo = new ExamBuilder(FINAL_EXAM).withTotalNumOfTasks(0).build(); + Exam examWithDifferentNumOfTotalTasks = new ExamBuilder(FINAL_EXAM).withTotalNumOfTasks(1).build(); + assertFalse(examTwo.hasAllSameFields(examWithDifferentNumOfTotalTasks)); + } + + @Test + public void isEqual() { + Exam finalExam = FINAL_EXAM; + + // same object -> returns true + assertTrue(finalExam.equals(finalExam)); + + // same fields -> return true + Exam finalExamCopy = new ExamBuilder(FINAL_EXAM).build(); + assertTrue(finalExam.equals(finalExamCopy)); + + // different types -> returns false + assertFalse(finalExam.equals(5)); + + // null -> returns false + assertFalse(finalExam.equals(null)); + + // different description -> returns false + Exam examWithDifferentDescription = new ExamBuilder(finalExam).withDescription("another exam").build(); + assertFalse(finalExam.equals(examWithDifferentDescription)); + + // different module -> returns false + Exam examWithDifferentModule = new ExamBuilder(finalExam).withModule("cs2100").build(); + assertFalse(finalExam.equals(examWithDifferentModule)); + + // different date -> returns false + Exam examWithDifferentDate = new ExamBuilder(finalExam).withDate("11-11-2025").build(); + assertFalse(finalExam.equals(examWithDifferentDate)); + } + + @Test + public void isCorrectString() { + String expectedResult = String.format("Module: %s; ExamDescription: %s; ExamDate: %s", + FINAL_EXAM.getModule(), FINAL_EXAM.getDescription(), FINAL_EXAM.getExamDate()); + assertEquals(expectedResult, FINAL_EXAM.toString()); + } +} diff --git a/src/test/java/seedu/address/model/module/DistinctModuleListTest.java b/src/test/java/seedu/address/model/module/DistinctModuleListTest.java new file mode 100644 index 00000000000..f1a13afff10 --- /dev/null +++ b/src/test/java/seedu/address/model/module/DistinctModuleListTest.java @@ -0,0 +1,189 @@ +package seedu.address.model.module; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalModules.CS2040S; +import static seedu.address.testutil.TypicalModules.CS2100; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.module.exceptions.DuplicateModuleException; +import seedu.address.model.module.exceptions.ModuleNotFoundException; +import seedu.address.model.task.DistinctTaskList; +import seedu.address.model.task.Task; +import seedu.address.testutil.ModuleBuilder; +import seedu.address.testutil.TaskBuilder; + +/** + * Integration testing of DistinctModuleList with Module class and DistinctTaskList. + */ +public class DistinctModuleListTest { + private final DistinctModuleList moduleList = new DistinctModuleList(); + + @Test + public void containsModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList.containsModule(null)); + } + + @Test + public void containsModule_moduleNotInList_returnsFalse() { + assertFalse(moduleList.containsModule(CS2030)); + } + + @Test + public void containsModule_moduleInList_returnsTrue() { + moduleList.addModule(CS2030); + assertTrue(moduleList.containsModule(CS2030)); + } + + @Test + public void containsModule_moduleWithSameModuleCode_returnsTrue() { + Module module = new ModuleBuilder() + .withModuleCode("CS2030").build(); + moduleList.addModule(module); + assertTrue(moduleList.containsModule(CS2030)); + } + + @Test + public void addModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList.addModule(null)); + } + + @Test + public void addModule_duplicateModuleAdded_throwsDuplicateModuleException() { + moduleList.addModule(CS2030); + assertThrows(DuplicateModuleException.class, () -> moduleList.addModule(CS2030)); + } + + @Test + public void setModules_nullModules_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList.setModules(null)); + } + + @Test + public void setModules_newListIsSet_listInDistinctModuleListIsChanged() { + moduleList.addModule(CS2030); + List modules = List.of(CS2100, CS2040S); + moduleList.setModules(modules); + DistinctModuleList distinctModuleList = new DistinctModuleList(); + distinctModuleList.addModule(CS2100); + distinctModuleList.addModule(CS2040S); + assertEquals(moduleList, distinctModuleList); + } + + @Test + public void updateTotalNumberOfTasks_nullModule_throwsNullPointerException() { + DistinctTaskList distinctTaskList = new DistinctTaskList(); + assertThrows(NullPointerException.class, () -> moduleList + .updateTotalNumOfTasks(null, distinctTaskList)); + } + + @Test + public void updateTotalNumberOfTasks_nullDistinctTaskList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList + .updateTotalNumOfTasks(CS2030, null)); + } + + @Test + public void updateTotalNumberOfTasks_moduleNotInTask_throwsModuleNotFoundException() { + DistinctTaskList distinctTaskList = new DistinctTaskList(); + Task addedTask = new TaskBuilder().withModule("CS2040S").build(); + distinctTaskList.addTask(addedTask); + assertThrows(ModuleNotFoundException.class, () -> moduleList + .updateTotalNumOfTasks(CS2030, distinctTaskList)); + } + + @Test + public void updateNumOfCompletedTasks_nullModule_throwsNullPointerException() { + DistinctTaskList distinctTaskList = new DistinctTaskList(); + assertThrows(NullPointerException.class, () -> moduleList + .updateNumOfCompletedTasks(null, distinctTaskList)); + } + + @Test + public void updateNumOfCompletedTasks_nullDistinctTaskList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList + .updateNumOfCompletedTasks(CS2030, null)); + } + + @Test + public void updateNumOfCompletedTasks_moduleNotInTask_throwsModuleNotFoundException() { + DistinctTaskList distinctTaskList = new DistinctTaskList(); + Task addedTask = new TaskBuilder().withModule("CS2040S").build(); + distinctTaskList.addTask(addedTask); + assertThrows(ModuleNotFoundException.class, () -> moduleList + .updateNumOfCompletedTasks(CS2030, distinctTaskList)); + } + + @Test + public void remove_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList.remove(null)); + } + + @Test + public void remove_moduleNotPresent_throwsModuleNotFoundException() { + assertThrows(ModuleNotFoundException.class, () -> moduleList.remove(CS2040)); + } + + @Test + public void remove_moduleRemovedFromSet_moduleNoLongerInSet() { + moduleList.addModule(CS2040); + DistinctModuleList distinctModuleList = new DistinctModuleList(); + moduleList.remove(CS2040); + assertEquals(distinctModuleList, moduleList); + } + + @Test + public void replaceModule_nullTargetModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList.replaceModule(null, CS2030)); + } + + @Test + public void replaceModule_nullEditedModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> moduleList.replaceModule(CS2030, null)); + } + + @Test + public void replaceModule_moduleNotPresent_throwsModuleNotFoundException() { + assertThrows(ModuleNotFoundException.class, () -> moduleList.replaceModule(CS2030, CS2040)); + } + + @Test + public void replaceModule_duplicateModule_throwsDuplicateModuleException() { + moduleList.addModule(CS2030); + moduleList.addModule(CS2040); + assertThrows(DuplicateModuleException.class, () -> moduleList.replaceModule(CS2030, CS2040)); + } + + @Test + public void testEquals() { + moduleList.addModule(CS2040); + moduleList.addModule(CS2030); + DistinctModuleList distinctModuleList = new DistinctModuleList(); + distinctModuleList.addModule(CS2040); + distinctModuleList.addModule(CS2030); + + //Same DistinctModuleList + assertTrue(moduleList.equals(moduleList)); + + //Different DistinctModuleList but with same modules + assertTrue(moduleList.equals(distinctModuleList)); + + //Different DistinctModuleList with different modules + distinctModuleList.setModules(List.of(CS2100)); + assertFalse(moduleList.equals(distinctModuleList)); + + //Different Object type + assertFalse(moduleList.equals(12012)); + + //Null + assertFalse(moduleList.equals(null)); + } + +} diff --git a/src/test/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..c45bb2984e7 --- /dev/null +++ b/src/test/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicateTest.java @@ -0,0 +1,43 @@ +package seedu.address.model.module; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.ModuleBuilder; + +public class ModuleCodeContainsKeywordsPredicateTest { + + @Test + public void test_modCodeContainsKeywords_returnsTrue() { + ModuleCodeContainsKeywordsPredicate predicate = + new ModuleCodeContainsKeywordsPredicate(Collections.singletonList("cs2030s")); + assertTrue(predicate.test(new ModuleBuilder().withModuleCode("cs2030s").build())); + + predicate = new ModuleCodeContainsKeywordsPredicate(Collections.singletonList("cs2030s")); + assertTrue(predicate.test(new ModuleBuilder().withModuleCode("cS2030S").build())); + + predicate = new ModuleCodeContainsKeywordsPredicate(Arrays.asList("20")); + assertTrue(predicate.test(new ModuleBuilder().withModuleCode("Cs2030S").build())); + + predicate = new ModuleCodeContainsKeywordsPredicate(Arrays.asList("30s")); + assertTrue(predicate.test(new ModuleBuilder().withModuleCode("CS2030S").build())); + } + + @Test + public void test_modCodeDoesNotContainKeywords_returnsFalse() { + // Zero keywords + ModuleCodeContainsKeywordsPredicate predicate = + new ModuleCodeContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new ModuleBuilder().withModuleCode("cs2030s").build())); + + // Non-matching keyword + predicate = new ModuleCodeContainsKeywordsPredicate(Arrays.asList("cs2030s")); + assertFalse(predicate.test(new ModuleBuilder().withModuleCode("cs2040s").build())); + + } +} diff --git a/src/test/java/seedu/address/model/module/ModuleCodeTest.java b/src/test/java/seedu/address/model/module/ModuleCodeTest.java new file mode 100644 index 00000000000..e0ac06d7f4b --- /dev/null +++ b/src/test/java/seedu/address/model/module/ModuleCodeTest.java @@ -0,0 +1,66 @@ +package seedu.address.model.module; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ModuleCodeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ModuleCode(null)); + } + + @Test + public void constructor_invalidModuleCode_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new ModuleCode("a")); + } + + @Test + public void testIsValidModuleCode() { + + //Valid Module Code + assertTrue(ModuleCode.isValidModuleCode("CS2103T")); + + //Invalid length for module code(Less than 6) + assertFalse(ModuleCode.isValidModuleCode("abc")); + + //Invalid characters for module code + assertFalse(ModuleCode.isValidModuleCode("CS2103/T")); + + //First two characters are not alphabetical + assertFalse(ModuleCode.isValidModuleCode("C2103L")); + } + + @Test + public void testEquals() { + ModuleCode moduleCodeCS2100 = new ModuleCode("CS2100"); + ModuleCode moduleCodeCS2103 = new ModuleCode("CS2103"); + ModuleCode moduleCodeCS2100Copy = new ModuleCode("CS2100"); + + //Same ModuleCode object + assertTrue(moduleCodeCS2100.equals(moduleCodeCS2100)); + + //Different ModuleCode object but same module code + assertTrue(moduleCodeCS2100.equals(moduleCodeCS2100Copy)); + + //Different ModuleCode object but different module code + assertFalse(moduleCodeCS2100.equals(moduleCodeCS2103)); + + //Different object type + assertFalse(moduleCodeCS2100.equals(921)); + + //Comparing with null + assertFalse(moduleCodeCS2100.equals(null)); + } + + @Test + public void testToString() { + ModuleCode moduleCodeCS2100 = new ModuleCode("CS2100"); + assertEquals("CS2100", moduleCodeCS2100.toString()); + } +} + diff --git a/src/test/java/seedu/address/model/module/ModuleTest.java b/src/test/java/seedu/address/model/module/ModuleTest.java new file mode 100644 index 00000000000..d8c6bb9293d --- /dev/null +++ b/src/test/java/seedu/address/model/module/ModuleTest.java @@ -0,0 +1,167 @@ +package seedu.address.model.module; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.model.module.Module.MESSAGE_NO_TASKS_FOR_MODULE; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.ModuleBuilder; + +/** + * Integration testing of Module class with some unit testing. + */ +public class ModuleTest { + + @Test + public void constructor_nullModuleCode_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Module(null)); + } + + @Test + public void constructor_nullModuleCredit_throwsNullPointerException() { + ModuleCode moduleCode = new ModuleCode("CS2100"); + ModuleName moduleName = new ModuleName("Computer Organisation"); + assertThrows(NullPointerException.class, () -> new Module(moduleCode, moduleName, null)); + } + + @Test + public void constructor_nullModuleName_throwsNullPointerException() { + ModuleCode moduleCode = new ModuleCode("CS2100"); + ModuleCredit moduleCredit = new ModuleCredit(4); + assertThrows(NullPointerException.class, () -> new Module(moduleCode, + null, moduleCredit)); + } + + @Test + public void testIsSameModule() { + Module module = new ModuleBuilder().withModuleCode("CS2100").build(); + Module sameModule = new ModuleBuilder(module).build(); + Module differentModule = new ModuleBuilder().withModuleCode("CS2102").build(); + + //Checks if it is same module as itself + assertTrue(module.isSameModule(module)); + + //Checks if it is same module as another module with same module code + assertTrue(module.isSameModule(sameModule)); + + //Checks if the module is different from another module with different module code + assertFalse(module.isSameModule(differentModule)); + + //Checks if the module is different from null + assertFalse(module.isSameModule(null)); + } + + @Test + public void hasAllSameFields_identicalFields_success() { + ModuleCode firstModuleCode = new ModuleCode("CS2100"); + ModuleCode firstModuleCodeCopy = new ModuleCode("CS2100"); + ModuleName firstModuleName = new ModuleName("Computer Organisation"); + ModuleName firstModuleNameCopy = new ModuleName("Computer Organisation"); + ModuleCredit firstModuleCredit = new ModuleCredit(4); + ModuleCredit firstModuleCreditCopy = new ModuleCredit(4); + + //All fields are identical + Module firstModule = new Module(firstModuleCode, firstModuleName, firstModuleCredit); + Module firstModuleCopy = new Module(firstModuleCodeCopy, firstModuleNameCopy, firstModuleCreditCopy); + + //Checks if it is same as itself + firstModule.hasAllSameFields(firstModule); + + //Checks if it is same as another module with a same fields + firstModule.hasAllSameFields(firstModuleCopy); + } + + @Test + public void hasAllSameFields_differentFields_failure() { + ModuleCode firstModuleCode = new ModuleCode("CS2100"); + ModuleName firstModuleName = new ModuleName("Computer Organisation"); + ModuleCredit firstModuleCredit = new ModuleCredit(4); + ModuleCode secondModuleCode = new ModuleCode("CS2102"); + ModuleName secondModuleName = new ModuleName("Database"); + ModuleCredit secondModuleCredit = new ModuleCredit(5); + + //Missing fields + Module firstModule = new Module(firstModuleCode, firstModuleName, firstModuleCredit); + Module secondModule = new Module(secondModuleCode); + + //Compare with module with only module code + assertFalse(secondModule.hasAllSameFields(firstModule)); + + //Different Fields + Module thirdModule = new Module(secondModuleCode, firstModuleName, firstModuleCredit); + Module fourthModule = new Module(firstModuleCode, secondModuleName, firstModuleCredit); + Module fifthModule = new Module(firstModuleCode, firstModuleName, secondModuleCredit); + + //Different module code + assertFalse(firstModule.hasAllSameFields(thirdModule)); + + //Different module name + assertFalse(firstModule.hasAllSameFields(fourthModule)); + + //Different module credit + assertFalse(firstModule.hasAllSameFields(fifthModule)); + } + + @Test + public void edit_nullEditModuleDescriptor_throwsNullPointerException() { + Module module = new ModuleBuilder().withModuleCode("CS2100").build(); + assertThrows(NullPointerException.class, () -> module.edit(null)); + } + + @Test + public void testGenerateProgressMessage() { + ModuleCode moduleCode = new ModuleCode("CS2100"); + ModuleName moduleName = new ModuleName("Computer Organisation"); + ModuleCredit moduleCredit = new ModuleCredit(4); + Module module = new Module(moduleCode, moduleName, moduleCredit); + module = module.setTotalNumOfTasks(0); + + //Checks if progress message shows no task if there are no tasks + assertEquals(MESSAGE_NO_TASKS_FOR_MODULE, module.generateProgressMessage()); + + //Checks if progress message shows correct number of tasks completed + module = module.setTotalNumOfTasks(5); + module = module.setNumOfCompletedTasks(2); + + assertEquals("2 / 5 task(s) completed", module.generateProgressMessage()); + } + + @Test + public void testGetPercentageCompleted() { + ModuleCode moduleCode = new ModuleCode("CS2105"); + ModuleName moduleName = new ModuleName("Networking"); + ModuleCredit moduleCredit = new ModuleCredit(4); + Module module = new Module(moduleCode, moduleName, moduleCredit); + module = module.setTotalNumOfTasks(5).setNumOfCompletedTasks(2); + + //Checks if percentage completed is correct + assertEquals(0.4, module.getPercentageCompleted()); + } + + @Test + public void testHasTasks() { + ModuleCode moduleCode = new ModuleCode("CS2103"); + ModuleName moduleName = new ModuleName("Software engineering"); + ModuleCredit moduleCredit = new ModuleCredit(6); + + Module module = new Module(moduleCode, moduleName, moduleCredit); + module = module.setTotalNumOfTasks(0); + + //Module has no tasks + assertFalse(module.hasTasks()); + + //Module has tasks + module = module.setTotalNumOfTasks(5); + assertTrue(module.hasTasks()); + } + + @Test + public void testToString() { + Module module = new ModuleBuilder().withModuleCode("CS2103T").build(); + assertEquals("CS2103T", module.toString()); + } + +} diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java deleted file mode 100644 index dcd3be87b3a..00000000000 --- a/src/test/java/seedu/address/model/person/AddressTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class AddressTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Address(null)); - } - - @Test - public void constructor_invalidAddress_throwsIllegalArgumentException() { - String invalidAddress = ""; - assertThrows(IllegalArgumentException.class, () -> new Address(invalidAddress)); - } - - @Test - public void isValidAddress() { - // null address - assertThrows(NullPointerException.class, () -> Address.isValidAddress(null)); - - // invalid addresses - assertFalse(Address.isValidAddress("")); // empty string - assertFalse(Address.isValidAddress(" ")); // spaces only - - // valid addresses - assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355")); - assertTrue(Address.isValidAddress("-")); // one character - assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address - } -} diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/person/EmailTest.java deleted file mode 100644 index bbcc6c8c98e..00000000000 --- a/src/test/java/seedu/address/model/person/EmailTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class EmailTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Email(null)); - } - - @Test - public void constructor_invalidEmail_throwsIllegalArgumentException() { - String invalidEmail = ""; - assertThrows(IllegalArgumentException.class, () -> new Email(invalidEmail)); - } - - @Test - public void isValidEmail() { - // null email - assertThrows(NullPointerException.class, () -> Email.isValidEmail(null)); - - // blank email - assertFalse(Email.isValidEmail("")); // empty string - assertFalse(Email.isValidEmail(" ")); // spaces only - - // missing parts - assertFalse(Email.isValidEmail("@example.com")); // missing local part - assertFalse(Email.isValidEmail("peterjackexample.com")); // missing '@' symbol - assertFalse(Email.isValidEmail("peterjack@")); // missing domain name - - // invalid parts - assertFalse(Email.isValidEmail("peterjack@-")); // invalid domain name - assertFalse(Email.isValidEmail("peterjack@exam_ple.com")); // underscore in domain name - assertFalse(Email.isValidEmail("peter jack@example.com")); // spaces in local part - assertFalse(Email.isValidEmail("peterjack@exam ple.com")); // spaces in domain name - assertFalse(Email.isValidEmail(" peterjack@example.com")); // leading space - assertFalse(Email.isValidEmail("peterjack@example.com ")); // trailing space - assertFalse(Email.isValidEmail("peterjack@@example.com")); // double '@' symbol - assertFalse(Email.isValidEmail("peter@jack@example.com")); // '@' symbol in local part - assertFalse(Email.isValidEmail("-peterjack@example.com")); // local part starts with a hyphen - assertFalse(Email.isValidEmail("peterjack-@example.com")); // local part ends with a hyphen - assertFalse(Email.isValidEmail("peter..jack@example.com")); // local part has two consecutive periods - assertFalse(Email.isValidEmail("peterjack@example@com")); // '@' symbol in domain name - assertFalse(Email.isValidEmail("peterjack@.example.com")); // domain name starts with a period - assertFalse(Email.isValidEmail("peterjack@example.com.")); // domain name ends with a period - assertFalse(Email.isValidEmail("peterjack@-example.com")); // domain name starts with a hyphen - assertFalse(Email.isValidEmail("peterjack@example.com-")); // domain name ends with a hyphen - assertFalse(Email.isValidEmail("peterjack@example.c")); // top level domain has less than two chars - - // valid email - assertTrue(Email.isValidEmail("PeterJack_1190@example.com")); // underscore in local part - assertTrue(Email.isValidEmail("PeterJack.1190@example.com")); // period in local part - assertTrue(Email.isValidEmail("PeterJack+1190@example.com")); // '+' symbol in local part - assertTrue(Email.isValidEmail("PeterJack-1190@example.com")); // hyphen in local part - assertTrue(Email.isValidEmail("a@bc")); // minimal - assertTrue(Email.isValidEmail("test@localhost")); // alphabets only - assertTrue(Email.isValidEmail("123@145")); // numeric local part and domain name - assertTrue(Email.isValidEmail("a1+be.d@example1.com")); // mixture of alphanumeric and special characters - assertTrue(Email.isValidEmail("peter_jack@very-very-very-long-example.com")); // long domain name - assertTrue(Email.isValidEmail("if.you.dream.it_you.can.do.it@example.com")); // long local part - assertTrue(Email.isValidEmail("e1234567@u.nus.edu")); // more than one period in domain - } -} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java deleted file mode 100644 index f136664e017..00000000000 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import seedu.address.testutil.PersonBuilder; - -public class NameContainsKeywordsPredicateTest { - - @Test - public void equals() { - List firstPredicateKeywordList = Collections.singletonList("first"); - List secondPredicateKeywordList = Arrays.asList("first", "second"); - - NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList); - - // same object -> returns true - assertTrue(firstPredicate.equals(firstPredicate)); - - // same values -> returns true - NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - assertTrue(firstPredicate.equals(firstPredicateCopy)); - - // different types -> returns false - assertFalse(firstPredicate.equals(1)); - - // null -> returns false - assertFalse(firstPredicate.equals(null)); - - // different person -> returns false - assertFalse(firstPredicate.equals(secondPredicate)); - } - - @Test - public void test_nameContainsKeywords_returnsTrue() { - // One keyword - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Multiple keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); - - // Mixed-case keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - } - - @Test - public void test_nameDoesNotContainKeywords_returnsFalse() { - // Zero keywords - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); - - // Non-matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Keywords match phone, email and address, but does not match name - predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") - .withEmail("alice@email.com").withAddress("Main Street").build())); - } -} diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/person/NameTest.java deleted file mode 100644 index c9801392874..00000000000 --- a/src/test/java/seedu/address/model/person/NameTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class NameTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Name(null)); - } - - @Test - public void constructor_invalidName_throwsIllegalArgumentException() { - String invalidName = ""; - assertThrows(IllegalArgumentException.class, () -> new Name(invalidName)); - } - - @Test - public void isValidName() { - // null name - assertThrows(NullPointerException.class, () -> Name.isValidName(null)); - - // invalid name - assertFalse(Name.isValidName("")); // empty string - assertFalse(Name.isValidName(" ")); // spaces only - assertFalse(Name.isValidName("^")); // only non-alphanumeric characters - assertFalse(Name.isValidName("peter*")); // contains non-alphanumeric characters - - // valid name - assertTrue(Name.isValidName("peter jack")); // alphabets only - assertTrue(Name.isValidName("12345")); // numbers only - assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters - assertTrue(Name.isValidName("Capital Tan")); // with capital letters - assertTrue(Name.isValidName("David Roger Jackson Ray Jr 2nd")); // long names - } -} diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java deleted file mode 100644 index b29c097cfd4..00000000000 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; - -import org.junit.jupiter.api.Test; - -import seedu.address.testutil.PersonBuilder; - -public class PersonTest { - - @Test - public void asObservableList_modifyList_throwsUnsupportedOperationException() { - Person person = new PersonBuilder().build(); - assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0)); - } - - @Test - public void isSamePerson() { - // same object -> returns true - assertTrue(ALICE.isSamePerson(ALICE)); - - // null -> returns false - assertFalse(ALICE.isSamePerson(null)); - - // same name, all other attributes different -> returns true - Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); - assertTrue(ALICE.isSamePerson(editedAlice)); - - // different name, all other attributes same -> returns false - editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); - assertFalse(ALICE.isSamePerson(editedAlice)); - - // name differs in case, all other attributes same -> returns false - Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); - assertFalse(BOB.isSamePerson(editedBob)); - - // name has trailing spaces, all other attributes same -> returns false - String nameWithTrailingSpaces = VALID_NAME_BOB + " "; - editedBob = new PersonBuilder(BOB).withName(nameWithTrailingSpaces).build(); - assertFalse(BOB.isSamePerson(editedBob)); - } - - @Test - public void equals() { - // same values -> returns true - Person aliceCopy = new PersonBuilder(ALICE).build(); - assertTrue(ALICE.equals(aliceCopy)); - - // same object -> returns true - assertTrue(ALICE.equals(ALICE)); - - // null -> returns false - assertFalse(ALICE.equals(null)); - - // different type -> returns false - assertFalse(ALICE.equals(5)); - - // different person -> returns false - assertFalse(ALICE.equals(BOB)); - - // different name -> returns false - Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different phone -> returns false - editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different email -> returns false - editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different address -> returns false - editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different tags -> returns false - editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); - assertFalse(ALICE.equals(editedAlice)); - } -} diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/person/PhoneTest.java deleted file mode 100644 index 8dd52766a5f..00000000000 --- a/src/test/java/seedu/address/model/person/PhoneTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class PhoneTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Phone(null)); - } - - @Test - public void constructor_invalidPhone_throwsIllegalArgumentException() { - String invalidPhone = ""; - assertThrows(IllegalArgumentException.class, () -> new Phone(invalidPhone)); - } - - @Test - public void isValidPhone() { - // null phone number - assertThrows(NullPointerException.class, () -> Phone.isValidPhone(null)); - - // invalid phone numbers - assertFalse(Phone.isValidPhone("")); // empty string - assertFalse(Phone.isValidPhone(" ")); // spaces only - assertFalse(Phone.isValidPhone("91")); // less than 3 numbers - assertFalse(Phone.isValidPhone("phone")); // non-numeric - assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits - assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits - - // valid phone numbers - assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers - assertTrue(Phone.isValidPhone("93121534")); - assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers - } -} diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java deleted file mode 100644 index 1cc5fe9e0fe..00000000000 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.testutil.PersonBuilder; - -public class UniquePersonListTest { - - private final UniquePersonList uniquePersonList = new UniquePersonList(); - - @Test - public void contains_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.contains(null)); - } - - @Test - public void contains_personNotInList_returnsFalse() { - assertFalse(uniquePersonList.contains(ALICE)); - } - - @Test - public void contains_personInList_returnsTrue() { - uniquePersonList.add(ALICE); - assertTrue(uniquePersonList.contains(ALICE)); - } - - @Test - public void contains_personWithSameIdentityFieldsInList_returnsTrue() { - uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(uniquePersonList.contains(editedAlice)); - } - - @Test - public void add_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.add(null)); - } - - @Test - public void add_duplicatePerson_throwsDuplicatePersonException() { - uniquePersonList.add(ALICE); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.add(ALICE)); - } - - @Test - public void setPerson_nullTargetPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(null, ALICE)); - } - - @Test - public void setPerson_nullEditedPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(ALICE, null)); - } - - @Test - public void setPerson_targetPersonNotInList_throwsPersonNotFoundException() { - assertThrows(PersonNotFoundException.class, () -> uniquePersonList.setPerson(ALICE, ALICE)); - } - - @Test - public void setPerson_editedPersonIsSamePerson_success() { - uniquePersonList.add(ALICE); - uniquePersonList.setPerson(ALICE, ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(ALICE); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPerson_editedPersonHasSameIdentity_success() { - uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - uniquePersonList.setPerson(ALICE, editedAlice); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(editedAlice); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPerson_editedPersonHasDifferentIdentity_success() { - uniquePersonList.add(ALICE); - uniquePersonList.setPerson(ALICE, BOB); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPerson_editedPersonHasNonUniqueIdentity_throwsDuplicatePersonException() { - uniquePersonList.add(ALICE); - uniquePersonList.add(BOB); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPerson(ALICE, BOB)); - } - - @Test - public void remove_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.remove(null)); - } - - @Test - public void remove_personDoesNotExist_throwsPersonNotFoundException() { - assertThrows(PersonNotFoundException.class, () -> uniquePersonList.remove(ALICE)); - } - - @Test - public void remove_existingPerson_removesPerson() { - uniquePersonList.add(ALICE); - uniquePersonList.remove(ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPersons_nullUniquePersonList_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((UniquePersonList) null)); - } - - @Test - public void setPersons_uniquePersonList_replacesOwnListWithProvidedUniquePersonList() { - uniquePersonList.add(ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - uniquePersonList.setPersons(expectedUniquePersonList); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPersons_nullList_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((List) null)); - } - - @Test - public void setPersons_list_replacesOwnListWithProvidedList() { - uniquePersonList.add(ALICE); - List personList = Collections.singletonList(BOB); - uniquePersonList.setPersons(personList); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPersons_listWithDuplicatePersons_throwsDuplicatePersonException() { - List listWithDuplicatePersons = Arrays.asList(ALICE, ALICE); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPersons(listWithDuplicatePersons)); - } - - @Test - public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () - -> uniquePersonList.asUnmodifiableObservableList().remove(0)); - } -} diff --git a/src/test/java/seedu/address/model/tag/DeadlineTagTest.java b/src/test/java/seedu/address/model/tag/DeadlineTagTest.java new file mode 100644 index 00000000000..747e43a9346 --- /dev/null +++ b/src/test/java/seedu/address/model/tag/DeadlineTagTest.java @@ -0,0 +1,113 @@ +package seedu.address.model.tag; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.DeadlineTagBuilder; + +/** + * Unit testing Deadline tag. + */ +public class DeadlineTagTest { + + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-uuuu"); + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new DeadlineTag(null)); + } + + @Test + public void testCheckDateFormat() { + //Normal valid input for date + assertTrue(DeadlineTag.checkDateFormat("31-12-2022")); + + //Invalid length for deadline(Length is less than 10 characters long) + assertFalse(DeadlineTag.checkDateFormat("31-12-22")); + + //Invalid length for deadline(Length is more than 10 character long) + assertFalse(DeadlineTag.checkDateFormat("031-012-2022")); + + //Using invalid character(alphabets) + assertFalse(DeadlineTag.checkDateFormat("th-no-2022")); + + //Using invalid number for date for deadline(In this case there is no month with 32nd day) + assertFalse(DeadlineTag.checkDateFormat("32-01-2022")); + + //Using invalid number for month for deadline + assertFalse(DeadlineTag.checkDateFormat("10-13-2022")); + + } + + @Test + public void testIsValidDeadline() { + + //Normal valid input for date + assertTrue(DeadlineTag.isValidDeadline(LocalDate.parse("12-12-2022", dateTimeFormatter))); + + //Null value for date + assertFalse(DeadlineTag.isValidDeadline(null)); + + //Invalid date (Date in the past) + assertFalse(DeadlineTag.isValidDeadline(LocalDate.parse("01-01-2022", dateTimeFormatter))); + } + + @Test + public void testEquals() { + DeadlineTag decemberDeadlineTag = new DeadlineTagBuilder().withDeadline(LocalDate.parse("13-12-2022", + dateTimeFormatter)).build(); + DeadlineTag januaryDeadlineTag = new DeadlineTagBuilder().withDeadline(LocalDate + .parse("01-01-2023", dateTimeFormatter)).build(); + + //Creates a copy of the deadline tag + DeadlineTag decemberDeadlineTagCopy = new DeadlineTagBuilder(decemberDeadlineTag).build(); + + //Checks whether the deadline tag is same as itself + assertTrue(decemberDeadlineTag.equals(decemberDeadlineTag)); + + //Checks whether the deadline tag is same as another deadline tag object with same dates + assertTrue(decemberDeadlineTag.equals(decemberDeadlineTagCopy)); + + //Checks whether the deadline tag is different as null + assertFalse(decemberDeadlineTag.equals(null)); + + //Checks whether the deadline tag is different from deadline tag with different date + assertFalse(decemberDeadlineTag.equals(januaryDeadlineTag)); + + //Checks whether deadline tag is equal with different object type + assertFalse(decemberDeadlineTag.equals(15)); + } + + @Test + public void testToString() { + DeadlineTag deadlineTag = new DeadlineTag(LocalDate.parse("16-12-2022", dateTimeFormatter)); + assertEquals("16-12-2022", deadlineTag.toString()); + } + + @Test + public void testCompareTo() { + DeadlineTag novemberDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("16-11-2022", dateTimeFormatter)).build(); + DeadlineTag novemberDeadlineTagCopy = new DeadlineTagBuilder(novemberDeadlineTag).build(); + DeadlineTag decemberDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("16-12-2022", dateTimeFormatter)).build(); + DeadlineTag octoberDeadlineTag = new DeadlineTagBuilder() + .withDeadline(LocalDate.parse("16-10-2022", dateTimeFormatter)).build(); + + //Same date + assertEquals(0, novemberDeadlineTag.compareTo(novemberDeadlineTagCopy)); + + //Date in the future + assertEquals(-1, novemberDeadlineTag.compareTo(decemberDeadlineTag)); + + //Date in the past + assertEquals(1, novemberDeadlineTag.compareTo(octoberDeadlineTag)); + + } +} diff --git a/src/test/java/seedu/address/model/tag/PriorityTagTest.java b/src/test/java/seedu/address/model/tag/PriorityTagTest.java new file mode 100644 index 00000000000..87a30106e77 --- /dev/null +++ b/src/test/java/seedu/address/model/tag/PriorityTagTest.java @@ -0,0 +1,115 @@ +package seedu.address.model.tag; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PriorityTagBuilder; + +public class PriorityTagTest { + + private final PriorityTag highPriorityTag = new PriorityTagBuilder().withStatus("HIGH").build(); + private final PriorityTag lowPriorityTag = new PriorityTagBuilder().withStatus("LOW").build(); + private final PriorityTag mediumPriorityTag = new PriorityTagBuilder().withStatus("MEDIUM").build(); + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new PriorityTag(null)); + } + + @Test + public void constructor_invalidStatus_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new PriorityTag("PRIORITY")); + } + + @Test + public void testIsValidTag() { + + //Valid input(LOW) + assertTrue(PriorityTag.isValidTag("LOW")); + + //Valid input(HIGH) + assertTrue(PriorityTag.isValidTag("HIGH")); + + //Valid input(MEDIUM) + assertTrue(PriorityTag.isValidTag("MEDIUM")); + + //Another valid input(lOw) - checking if support case-insensitive status + assertTrue(PriorityTag.isValidTag("lOw")); + + //Null input + assertFalse(PriorityTag.isValidTag(null)); + + //Different word given for status + assertFalse(PriorityTag.isValidTag("Hello")); + + } + + @Test + public void testCompareTo() { + PriorityTag highPriorityTagCopy = new PriorityTagBuilder(highPriorityTag).build(); + PriorityTag mediumPriorityTagCopy = new PriorityTagBuilder(mediumPriorityTag).build(); + PriorityTag lowPriorityTagCopy = new PriorityTagBuilder(lowPriorityTag).build(); + + //Comparing high priority tag with another high priority tag + assertEquals(0, highPriorityTag.compareTo(highPriorityTagCopy)); + + //Comparing high priority tag with low priority tag + assertEquals(-1, highPriorityTag.compareTo(lowPriorityTag)); + + //Comparing high priority tag with medium priority tag + assertEquals(-1, highPriorityTag.compareTo(mediumPriorityTag)); + + //Comparing medium priority tag with high priority tag + assertEquals(1, mediumPriorityTag.compareTo(highPriorityTag)); + + //Comparing medium priority tag with another medium priority tag + assertEquals(0, mediumPriorityTag.compareTo(mediumPriorityTagCopy)); + + //Comparing medium priority tag with low priority tag + assertEquals(-1, mediumPriorityTag.compareTo(lowPriorityTag)); + + //Comparing low priority tag with high priority tag + assertEquals(1, lowPriorityTag.compareTo(highPriorityTag)); + + //Comparing low priority tag with medium priority tag + assertEquals(1, lowPriorityTag.compareTo(mediumPriorityTag)); + + //Comparing low priority tag with another low priority tag + assertEquals(0, lowPriorityTag.compareTo(lowPriorityTagCopy)); + } + + @Test + public void testEquals() { + PriorityTag highPriorityTagCopy = new PriorityTagBuilder(highPriorityTag).build(); + PriorityTag highPriorityTagDiffSpelling = new PriorityTagBuilder(highPriorityTag) + .withStatus("hIgH").build(); + + //Checks if equal with itself + assertTrue(highPriorityTag.equals(highPriorityTag)); + + //Checks if equal with another priority tag with same status and spelling + assertTrue(highPriorityTag.equals(highPriorityTagCopy)); + + //Checks if equal with another priority tag with same status but different spelling + assertTrue(highPriorityTag.equals(highPriorityTagDiffSpelling)); + + //Checks if equal with null + assertFalse(highPriorityTag.equals(null)); + + //Checks if equal with different status + assertFalse(highPriorityTag.equals(lowPriorityTag)); + assertFalse(highPriorityTag.equals(mediumPriorityTag)); + + //Checks if equal with different object type + assertFalse(highPriorityTag.equals(79)); + } + + @Test + public void testToString() { + assertEquals("HIGH", highPriorityTag.toString()); + } +} diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java deleted file mode 100644 index 64d07d79ee2..00000000000 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.model.tag; - -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class TagTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Tag(null)); - } - - @Test - public void constructor_invalidTagName_throwsIllegalArgumentException() { - String invalidTagName = ""; - assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName)); - } - - @Test - public void isValidTagName() { - // null tag name - assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); - } - -} diff --git a/src/test/java/seedu/address/model/task/DescriptionContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/task/DescriptionContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..24506955cf5 --- /dev/null +++ b/src/test/java/seedu/address/model/task/DescriptionContainsKeywordsPredicateTest.java @@ -0,0 +1,53 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.TaskBuilder; + +public class DescriptionContainsKeywordsPredicateTest { + + + @Test + public void test_descriptionContainsKeywords_returnsTrue() { + // One keyword + DescriptionContainsKeywordsPredicate predicate = + new DescriptionContainsKeywordsPredicate(Collections.singletonList("task")); + assertTrue(predicate.test(new TaskBuilder().withTaskDescription("task").build())); + + //One keyword mixed case + predicate = new DescriptionContainsKeywordsPredicate(Collections.singletonList("task")); + assertTrue(predicate.test(new TaskBuilder().withTaskDescription("TAsK").build())); + + // Multiple keywords + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("task", "one")); + assertTrue(predicate.test(new TaskBuilder().withTaskDescription("TasK ONe").build())); + + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("task", "one")); + assertTrue(predicate.test(new TaskBuilder().withTaskDescription("TasK Two Task ONe").build())); + + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("as")); + assertTrue(predicate.test(new TaskBuilder().withTaskDescription("TasK Two Task ONe").build())); + + } + + @Test + public void test_nameDoesNotContainKeywords_returnsFalse() { + // Zero keywords + DescriptionContainsKeywordsPredicate predicate = + new DescriptionContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TaskBuilder() + .withTaskDescription("Task one").build())); + + // Non-matching keyword + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("task")); + assertFalse(predicate.test(new TaskBuilder() + .withTaskDescription("homework paper").build())); + + } +} diff --git a/src/test/java/seedu/address/model/task/DistinctTaskListTest.java b/src/test/java/seedu/address/model/task/DistinctTaskListTest.java new file mode 100644 index 00000000000..6a4dbab4848 --- /dev/null +++ b/src/test/java/seedu/address/model/task/DistinctTaskListTest.java @@ -0,0 +1,419 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalExams.FINAL_EXAM; +import static seedu.address.testutil.TypicalExams.MIDTERM_EXAM; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalModules.CS2103T; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_B; +import static seedu.address.testutil.TypicalTasks.TASK_C; +import static seedu.address.testutil.TypicalTasks.TASK_D; +import static seedu.address.testutil.TypicalTasks.TASK_H; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Criteria; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; +import seedu.address.model.task.exceptions.WrongTaskModifiedException; +import seedu.address.testutil.ExamBuilder; +import seedu.address.testutil.ModuleBuilder; +import seedu.address.testutil.TaskBuilder; + +public class DistinctTaskListTest { + + private final DistinctTaskList distinctTaskList = new DistinctTaskList(); + + @Test + public void contains_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.contains(null)); + } + + @Test + public void contains_taskNotInList_returnsFalse() { + assertFalse(distinctTaskList.contains(TASK_A)); + } + + @Test + public void contains_taskInList_returnsTrue() { + distinctTaskList.addTask(TASK_A); + assertTrue(distinctTaskList.contains(TASK_A)); + } + + @Test + public void contains_taskWithSameIdentityFieldsInList_returnsTrue() { + distinctTaskList.addTask(TASK_A); + Task editedTask = new TaskBuilder(TASK_A).withStatus("complete").build(); + assertTrue(distinctTaskList.contains(editedTask)); + } + + @Test + public void containsModule_nullModule_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.containsModule(null)); + } + + @Test + public void containsModule_moduleNotInList_returnsFalse() { + assertFalse(distinctTaskList.containsModule(CS2030)); + } + + @Test + public void containsModule_moduleInList_returnsTrue() { + distinctTaskList.addTask(TASK_A); + assertTrue(distinctTaskList.containsModule(CS2030)); + } + + @Test + public void addTask_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.addTask(null)); + } + + @Test + public void add_duplicateTask_throwsDuplicatePersonException() { + distinctTaskList.addTask(TASK_A); + assertThrows(DuplicateTaskException.class, () -> distinctTaskList.addTask(TASK_A)); + } + + @Test + public void replaceTask_nullTargetTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList + .replaceTask(null, TASK_A, false)); + } + + @Test + public void replaceTask_nullEditedTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList + .replaceTask(TASK_A, null, false)); + } + + @Test + public void replaceTask_targetTaskNotInList_throwsTaskNotFoundException() { + assertThrows(TaskNotFoundException.class, () -> distinctTaskList.replaceTask(TASK_A, TASK_A, true)); + } + + @Test + public void replaceTask_expectedSameTask_success() { + distinctTaskList.addTask(TASK_A); + distinctTaskList.replaceTask(TASK_A, TASK_A, true); + DistinctTaskList expectedDistinctTaskList = new DistinctTaskList(); + expectedDistinctTaskList.addTask(TASK_A); + assertEquals(expectedDistinctTaskList, distinctTaskList); + } + + @Test + public void replaceTask_expectedSameTaskButIsDifferentTask_throwsWrongTaskModifiedException() { + distinctTaskList.addTask(TASK_A); + assertThrows(WrongTaskModifiedException.class, () -> distinctTaskList + .replaceTask(TASK_A, TASK_B, true)); + } + + @Test + public void replaceTask_expectedDifferentTask_success() { + distinctTaskList.addTask(TASK_A); + distinctTaskList.replaceTask(TASK_A, TASK_B, false); + DistinctTaskList expectedDistinctTaskList = new DistinctTaskList(); + expectedDistinctTaskList.addTask(TASK_B); + assertEquals(expectedDistinctTaskList, distinctTaskList); + } + + @Test + public void replaceTask_expectedDifferentTaskButIsSameTask_throwsDuplicateTaskException() { + // edited task has non-unique identity + distinctTaskList.addTask(TASK_A); + distinctTaskList.addTask(TASK_B); + assertThrows(DuplicateTaskException.class, () -> distinctTaskList.replaceTask(TASK_A, TASK_B, false)); + } + + @Test + public void remove_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.remove(null)); + } + + @Test + public void remove_taskDoesNotExist_throwsTaskNotFoundException() { + assertThrows(TaskNotFoundException.class, () -> distinctTaskList.remove(TASK_A)); + } + + @Test + public void remove_existingTask_success() { + distinctTaskList.addTask(TASK_A); + distinctTaskList.remove(TASK_A); + DistinctTaskList expectedDistinctTaskList = new DistinctTaskList(); + assertEquals(expectedDistinctTaskList, distinctTaskList); + } + + @Test + public void getNumOfCompletedModuleTasks() { + distinctTaskList.addTask(TASK_D); + distinctTaskList.addTask(TASK_C); + distinctTaskList.addTask(TASK_B); + + // null module -> throws null pointer exception + assertThrows(NullPointerException.class, () -> distinctTaskList.getNumOfCompletedModuleTasks(null)); + + // 1 completed task with module + assertEquals(distinctTaskList.getNumOfCompletedModuleTasks(CS2040), 1); + + // adding another completed task linked to the module -> count increases by 1 + Task anotherTask = new TaskBuilder(TASK_D).withTaskDescription("another task").build(); + distinctTaskList.addTask(anotherTask); + assertEquals(distinctTaskList.getNumOfCompletedModuleTasks(CS2040), 2); + + // no completed task with module + assertEquals(distinctTaskList.getNumOfCompletedModuleTasks(CS2030), 0); + + // no tasks with module + assertEquals(distinctTaskList.getNumOfCompletedModuleTasks(CS2103T), 0); + } + + @Test + public void getTotalNumOfModuleTasks() { + distinctTaskList.addTask(TASK_C); + distinctTaskList.addTask(TASK_D); + + // null module -> throws null pointer exception + assertThrows(NullPointerException.class, () -> distinctTaskList.getTotalNumOfModuleTasks(null)); + + // 2 tasks with module + assertEquals(distinctTaskList.getTotalNumOfModuleTasks(CS2040), 2); + + // adding another task with the same module -> count increases by 1 + Task anotherTask = new TaskBuilder(TASK_C).withTaskDescription("another task").build(); + distinctTaskList.addTask(anotherTask); + assertEquals(distinctTaskList.getTotalNumOfModuleTasks(CS2040), 3); + + // no tasks with module + assertEquals(distinctTaskList.getTotalNumOfModuleTasks(CS2103T), 0); + } + + @Test + public void getNumOfCompletedExamTasks() { + + // null exam -> throws null pointer exception + assertThrows(NullPointerException.class, () -> distinctTaskList.getNumOfCompletedExamTasks(null)); + + // no completed task with exam + Task incompleteTask = new TaskBuilder(TASK_D).withTaskDescription("incomplete task") + .withStatus("incomplete").build(); + distinctTaskList.addTask(incompleteTask); + assertEquals(distinctTaskList.getNumOfCompletedExamTasks(MIDTERM_EXAM), 0); + + // 1 completed task with exam + distinctTaskList.addTask(TASK_D); + assertEquals(distinctTaskList.getNumOfCompletedExamTasks(MIDTERM_EXAM), 1); + + // adding another completed task linked to the exam -> count increases by 1 + Task anotherTask = new TaskBuilder(TASK_D).withTaskDescription("another task").build(); + distinctTaskList.addTask(anotherTask); + assertEquals(distinctTaskList.getNumOfCompletedExamTasks(MIDTERM_EXAM), 2); + + // no tasks with exam + assertEquals(distinctTaskList.getNumOfCompletedExamTasks(FINAL_EXAM), 0); + } + + + @Test + public void getTotalNumOfExamTasks() { + distinctTaskList.addTask(TASK_D); + + // null exam -> throws null pointer exception + assertThrows(NullPointerException.class, () -> distinctTaskList.getTotalNumOfExamTasks(null)); + + // 1 task with exam + assertEquals(distinctTaskList.getTotalNumOfExamTasks(MIDTERM_EXAM), 1); + + // adding another task with the same exam -> count increases by 1 + Task anotherTask = new TaskBuilder(TASK_D).withTaskDescription("another task").build(); + distinctTaskList.addTask(anotherTask); + assertEquals(distinctTaskList.getTotalNumOfExamTasks(MIDTERM_EXAM), 2); + + // no tasks with exam + assertEquals(distinctTaskList.getTotalNumOfExamTasks(FINAL_EXAM), 0); + } + + @Test + public void deleteTasksWithModule_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.deleteTasksWithModule(null)); + } + + @Test + public void deleteTasksWithModule_hasTasksWithModule_success() { + distinctTaskList.addTask(TASK_A); + distinctTaskList.addTask(TASK_C); + distinctTaskList.deleteTasksWithModule(CS2030); + DistinctTaskList expectedList = new DistinctTaskList(); + expectedList.addTask(TASK_C); + assertEquals(distinctTaskList, expectedList); + } + + @Test + public void deleteTasksWithModule_noTasksWithModule_success() { + distinctTaskList.addTask(TASK_A); + distinctTaskList.deleteTasksWithModule(CS2040); + DistinctTaskList expectedList = new DistinctTaskList(); + expectedList.addTask(TASK_A); + assertEquals(distinctTaskList, expectedList); + } + + @Test + public void unlinkTasksFromExam_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.unlinkTasksFromExam(null)); + } + + @Test // mght need to edit since method was changed + public void unlinkTasksFromExam_success() { + distinctTaskList.addTask(TASK_D); + distinctTaskList.unlinkTasksFromExam(MIDTERM_EXAM); + assertFalse(distinctTaskList.taskList.get(0).isLinked()); + } + + @Test + public void isExamLinkedToTask() { + // null + assertThrows(NullPointerException.class, () -> distinctTaskList.isExamLinkedToTask(null)); + + // empty list + assertFalse(distinctTaskList.isExamLinkedToTask(FINAL_EXAM)); + + // no tasks linked to exam + distinctTaskList.addTask(TASK_D); + assertFalse(distinctTaskList.isExamLinkedToTask(FINAL_EXAM)); + + // 1 task linked to exam + assertTrue(distinctTaskList.isExamLinkedToTask(MIDTERM_EXAM)); + } + + @Test + public void sortTasks_byPriority() { + Criteria priority = new Criteria("priority"); + distinctTaskList.addTask(TASK_B); // task with low priority + distinctTaskList.addTask(TASK_D); // task with high priority + DistinctTaskList expectedList = new DistinctTaskList(); + expectedList.setTasks(new ArrayList<>(Arrays.asList(TASK_D, TASK_B))); + distinctTaskList.sortTasks(priority); + assertEquals(expectedList, distinctTaskList); + } + + @Test + public void sortTasks_byDeadline() { + Criteria deadline = new Criteria("deadline"); + distinctTaskList.addTask(TASK_H); // task with a further deadline + distinctTaskList.addTask(TASK_D); // task with a closer deadline + DistinctTaskList expectedList = new DistinctTaskList(); + expectedList.setTasks(new ArrayList<>(Arrays.asList(TASK_D, TASK_H))); + distinctTaskList.sortTasks(deadline); + assertEquals(expectedList, distinctTaskList); + } + + @Test + public void sortTasks_byModule() { + Criteria module = new Criteria("module"); + distinctTaskList.addTask(TASK_H); + distinctTaskList.addTask(TASK_D); + distinctTaskList.addTask(TASK_A); + DistinctTaskList expectedList = new DistinctTaskList(); + expectedList.setTasks(new ArrayList<>(Arrays.asList(TASK_A, TASK_D, TASK_H))); + distinctTaskList.sortTasks(module); + assertEquals(expectedList, distinctTaskList); + } + + @Test + public void sortTasks_byDescription() { + Criteria description = new Criteria("description"); + distinctTaskList.addTask(TASK_H); + distinctTaskList.addTask(TASK_D); + distinctTaskList.addTask(TASK_A); + DistinctTaskList expectedList = new DistinctTaskList(); + expectedList.setTasks(new ArrayList<>(Arrays.asList(TASK_A, TASK_D, TASK_H))); + distinctTaskList.sortTasks(description); + assertEquals(expectedList, distinctTaskList); + } + + @Test + public void updateExamFieldForTask_null_throwsNullPointerException() { + // null previousExam + assertThrows(NullPointerException.class, () -> + distinctTaskList.updateExamFieldForTask(null, MIDTERM_EXAM)); + + // null newExam + assertThrows(NullPointerException.class, () -> + distinctTaskList.updateExamFieldForTask(MIDTERM_EXAM, null)); + } + + @Test + public void updateExamFieldForTask_success() { + // task linked to previousExam + Task taskLinkedToPreviousExam = new TaskBuilder(TASK_A).withExam(FINAL_EXAM).build(); + distinctTaskList.addTask(taskLinkedToPreviousExam); + + // task linked to a different exam + Task taskLinkedToDifferentExam = new TaskBuilder(TASK_C).withExam(MIDTERM_EXAM).build(); + distinctTaskList.addTask(taskLinkedToDifferentExam); + + // task not linked + distinctTaskList.addTask(TASK_B); + + Exam editedExam = new ExamBuilder(FINAL_EXAM).withDescription("edited name").build(); + distinctTaskList.updateExamFieldForTask(FINAL_EXAM, editedExam); + assertEquals(distinctTaskList.taskList.get(0).getExam(), editedExam); + assertNotEquals(distinctTaskList.taskList.get(1).getExam(), editedExam); + assertNotEquals(distinctTaskList.taskList.get(2).getExam(), editedExam); + } + + @Test + public void updateModuleFieldForTask_null_throwsNullPointerException() { + // null previousModule + assertThrows(NullPointerException.class, () -> distinctTaskList.updateModuleFieldForTask(null, CS2030)); + + // null newModule + assertThrows(NullPointerException.class, () -> distinctTaskList.updateModuleFieldForTask(CS2030, null)); + } + + @Test // might need to edit since method was changed + public void updateModuleFieldForTask_success() { + // task with same module + distinctTaskList.addTask(TASK_A); + + // task with different module + distinctTaskList.addTask(TASK_C); + + Module editedModule = new ModuleBuilder(CS2030).withModuleName("edited name").build(); + distinctTaskList.updateModuleFieldForTask(CS2030, editedModule); + assertTrue(distinctTaskList.taskList.get(0).getModule().hasAllSameFields(editedModule)); + assertFalse(distinctTaskList.taskList.get(1).getModule().hasAllSameFields(editedModule)); + } + + @Test + public void setTasks_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> distinctTaskList.setTasks(null)); + } + + @Test + public void setTasks_list_replacesOwnListWithProvidedList() { + distinctTaskList.addTask(TASK_A); + List taskList = Collections.singletonList(TASK_B); + distinctTaskList.setTasks(taskList); + DistinctTaskList expectedDistinctTaskList = new DistinctTaskList(); + expectedDistinctTaskList.addTask(TASK_B); + assertEquals(expectedDistinctTaskList, distinctTaskList); + } + + @Test + public void getUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () + -> distinctTaskList.getUnmodifiableTaskList().remove(0)); + } +} diff --git a/src/test/java/seedu/address/model/task/FilterPredicateTest.java b/src/test/java/seedu/address/model/task/FilterPredicateTest.java new file mode 100644 index 00000000000..a883deaf98e --- /dev/null +++ b/src/test/java/seedu/address/model/task/FilterPredicateTest.java @@ -0,0 +1,101 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalModules.CS2030; +import static seedu.address.testutil.TypicalModules.CS2040; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_D; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +public class FilterPredicateTest { + @Test + public void test_taskFulfilsConditions_returnsTrue() { + // Correct module + FilterPredicate predicate = new FilterPredicate(Optional.of(CS2030), Optional.empty(), + Optional.empty()); + assertTrue(predicate.test(TASK_A)); + + // Correct completion status + predicate = new FilterPredicate(Optional.empty(), Optional.of(true), Optional.empty()); + assertTrue(predicate.test(TASK_D)); + + // Correct link status + predicate = new FilterPredicate(Optional.empty(), Optional.empty(), Optional.of(true)); + assertTrue(predicate.test(TASK_D)); + + // Correct module, completion status and link status + predicate = new FilterPredicate(Optional.of(CS2030), Optional.of(false), Optional.of(false)); + assertTrue(predicate.test(TASK_A)); + } + + @Test + public void test_taskDoesNotFulfilConditions_returnsFalse() { + // Incorrect module + FilterPredicate predicate = new FilterPredicate(Optional.of(CS2040), Optional.empty(), + Optional.empty()); + assertFalse(predicate.test(TASK_A)); + + // Incorrect completion status + predicate = new FilterPredicate(Optional.empty(), Optional.of(false), Optional.empty()); + assertFalse(predicate.test(TASK_D)); + + // Incorrect link status + predicate = new FilterPredicate(Optional.empty(), Optional.empty(), Optional.of(false)); + assertFalse(predicate.test(TASK_D)); + + // Incorrect module, completion status and link status + predicate = new FilterPredicate(Optional.of(CS2040), Optional.of(true), Optional.of(true)); + assertFalse(predicate.test(TASK_A)); + + // Incorrect module, correct completion status and link status + predicate = new FilterPredicate(Optional.of(CS2040), Optional.of(false), Optional.of(false)); + assertFalse(predicate.test(TASK_A)); + + // Incorrect completion status, correct module and link status + predicate = new FilterPredicate(Optional.of(CS2030), Optional.of(true), Optional.of(false)); + assertFalse(predicate.test(TASK_A)); + + // Incorrect link status, correct module and completion status + predicate = new FilterPredicate(Optional.of(CS2030), Optional.of(false), Optional.of(true)); + assertFalse(predicate.test(TASK_A)); + } + + @Test + public void equals() { + FilterPredicate firstFilterPredicate = new FilterPredicate(Optional.of(CS2030), Optional.of(true), + Optional.empty()); + FilterPredicate secondFilterPredicate = new FilterPredicate(Optional.of(CS2040), Optional.of(true), + Optional.empty()); + FilterPredicate thirdFilterPredicate = new FilterPredicate(Optional.of(CS2030), Optional.of(false), + Optional.empty()); + FilterPredicate fourthFilterPredicate = new FilterPredicate(Optional.of(CS2030), Optional.of(true), + Optional.of(true)); + FilterPredicate firstFilterPredicateCopy = new FilterPredicate(Optional.of(CS2030), Optional.of(true), + Optional.empty()); + + // same object -> returns true + assertTrue(firstFilterPredicate.equals(firstFilterPredicate)); + + // same values -> returns true + assertTrue(firstFilterPredicate.equals(firstFilterPredicateCopy)); + + // different types -> returns false + assertFalse(firstFilterPredicate.equals(1)); + + // null -> returns false + assertFalse(firstFilterPredicate.equals(null)); + + // different module -> returns false + assertFalse(firstFilterPredicate.equals(secondFilterPredicate)); + + // different completion status -> returns false + assertFalse(firstFilterPredicate.equals(thirdFilterPredicate)); + + // different link status -> returns false + assertFalse(firstFilterPredicate.equals(fourthFilterPredicate)); + } +} diff --git a/src/test/java/seedu/address/model/task/TaskDescriptionTest.java b/src/test/java/seedu/address/model/task/TaskDescriptionTest.java new file mode 100644 index 00000000000..388ebbc553a --- /dev/null +++ b/src/test/java/seedu/address/model/task/TaskDescriptionTest.java @@ -0,0 +1,55 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class TaskDescriptionTest { + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new TaskDescription(null)); + } + + @Test + public void constructor_invalidDescription_throwsIllegalArgumentException() { + String invalidDescription = ""; + assertThrows(IllegalArgumentException.class, () -> new TaskDescription(invalidDescription)); + } + + @Test + public void isEqual() { + TaskDescription taskDescriptionA = new TaskDescription("task 1"); + TaskDescription taskDescriptionACopy = new TaskDescription("TASK 1"); + TaskDescription taskDescriptionB = new TaskDescription("task 2"); + + // same object -> returns true + assertTrue(taskDescriptionA.equals(taskDescriptionA)); + + // same value but capitalized -> returns true + assertTrue(taskDescriptionA.equals(taskDescriptionACopy)); + + // different types -> returns false + assertFalse(taskDescriptionA.equals(5)); + + // null -> returns false + assertFalse(taskDescriptionA.equals(null)); + + // different value -> returns false + assertFalse(taskDescriptionA.equals(taskDescriptionB)); + } + + @Test + public void compareTo() { + TaskDescription taskDescriptionA = new TaskDescription("task 1"); + TaskDescription taskDescriptionCopy = new TaskDescription("task 1"); + TaskDescription taskDescriptionB = new TaskDescription("task 2"); + + // same value + assertTrue(taskDescriptionA.compareTo(taskDescriptionCopy) == 0); + + // different values + assertTrue(taskDescriptionA.compareTo(taskDescriptionB) < 0); + } +} diff --git a/src/test/java/seedu/address/model/task/TaskLinkedToExamPredicateTest.java b/src/test/java/seedu/address/model/task/TaskLinkedToExamPredicateTest.java new file mode 100644 index 00000000000..aa58eb2de05 --- /dev/null +++ b/src/test/java/seedu/address/model/task/TaskLinkedToExamPredicateTest.java @@ -0,0 +1,52 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalExams.FINAL_EXAM; +import static seedu.address.testutil.TypicalExams.MIDTERM_EXAM; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_D; + +import org.junit.jupiter.api.Test; + +public class TaskLinkedToExamPredicateTest { + @Test + public void test_taskLinkedToCorrectExam_returnsTrue() { + TaskLinkedToExamPredicate predicate = new TaskLinkedToExamPredicate(MIDTERM_EXAM); + assertTrue(predicate.test(TASK_D)); + } + + @Test + public void test_taskLinkedToWrongExam_returnsFalse() { + TaskLinkedToExamPredicate predicate = new TaskLinkedToExamPredicate(FINAL_EXAM); + assertFalse(predicate.test(TASK_D)); + } + + @Test + public void test_taskUnlinked_returnsFalse() { + TaskLinkedToExamPredicate predicate = new TaskLinkedToExamPredicate(MIDTERM_EXAM); + assertFalse(predicate.test(TASK_A)); + } + + @Test + public void equals() { + TaskLinkedToExamPredicate firstTaskLinkedToExamPredicate = new TaskLinkedToExamPredicate(MIDTERM_EXAM); + TaskLinkedToExamPredicate secondTaskLinkedToExamPredicate = new TaskLinkedToExamPredicate(FINAL_EXAM); + TaskLinkedToExamPredicate firstTaskLinkedToExamPredicateCopy = new TaskLinkedToExamPredicate(MIDTERM_EXAM); + + // same object -> returns true + assertTrue(firstTaskLinkedToExamPredicate.equals(firstTaskLinkedToExamPredicate)); + + // same values -> returns true + assertTrue(firstTaskLinkedToExamPredicate.equals(firstTaskLinkedToExamPredicateCopy)); + + // different types -> returns false + assertFalse(firstTaskLinkedToExamPredicate.equals(1)); + + // null -> returns false + assertFalse(firstTaskLinkedToExamPredicate.equals(null)); + + // different exam + assertFalse(firstTaskLinkedToExamPredicate.equals(secondTaskLinkedToExamPredicate)); + } +} diff --git a/src/test/java/seedu/address/model/task/TaskStatusTest.java b/src/test/java/seedu/address/model/task/TaskStatusTest.java new file mode 100644 index 00000000000..6f66f6dbdc8 --- /dev/null +++ b/src/test/java/seedu/address/model/task/TaskStatusTest.java @@ -0,0 +1,73 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class TaskStatusTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> TaskStatus.of(null)); + } + + @Test + public void constructor_invalidStatus_throwsIllegalArgumentException() { + String invalidStatus = ""; + assertThrows(IllegalArgumentException.class, () -> TaskStatus.of(invalidStatus)); + } + + @Test + public void constructor_validStatus_success() { + assertEquals(TaskStatus.COMPLETE, TaskStatus.of("Complete")); + } + + @Test + public void isComplete() { + assertTrue(TaskStatus.of("ComPlete").isComplete()); + assertFalse(TaskStatus.of("InComplete").isComplete()); + } + + @Test + public void isValidStatus() { + // null status + assertThrows(NullPointerException.class, () -> TaskStatus.isValidStatus(null)); + + // invalid status + assertFalse(TaskStatus.isValidStatus("")); // empty string + assertFalse(TaskStatus.isValidStatus(" ")); // spaces only + assertFalse(TaskStatus.isValidStatus("completed")); // contains an extra character + + // valid status + assertTrue(TaskStatus.isValidStatus("complete")); + assertTrue(TaskStatus.isValidStatus("COMPLETE")); + assertTrue(TaskStatus.isValidStatus("INCOMPLETE")); + assertTrue(TaskStatus.isValidStatus("incomplete")); + assertTrue(TaskStatus.isValidStatus("InComplete")); + } + + @Test + public void equals() { + TaskStatus completeStatus = TaskStatus.of("complete"); + TaskStatus incompleteStatus = TaskStatus.of("incomplete"); + TaskStatus completeStatusCopy = TaskStatus.of("Complete"); + + // same object -> returns true + assertTrue(completeStatus.equals(completeStatus)); + + // same values -> returns true + assertTrue(completeStatus.equals(completeStatusCopy)); + + // different types -> returns false + assertFalse(completeStatus.equals(1)); + + // null -> returns false + assertFalse(completeStatus.equals(null)); + + // different status -> returns false + assertFalse(completeStatus.equals(incompleteStatus)); + } +} diff --git a/src/test/java/seedu/address/model/task/TaskTest.java b/src/test/java/seedu/address/model/task/TaskTest.java new file mode 100644 index 00000000000..c35e3c2db16 --- /dev/null +++ b/src/test/java/seedu/address/model/task/TaskTest.java @@ -0,0 +1,180 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_DO_TUTORIAL; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_CS2030; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.TASK_B; +import static seedu.address.testutil.TypicalTasks.TASK_D; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.tag.exceptions.DeadlineTagAlreadyExistsException; +import seedu.address.model.tag.exceptions.DeadlineTagDoesNotExistException; +import seedu.address.model.tag.exceptions.DeadlineTagUnchangedException; +import seedu.address.model.tag.exceptions.PriorityTagAlreadyExistsException; +import seedu.address.model.tag.exceptions.PriorityTagDoesNotExistException; +import seedu.address.model.tag.exceptions.PriorityTagUnchangedException; +import seedu.address.model.task.exceptions.TaskAlreadyMarkedException; +import seedu.address.model.task.exceptions.TaskAlreadyUnmarkedException; +import seedu.address.testutil.DeadlineTagBuilder; +import seedu.address.testutil.EditTaskDescriptorBuilder; +import seedu.address.testutil.PriorityTagBuilder; +import seedu.address.testutil.TaskBuilder; + +public class TaskTest { + + @Test + public void mark_success() { + Task expectedTask = new TaskBuilder(TASK_A).withStatus("complete").build(); + assertTrue(TASK_A.mark().hasAllSameFields(expectedTask)); + } + + @Test + public void mark_markedTask_throwsTaskAlreadyMarkedException() { + assertThrows(TaskAlreadyMarkedException.class, () -> TASK_D.mark()); + } + + @Test + public void unmark_success() { + Task expectedTask = new TaskBuilder(TASK_D).withStatus("incomplete").build(); + assertTrue(TASK_D.unmark().hasAllSameFields(expectedTask)); + } + + @Test + public void unmark_unmarkedTask_throwsTaskAlreadyUnmarkedException() { + assertThrows(TaskAlreadyUnmarkedException.class, () -> TASK_A.unmark()); + } + + @Test + public void isSameTask_nullTask_returnsFalse() { + Task task = new TaskBuilder().build(); + assertFalse(task.isSameTask(null)); + } + + @Test + public void setPriorityTag_taskAlreadyHasPriorityTag_throwPriorityTagAlreadyExistsException() { + Task task = new TaskBuilder().withPriorityTag(new PriorityTagBuilder() + .build()).build(); + PriorityTag priorityTag = new PriorityTagBuilder().withStatus("LOW").build(); + assertThrows(PriorityTagAlreadyExistsException.class, () -> task.setPriorityTag(priorityTag)); + } + + @Test + public void replacePriorityTag_taskPriorityTagIsUnchanged_throwsPriorityTagUnchangedException() { + Task task = new TaskBuilder().withPriorityTag(new PriorityTagBuilder() + .build()).build(); + PriorityTag priorityTag = new PriorityTagBuilder().build(); + assertThrows(PriorityTagUnchangedException.class, () -> task.replacePriorityTag(priorityTag)); + } + + @Test + public void deletePriorityTag_taskPriorityTagNotPresent_throwsPriorityTagDoesNotExistException() { + Task task = new TaskBuilder().withPriorityTag(null).build(); + assertThrows(PriorityTagDoesNotExistException.class, task::deletePriorityTag); + } + + @Test + public void setDeadlineTag_taskAlreadyHasDeadlineTag_throwDeadlineTagAlreadyExistsException() { + Task task = new TaskBuilder().withDeadlineTag(new DeadlineTagBuilder() + .build()).build(); + LocalDate date = LocalDate.parse("25-12-2023", DateTimeFormatter.ofPattern("dd-MM-yyyy")); + DeadlineTag deadlineTag = new DeadlineTagBuilder().withDeadline(date).build(); + assertThrows(DeadlineTagAlreadyExistsException.class, () -> task.setDeadlineTag(deadlineTag)); + } + + @Test + public void replaceDeadlineTag_taskDeadlineTagIsUnchanged_throwsDeadlineTagUnchangedException() { + Task task = new TaskBuilder().withDeadlineTag(new DeadlineTagBuilder() + .build()).build(); + DeadlineTag deadlineTag = new DeadlineTagBuilder().build(); + assertThrows(DeadlineTagUnchangedException.class, () -> task.replaceDeadlineTag(deadlineTag)); + } + + @Test + public void deleteDeadlineTag_taskDeadlineTagNotPresent_throwsDeadlineTagDoesNotExistException() { + Task task = new TaskBuilder().withDeadlineTag(null).build(); + assertThrows(DeadlineTagDoesNotExistException.class, task::deleteDeadlineTag); + } + + @Test + public void edit_withEditTaskDescriptor() { + + // null descriptor + assertThrows(NullPointerException.class, () -> TASK_A.edit(null)); + + // different module + EditTaskCommand.EditTaskDescriptor descriptorModule = new EditTaskDescriptorBuilder() + .withModule(VALID_MODULE_CS2030).build(); + Task taskDModuleEdited = new TaskBuilder(TASK_D).withModule(VALID_MODULE_CS2030).withExam(null).build(); + assertTrue(TASK_D.edit(descriptorModule).hasAllSameFields(taskDModuleEdited)); + + // different description + EditTaskCommand.EditTaskDescriptor descriptorDescription = new EditTaskDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + Task taskADescriptionEdited = new TaskBuilder(TASK_A) + .withTaskDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + assertTrue(TASK_A.edit(descriptorDescription).hasAllSameFields(taskADescriptionEdited)); + } + + @Test + public void edit_withModuleAndTaskDescriptionWithoutUnlinkingExam() { + // null module and description + assertThrows(NullPointerException.class, () -> TASK_A + .editWithoutUnlinkingExam(null, null)); + + // module edited + Module module = new Module(new ModuleCode(VALID_MODULE_CS2030)); + Task taskDModuleEdited = new TaskBuilder(TASK_D).withModule(VALID_MODULE_CS2030).build(); + assertTrue(TASK_D.editWithoutUnlinkingExam(module, null).hasAllSameFields(taskDModuleEdited)); + + // description edited + TaskDescription description = new TaskDescription(VALID_DESCRIPTION_DO_TUTORIAL); + Task taskADescriptionEdited = new TaskBuilder(TASK_A) + .withTaskDescription(VALID_DESCRIPTION_DO_TUTORIAL).build(); + assertTrue(TASK_A.editWithoutUnlinkingExam(null, description) + .hasAllSameFields(taskADescriptionEdited)); + } + + @Test + public void isEqual() { + Task taskA = TASK_A; + Task taskB = TASK_B; + Task taskD = TASK_D; + + // same object -> returns true + assertTrue(taskA.equals(taskA)); + + // same module and description -> return true + Task taskACopy = new TaskBuilder(TASK_A).build(); + assertTrue(taskA.equals(taskACopy)); + + // same module and description but with other different fields -> return false + Task taskDCopy = new TaskBuilder(TASK_D).withStatus("incomplete").withExam(null).build(); + assertFalse(taskD.equals(taskDCopy)); + + // different types -> returns false + assertFalse(taskA.equals(5)); + + // null -> returns false + assertFalse(taskA.equals(null)); + + // different description -> returns false + assertFalse(taskA.equals(taskB)); + + // different module -> returns false + Task taskAWithDifferentModule = new TaskBuilder(TASK_A).withModule("cs2040").build(); + assertFalse(taskA.equals(taskAWithDifferentModule)); + } + +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedExamTest.java b/src/test/java/seedu/address/storage/JsonAdaptedExamTest.java new file mode 100644 index 00000000000..ac4e12b5c29 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedExamTest.java @@ -0,0 +1,82 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedExam.MISSING_EXAM_DATE; +import static seedu.address.storage.JsonAdaptedExam.MISSING_EXAM_DESCRIPTION; +import static seedu.address.storage.JsonAdaptedExam.MISSING_MODULE_CODE; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalExams.EXAMONE; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.ModuleCode; + +public class JsonAdaptedExamTest { + private static final String INVALID_DATE = "20-20-20"; + private static final String INVALID_EXAM_DESCRIPTION = ""; + private static final String INVALID_MODULE_CODE = "20"; + + private static final String VALID_DATE = EXAMONE.getExamDate().toString(); + private static final String VALID_EXAM_DESCRIPTION = EXAMONE.getDescription().toString(); + private static final String VALID_MODULE_CODE = EXAMONE.getModule().toString(); + + @Test + public void toModelType_validExamDetails_returnsExam() throws IllegalValueException { + JsonAdaptedExam exam = new JsonAdaptedExam(EXAMONE); + assertEquals(EXAMONE, exam.toModelType()); + } + + @Test + public void toModelType_invalidExamDate_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(VALID_EXAM_DESCRIPTION, VALID_MODULE_CODE, INVALID_DATE); + String expectedMessage = ExamDate.DATE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + + @Test + public void toModelType_invalidNonExistingExamDate_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(VALID_EXAM_DESCRIPTION, VALID_MODULE_CODE, "29-02-2023"); + String expectedMessage = ExamDate.DATE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + + @Test + public void toModelType_nullExamDate_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(VALID_EXAM_DESCRIPTION, VALID_MODULE_CODE, null); + String expectedMessage = String.format(MISSING_EXAM_DATE); + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + + @Test + public void toModelType_invalidExamDescription_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(INVALID_EXAM_DESCRIPTION, VALID_MODULE_CODE, VALID_DATE); + String expectedMessage = ExamDescription.DESCRIPTION_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + + @Test + public void toModelType_nullExamDescription_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(null, VALID_MODULE_CODE, VALID_DATE); + String expectedMessage = String.format(MISSING_EXAM_DESCRIPTION); + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + + + @Test + public void toModelType_invalidModule_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(VALID_EXAM_DESCRIPTION, INVALID_MODULE_CODE, VALID_DATE); + String expectedMessage = ModuleCode.MODULE_CODE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + + @Test + public void toModelType_nullModule_throwsIllegalValueException() { + JsonAdaptedExam exam = new JsonAdaptedExam(VALID_EXAM_DESCRIPTION, null, VALID_DATE); + String expectedMessage = String.format(MISSING_MODULE_CODE); + assertThrows(IllegalValueException.class, expectedMessage, exam::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedModuleTest.java b/src/test/java/seedu/address/storage/JsonAdaptedModuleTest.java new file mode 100644 index 00000000000..9fc9d22b359 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedModuleTest.java @@ -0,0 +1,77 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalModules.CS2040; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; + +public class JsonAdaptedModuleTest { + private static final String INVALID_MODULE_CODE = "cs1"; + private static final String INVALID_MODULE_NAME = ""; + private static final String INVALID_MODULE_CREDIT = "-1"; + + private static final String VALID_MODULE_CODE = CS2040.getModuleCode().toString(); + private static final String VALID_MODULE_NAME = CS2040.getModuleName().toString(); + private static final String VALID_MODULE_CREDIT = CS2040.getModuleCredit().toString(); + + @Test + public void toModelType_validModuleDetails_returnsModule() throws IllegalValueException { + JsonAdaptedModule mod = new JsonAdaptedModule(CS2040); + assertEquals(CS2040, mod.toModelType()); + } + + @Test + public void toModelType_invalidModuleCode_throwsIllegalValueException() { + JsonAdaptedModule mod = new JsonAdaptedModule(INVALID_MODULE_CODE, + VALID_MODULE_NAME, VALID_MODULE_CREDIT); + String expectedMessage = ModuleCode.MODULE_CODE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, mod::toModelType); + } + + @Test + public void toModelType_nullModuleCode_throwsIllegalValueException() { + JsonAdaptedModule mod = new JsonAdaptedModule(null, + VALID_MODULE_NAME, VALID_MODULE_CREDIT); + String expectedMessage = JsonAdaptedModule.MISSING_MODULE_MESSAGE_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, mod::toModelType); + } + + @Test + public void toModelType_invalidModuleDescription_throwsIllegalValueException() { + JsonAdaptedModule mod = new JsonAdaptedModule(VALID_MODULE_CODE, + INVALID_MODULE_NAME, VALID_MODULE_CREDIT); + String expectedMessage = ModuleName.MODULE_NAME_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, mod::toModelType); + } + + @Test + public void toModelType_nullModuleDescription_throwsIllegalValueException() { + JsonAdaptedModule mod = new JsonAdaptedModule(VALID_MODULE_CODE, + null, VALID_MODULE_CREDIT); + String expectedMessage = JsonAdaptedModule.MISSING_MODULE_MESSAGE_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, mod::toModelType); + } + + @Test + public void toModelType_invalidModuleCredit_throwsIllegalValueException() { + JsonAdaptedModule module = new JsonAdaptedModule(VALID_MODULE_CODE, + VALID_MODULE_NAME, INVALID_MODULE_CREDIT); + String expectedMessage = ModuleCredit.MODULE_CREDIT_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, module::toModelType); + } + + @Test + public void toModelType_nullModuleCredit_throwsIllegalValueException() { + JsonAdaptedModule mod = new JsonAdaptedModule(VALID_MODULE_CODE, + VALID_MODULE_NAME, null); + String expectedMessage = JsonAdaptedModule.MISSING_MODULE_MESSAGE_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, mod::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java deleted file mode 100644 index 83b11331cdb..00000000000 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package seedu.address.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.BENSON; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; - -public class JsonAdaptedPersonTest { - private static final String INVALID_NAME = "R@chel"; - private static final String INVALID_PHONE = "+651234"; - private static final String INVALID_ADDRESS = " "; - private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; - - private static final String VALID_NAME = BENSON.getName().toString(); - private static final String VALID_PHONE = BENSON.getPhone().toString(); - private static final String VALID_EMAIL = BENSON.getEmail().toString(); - private static final String VALID_ADDRESS = BENSON.getAddress().toString(); - private static final List VALID_TAGS = BENSON.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList()); - - @Test - public void toModelType_validPersonDetails_returnsPerson() throws Exception { - JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON); - assertEquals(BENSON, person.toModelType()); - } - - @Test - public void toModelType_invalidName_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - String expectedMessage = Name.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_nullName_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - String expectedMessage = Phone.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_nullPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - String expectedMessage = Email.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_nullEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); - String expectedMessage = Address.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_nullAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidTags_throwsIllegalValueException() { - List invalidTags = new ArrayList<>(VALID_TAGS); - invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); - assertThrows(IllegalValueException.class, person::toModelType); - } - -} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java new file mode 100644 index 00000000000..48da7f6df84 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java @@ -0,0 +1,160 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.storage.JsonAdaptedTask.MISSING_TASK_DESCRIPTION; +import static seedu.address.storage.JsonAdaptedTask.MISSING_TASK_MODULE; +import static seedu.address.storage.JsonAdaptedTask.MISSING_TASK_STATUS; +import static seedu.address.storage.JsonAdaptedTask.WRONG_EXAM_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalTasks.TASK_D; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskStatus; +import seedu.address.testutil.TaskBuilder; + +public class JsonAdaptedTaskTest { + + private static final String INVALID_MODULE = "12345"; + private static final String INVALID_DESCRIPTION = ""; + private static final String INVALID_STATUS = "incom"; + private static final String INVALID_PRIORITY_TAG = "highlow"; + private static final String INVALID_DEADLINE_TAG = "29 dec 2022"; + private static final String INVALID_EXAM_DATE = "29 dec 2022"; + private static final String INVALID_EXAM_DESCRIPTION = ""; + + private static final String VALID_MODULE = TASK_D.getModule().toString(); + private static final String VALID_DESCRIPTION = TASK_D.getDescription().toString(); + private static final String VALID_STATUS = TASK_D.getStatus().toString(); + private static final String VALID_PRIORITY_TAG = TASK_D.getPriorityTag().toString(); + private static final String VALID_DEADLINE_TAG = TASK_D.getDeadlineTag().toString(); + private static final String VALID_EXAM_DATE = TASK_D.getExam().getExamDate().toString(); + private static final String VALID_EXAM_DESCRIPTION = TASK_D.getExam().getDescription().toString(); + + @Test + public void toModelType_validTaskDetails_returnsTask() throws Exception { + JsonAdaptedTask task = new JsonAdaptedTask(TASK_D); + assertEquals(TASK_D, task.toModelType()); + } + + @Test + public void toModelType_invalidDescription_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(INVALID_DESCRIPTION, INVALID_MODULE, VALID_STATUS, + VALID_PRIORITY_TAG, VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = TaskDescription.DESCRIPTION_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(null, VALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = MISSING_TASK_DESCRIPTION; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidModule_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, INVALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = ModuleCode.MODULE_CODE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullModule_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, null, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = MISSING_TASK_MODULE; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidStatus_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, INVALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = TaskStatus.STATUS_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullStatus_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, null, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = MISSING_TASK_STATUS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidPriorityTag_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, INVALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = PriorityTag.PRIORITY_TAG_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullPriorityTag_returnsTask() throws Exception { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, null, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + Task expectedTask = new TaskBuilder(TASK_D).withPriorityTag(null).build(); + assertTrue(expectedTask.hasAllSameFields(task.toModelType())); + } + + @Test + public void toModelType_invalidDeadlineTag_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + INVALID_DEADLINE_TAG, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDeadlineTag_returnsTask() throws Exception { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, + VALID_PRIORITY_TAG, null, VALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + Task expectedTask = new TaskBuilder(TASK_D).withDeadlineTag(null).build(); + assertTrue(expectedTask.hasAllSameFields(task.toModelType())); + } + + @Test + public void toModelType_invalidExamDate_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, INVALID_EXAM_DATE, VALID_EXAM_DESCRIPTION); + String expectedMessage = ExamDate.DATE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullExamDate_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, null, VALID_EXAM_DESCRIPTION); + String expectedMessage = WRONG_EXAM_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidExamDescription_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, INVALID_EXAM_DESCRIPTION); + String expectedMessage = ExamDescription.DESCRIPTION_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullExamDescription_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_MODULE, VALID_STATUS, VALID_PRIORITY_TAG, + VALID_DEADLINE_TAG, VALID_EXAM_DATE, null); + String expectedMessage = WRONG_EXAM_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java index ac3c3af9566..4dfaf150415 100644 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java @@ -3,10 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalTasks.TASK_A; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; import java.io.IOException; import java.nio.file.Path; @@ -18,6 +16,7 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.testutil.TaskBuilder; public class JsonAddressBookStorageTest { private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonAddressBookStorageTest"); @@ -50,15 +49,23 @@ public void read_notJsonFormat_exceptionThrown() { assertThrows(DataConversionException.class, () -> readAddressBook("notJsonFormatAddressBook.json")); } + + //@@author dlimyy-reused + //Minor modifications from existing AB3 @Test public void readAddressBook_invalidPersonAddressBook_throwDataConversionException() { - assertThrows(DataConversionException.class, () -> readAddressBook("invalidPersonAddressBook.json")); + assertThrows(DataConversionException.class, () -> readAddressBook("invalidModuleAddressBook.json")); } + //@@author + //@@author dlimyy-reused + //Minor modifications from existing AB3 @Test public void readAddressBook_invalidAndValidPersonAddressBook_throwDataConversionException() { - assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidPersonAddressBook.json")); + assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidModuleAddressBook.json")); } + //@@author + @Test public void readAndSaveAddressBook_allInOrder_success() throws Exception { @@ -72,20 +79,23 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception { assertEquals(original, new AddressBook(readBack)); // Modify data, overwrite exiting file, and read back - original.addPerson(HOON); - original.removePerson(ALICE); + original.addTask(new TaskBuilder().withModule("cs2030") + .withTaskDescription("Second Quiz").build()); + original.removeTask(TASK_A); jsonAddressBookStorage.saveAddressBook(original, filePath); readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); assertEquals(original, new AddressBook(readBack)); // Save and read without specifying file path - original.addPerson(IDA); + original.addTask(new TaskBuilder().withModule("cs2030") + .withTaskDescription("Final Quiz").build()); jsonAddressBookStorage.saveAddressBook(original); // file path not specified readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified assertEquals(original, new AddressBook(readBack)); } + @Test public void saveAddressBook_nullAddressBook_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> saveAddressBook(null, "SomeFile.json")); diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java index 188c9058d20..0126ba1dd7d 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java @@ -11,37 +11,95 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.JsonUtil; import seedu.address.model.AddressBook; -import seedu.address.testutil.TypicalPersons; +import seedu.address.testutil.TypicalTasks; public class JsonSerializableAddressBookTest { private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); - private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); - private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); - private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); + private static final Path TYPICAL_TASKS_FILE = TEST_DATA_FOLDER.resolve("typicalTasksAddressBook.json"); + private static final Path DUPLICATE_MODULE_FILE = TEST_DATA_FOLDER.resolve("duplicateModuleAddressBook.json"); + private static final Path INVALID_MODULE_FILE = TEST_DATA_FOLDER.resolve("invalidModuleAddressBook.json"); + private static final Path DUPLICATE_EXAM_FILE = TEST_DATA_FOLDER.resolve("duplicateExamAddressBook.json"); + private static final Path EXAM_INVALID_MODULE_FILE = TEST_DATA_FOLDER + .resolve("examWithInvalidModuleAddressBook.json"); + private static final Path INVALID_EXAM_FILE = TEST_DATA_FOLDER.resolve("invalidExamAddressBook.json"); + private static final Path INVALID_TASK_FILE = TEST_DATA_FOLDER.resolve("invalidTaskAddressBook.json"); + private static final Path DUPLICATE_TASK_FILE = TEST_DATA_FOLDER.resolve("duplicateTaskAddressBook.json"); + private static final Path TASK_INVALID_MODULE_FILE = TEST_DATA_FOLDER + .resolve("taskWithInvalidModuleAddressBook.json"); + private static final Path TASK_INVALID_EXAM_FILE = TEST_DATA_FOLDER + .resolve("taskWithInvalidExamAddressBook.json"); + @Test + public void toModelType_typicalTasksFile_success() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_TASKS_FILE, + JsonSerializableAddressBook.class).get(); + AddressBook addressBookFromDataInFile = dataFromFile.toModelType(); + AddressBook typicalTasksAddressBook = TypicalTasks.getTypicalAddressBook(); + assertEquals(addressBookFromDataInFile, typicalTasksAddressBook); + } + + @Test + public void toModelType_duplicateModuleInFile_success() throws Exception { + JsonSerializableAddressBook dateFromFile = JsonUtil.readJsonFile(DUPLICATE_MODULE_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dateFromFile::toModelType); + + } + + @Test + public void toModelType_invalidModuleFile_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_MODULE_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } @Test - public void toModelType_typicalPersonsFile_success() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, + public void toModelType_duplicateExamFile_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_EXAM_FILE, JsonSerializableAddressBook.class).get(); - AddressBook addressBookFromFile = dataFromFile.toModelType(); - AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); - assertEquals(addressBookFromFile, typicalPersonsAddressBook); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); } @Test - public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, + public void toModelType_examWithInvalidModule_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(EXAM_INVALID_MODULE_FILE, JsonSerializableAddressBook.class).get(); assertThrows(IllegalValueException.class, dataFromFile::toModelType); } @Test - public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, + public void toModelType_invalidExam_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_EXAM_FILE, JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PERSON, - dataFromFile::toModelType); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_invalidTask_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_TASK_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicateTask_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_TASK_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_taskWithInvalidModule_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TASK_INVALID_MODULE_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_taskWithInvalidExam_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TASK_INVALID_EXAM_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); } } diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java index 16f33f4a6bb..5f52847381e 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java @@ -73,7 +73,10 @@ public void readUserPrefs_extraValuesInFile_extraValuesIgnored() throws DataConv private UserPrefs getTypicalUserPrefs() { UserPrefs userPrefs = new UserPrefs(); userPrefs.setGuiSettings(new GuiSettings(1000, 500, 300, 100)); - userPrefs.setAddressBookFilePath(Paths.get("addressbook.json")); + //@@author dlimyy-reused + //Minor modifications from existing AB3 + userPrefs.setAddressBookFilePath(Paths.get("modpro.json")); + //@@author return userPrefs; } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 99a16548970..525440d20d1 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; import java.nio.file.Path; @@ -47,6 +47,7 @@ public void prefsReadSave() throws Exception { assertEquals(original, retrieved); } + @Test public void addressBookReadSave() throws Exception { /* @@ -54,10 +55,12 @@ public void addressBookReadSave() throws Exception { * {@link JsonAddressBookStorage} class. * More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class. */ + AddressBook original = getTypicalAddressBook(); storageManager.saveAddressBook(original); ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); assertEquals(original, new AddressBook(retrieved)); + } @Test diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index d53799fd110..c9a2b0d9e07 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -1,7 +1,8 @@ package seedu.address.testutil; import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.module.Module; +import seedu.address.model.task.Task; /** * A utility class to help with building Addressbook objects. @@ -21,10 +22,18 @@ public AddressBookBuilder(AddressBook addressBook) { } /** - * Adds a new {@code Person} to the {@code AddressBook} that we are building. + * Adds a new {@code Module} to the {@code AddressBook} that we are building. */ - public AddressBookBuilder withPerson(Person person) { - addressBook.addPerson(person); + public AddressBookBuilder withModule(Module module) { + addressBook.addModule(module); + return this; + } + + /** + * Adds a new {@code Task} to the {@code AddressBook} that we are building. + */ + public AddressBookBuilder withTask(Task task) { + addressBook.addTask(task); return this; } diff --git a/src/test/java/seedu/address/testutil/Assert.java b/src/test/java/seedu/address/testutil/Assert.java index 9863093bd6e..e7be95c75c3 100644 --- a/src/test/java/seedu/address/testutil/Assert.java +++ b/src/test/java/seedu/address/testutil/Assert.java @@ -31,4 +31,5 @@ public static void assertThrows(Class expectedType, String Throwable thrownException = Assertions.assertThrows(expectedType, executable); Assertions.assertEquals(expectedMessage, thrownException.getMessage()); } + } diff --git a/src/test/java/seedu/address/testutil/CriteriaBuilder.java b/src/test/java/seedu/address/testutil/CriteriaBuilder.java new file mode 100644 index 00000000000..3bfaa4b4595 --- /dev/null +++ b/src/test/java/seedu/address/testutil/CriteriaBuilder.java @@ -0,0 +1,44 @@ +package seedu.address.testutil; + +import seedu.address.model.Criteria; + +/** + * Builds the {@code Criteria} using the criteria keyword given. + */ +public class CriteriaBuilder { + public static final String DEFAULT_CRITERIA = "module"; + + private String criteria; + + /** + * Initialises the CriteriaBuilder with the default criteria. + */ + public CriteriaBuilder() { + criteria = DEFAULT_CRITERIA; + } + + /** + * Initialises the CriteriaBuilder with the criteria string from + * Criteria object. + * + * @param criteria The Criteria object used. + */ + public CriteriaBuilder(Criteria criteria) { + this.criteria = criteria.getCriteria(); + } + + /** + * Sets the criteria of the CriteriaBuilder with the criteria string given. + * + * @param criteria The criteria string given. + * @return The CriteriaBuilder containing the new criteria. + */ + public CriteriaBuilder withCriteria(String criteria) { + this.criteria = criteria; + return this; + } + + public Criteria build() { + return new Criteria(criteria); + } +} diff --git a/src/test/java/seedu/address/testutil/DeadlineTagBuilder.java b/src/test/java/seedu/address/testutil/DeadlineTagBuilder.java new file mode 100644 index 00000000000..0e173afc737 --- /dev/null +++ b/src/test/java/seedu/address/testutil/DeadlineTagBuilder.java @@ -0,0 +1,48 @@ +package seedu.address.testutil; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import seedu.address.model.tag.DeadlineTag; + +/** + * DeadlineTagBuilder builds a {@code DeadlineTag}. + */ +public class DeadlineTagBuilder { + private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + public static final LocalDate DEFAULT_DATE = LocalDate.parse("31-12-2022", dtf); + private LocalDate deadline; + + + /** + * Initialises the DeadlineTagBuilder with the default date. + */ + public DeadlineTagBuilder() { + deadline = DEFAULT_DATE; + } + + /** + * Initialises the deadline of the DeadlineTagBuilder with the deadline tag given. + * + * @param deadlineTag The deadline tag given. + */ + public DeadlineTagBuilder(DeadlineTag deadlineTag) { + deadline = deadlineTag.deadline; + } + + /** + * Sets the deadline in DeadlineTagBuilder with the given deadline. + * + * @param deadline The deadline provided. + * @return DeadlineTagBuilder containing the new deadline. + */ + public DeadlineTagBuilder withDeadline(LocalDate deadline) { + this.deadline = deadline; + return this; + } + + public DeadlineTag build() { + return new DeadlineTag(deadline); + } + +} diff --git a/src/test/java/seedu/address/testutil/EditExamDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditExamDescriptorBuilder.java new file mode 100644 index 00000000000..2303c69e96e --- /dev/null +++ b/src/test/java/seedu/address/testutil/EditExamDescriptorBuilder.java @@ -0,0 +1,62 @@ +package seedu.address.testutil; + +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; + +/** + * A utility class to help with building EditExamDescriptor objects. + */ +public class EditExamDescriptorBuilder { + + private EditExamCommand.EditExamDescriptor descriptor; + + public EditExamDescriptorBuilder() { + descriptor = new EditExamCommand.EditExamDescriptor(); + } + + public EditExamDescriptorBuilder(EditExamCommand.EditExamDescriptor descriptor) { + this.descriptor = new EditExamCommand.EditExamDescriptor(descriptor); + } + + /** + * Returns an {@code EditExamDescriptor} with fields containing {@code exam}'s details + */ + public EditExamDescriptorBuilder(Exam exam) { + descriptor = new EditExamCommand.EditExamDescriptor(); + descriptor.setModule(exam.getModule()); + descriptor.setExamDate(exam.getExamDate()); + descriptor.setDescription(exam.getDescription()); + } + + /** + * Sets the {@code Description} of the {@code EditExamDescriptor} that we are building. + */ + public EditExamDescriptorBuilder withDescription(String description) { + descriptor.setDescription(new ExamDescription(description)); + return this; + } + + /** + * Sets the {@code Module} of the {@code EditExamDescriptor} that we are building. + */ + public EditExamDescriptorBuilder withModule(String module) { + descriptor.setModule(new Module(new ModuleCode(module))); + return this; + } + + /** + * Sets the {@code ExamDate} of the {@code EditExamDescriptor} that we are building. + */ + public EditExamDescriptorBuilder withDate(String examDate) { + descriptor.setExamDate(new ExamDate(examDate)); + return this; + } + + public EditExamCommand.EditExamDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/address/testutil/EditModuleDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditModuleDescriptorBuilder.java new file mode 100644 index 00000000000..b086a458ecd --- /dev/null +++ b/src/test/java/seedu/address/testutil/EditModuleDescriptorBuilder.java @@ -0,0 +1,61 @@ +package seedu.address.testutil; + +import seedu.address.logic.commands.EditModuleCommand; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; + +/** + * A utility class to help with building EditModuleDescriptor objects. + */ +public class EditModuleDescriptorBuilder { + + private EditModuleCommand.EditModuleDescriptor descriptor; + + public EditModuleDescriptorBuilder() { + descriptor = new EditModuleCommand.EditModuleDescriptor(); + } + + public EditModuleDescriptorBuilder(EditModuleCommand.EditModuleDescriptor descriptor) { + this.descriptor = new EditModuleCommand.EditModuleDescriptor(descriptor); + } + + /** + * Returns an {@code EditModuleDescriptor} with fields containing {@code module}'s details + */ + public EditModuleDescriptorBuilder(Module module) { + descriptor = new EditModuleCommand.EditModuleDescriptor(); + descriptor.setModuleCode(module.getModuleCode()); + descriptor.setModuleName(module.getModuleName()); + descriptor.setModuleCredit(module.getModuleCredit()); + } + + /** + * Sets the {@code moduleCode} of the {@code EditModuleDescriptor} that we are building. + */ + public EditModuleDescriptorBuilder withModuleCode(String moduleCode) { + descriptor.setModuleCode(new ModuleCode(moduleCode)); + return this; + } + + /** + * Sets the {@code moduleName} of the {@code EditModuleDescriptor} that we are building. + */ + public EditModuleDescriptorBuilder withModuleName(String moduleName) { + descriptor.setModuleName(new ModuleName(moduleName)); + return this; + } + + /** + * Sets the {@code moduleCredit} of the {@code EditModuleDescriptor} that we are building. + */ + public EditModuleDescriptorBuilder withModuleCredit(int moduleCredit) { + descriptor.setModuleCredit(new ModuleCredit(moduleCredit)); + return this; + } + + public EditModuleCommand.EditModuleDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java deleted file mode 100644 index 4584bd5044e..00000000000 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.testutil; - -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * A utility class to help with building EditPersonDescriptor objects. - */ -public class EditPersonDescriptorBuilder { - - private EditPersonDescriptor descriptor; - - public EditPersonDescriptorBuilder() { - descriptor = new EditPersonDescriptor(); - } - - public EditPersonDescriptorBuilder(EditPersonDescriptor descriptor) { - this.descriptor = new EditPersonDescriptor(descriptor); - } - - /** - * Returns an {@code EditPersonDescriptor} with fields containing {@code person}'s details - */ - public EditPersonDescriptorBuilder(Person person) { - descriptor = new EditPersonDescriptor(); - descriptor.setName(person.getName()); - descriptor.setPhone(person.getPhone()); - descriptor.setEmail(person.getEmail()); - descriptor.setAddress(person.getAddress()); - descriptor.setTags(person.getTags()); - } - - /** - * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withName(String name) { - descriptor.setName(new Name(name)); - return this; - } - - /** - * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withPhone(String phone) { - descriptor.setPhone(new Phone(phone)); - return this; - } - - /** - * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withEmail(String email) { - descriptor.setEmail(new Email(email)); - return this; - } - - /** - * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withAddress(String address) { - descriptor.setAddress(new Address(address)); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} - * that we are building. - */ - public EditPersonDescriptorBuilder withTags(String... tags) { - Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); - descriptor.setTags(tagSet); - return this; - } - - public EditPersonDescriptor build() { - return descriptor; - } -} diff --git a/src/test/java/seedu/address/testutil/EditTaskDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditTaskDescriptorBuilder.java new file mode 100644 index 00000000000..219d246e308 --- /dev/null +++ b/src/test/java/seedu/address/testutil/EditTaskDescriptorBuilder.java @@ -0,0 +1,52 @@ +package seedu.address.testutil; + +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; + +/** + * A utility class to help with building EditTaskDescriptor objects. + */ +public class EditTaskDescriptorBuilder { + + private EditTaskCommand.EditTaskDescriptor descriptor; + + public EditTaskDescriptorBuilder() { + descriptor = new EditTaskCommand.EditTaskDescriptor(); + } + + public EditTaskDescriptorBuilder(EditTaskCommand.EditTaskDescriptor descriptor) { + this.descriptor = new EditTaskCommand.EditTaskDescriptor(descriptor); + } + + /** + * Returns an {@code EditTaskDescriptor} with fields containing {@code task}'s details + */ + public EditTaskDescriptorBuilder(Task task) { + descriptor = new EditTaskCommand.EditTaskDescriptor(); + descriptor.setModule(task.getModule()); + descriptor.setDescription((task.getDescription())); + } + + /** + * Sets the {@code Module} of the {@code EditTaskDescriptor} that we are building. + */ + public EditTaskDescriptorBuilder withModule(String moduleCode) { + descriptor.setModule(new Module(new ModuleCode(moduleCode))); + return this; + } + + /** + * Sets the {@code TaskDescription} of the {@code EditTaskDescriptor} that we are building. + */ + public EditTaskDescriptorBuilder withDescription(String description) { + descriptor.setDescription(new TaskDescription(description)); + return this; + } + + public EditTaskCommand.EditTaskDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/address/testutil/ExamBuilder.java b/src/test/java/seedu/address/testutil/ExamBuilder.java new file mode 100644 index 00000000000..dbaaf7512e8 --- /dev/null +++ b/src/test/java/seedu/address/testutil/ExamBuilder.java @@ -0,0 +1,94 @@ +package seedu.address.testutil; + +import seedu.address.model.exam.Exam; +import seedu.address.model.exam.ExamDate; +import seedu.address.model.exam.ExamDescription; +import seedu.address.model.module.DistinctModuleList; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; + +/** + * A utility class to help with building Exam objects. + */ +public class ExamBuilder { + + public static final String DEFAULT_MODULE = "CS2103T"; + public static final String DEFAULT_DESCRIPTION = "Midterms"; + public static final String DEFAULT_DATE = "29-12-2022"; + + private Module module; + private ExamDescription examDescription; + private ExamDate examDate; + private int numOfCompletedTasks; + private int totalNumOfTasks; + + /** + * Creates a {@code ExamBuilder} with the default details. + */ + public ExamBuilder() { + Module m = new Module(new ModuleCode(DEFAULT_MODULE)); + DistinctModuleList list = new DistinctModuleList(); + list.addModule(m); + examDescription = new ExamDescription(DEFAULT_DESCRIPTION); + examDate = new ExamDate(DEFAULT_DATE); + module = new Module(new ModuleCode(DEFAULT_MODULE)); + numOfCompletedTasks = 0; + totalNumOfTasks = 0; + } + + /** + * Initializes the ExamBuilder with the data of {@code examToCopy}. + */ + public ExamBuilder(Exam examToCopy) { + module = examToCopy.getModule(); + examDescription = examToCopy.getDescription(); + examDate = examToCopy.getExamDate(); + numOfCompletedTasks = examToCopy.getNumOfCompletedTasks(); + totalNumOfTasks = examToCopy.getTotalNumOfTasks(); + } + + /** + * Sets the {@code Module} of the {@code Exam} that we are building. + */ + public ExamBuilder withModule(String module) { + this.module = new Module(new ModuleCode(module)); + return this; + } + + /** + * Sets the {@code ExamDescription} of the {@code Exam} that we are building. + */ + public ExamBuilder withDescription(String description) { + this.examDescription = new ExamDescription(description); + return this; + } + + /** + * Sets the {@code ExamDate} of the {@code Exam} that we are building. + */ + public ExamBuilder withDate(String date) { + this.examDate = new ExamDate(date); + return this; + } + + /** + * Sets the {@code numOfCompletedTasks} of the {@code Exam} that we are building. + */ + public ExamBuilder withNumOfCompletedTasks(int numOfCompletedTasks) { + this.numOfCompletedTasks = numOfCompletedTasks; + return this; + } + + /** + * Sets the {@code totalNumOfTasks} of the {@code Exam} that we are building. + */ + public ExamBuilder withTotalNumOfTasks(int totalNumOfTasks) { + this.totalNumOfTasks = totalNumOfTasks; + return this; + } + + public Exam build() { + return new Exam(module, examDescription, examDate, totalNumOfTasks, numOfCompletedTasks); + } + +} diff --git a/src/test/java/seedu/address/testutil/ExamUtil.java b/src/test/java/seedu/address/testutil/ExamUtil.java new file mode 100644 index 00000000000..2757ef7afc3 --- /dev/null +++ b/src/test/java/seedu/address/testutil/ExamUtil.java @@ -0,0 +1,50 @@ +package seedu.address.testutil; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import seedu.address.logic.commands.AddExamCommand; +import seedu.address.logic.commands.EditExamCommand; +import seedu.address.model.exam.Exam; + + + + +/** + * A utility class for Exam. + */ +public class ExamUtil { + + /** + * Returns an add exam command string for adding the {@code exam}. + */ + public static String getAddExamCommand(Exam exam) { + return "e " + AddExamCommand.COMMAND_WORD + " " + getExamDetails(exam); + } + + /** + * Returns the part of command string for the given {@code exam}'s details. + */ + public static String getExamDetails(Exam exam) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_MODULE + exam.getModule().getModuleCode().moduleCode + " "); + sb.append(PREFIX_EXAM_DESCRIPTION + exam.getDescription().description + " "); + sb.append(PREFIX_EXAM_DATE + exam.getExamDate().examDate + " "); + return sb.toString(); + } + + /** + * Returns the part of command string for the given {@code EditExamDescriptor}'s details. + */ + public static String getEditExamDescriptorDetails(EditExamCommand.EditExamDescriptor descriptor) { + StringBuilder sb = new StringBuilder(); + descriptor.getDescription().ifPresent(description + -> sb.append(PREFIX_EXAM_DESCRIPTION).append(description).append(" ")); + descriptor.getModule().ifPresent(module + -> sb.append(PREFIX_MODULE).append(module).append(" ")); + descriptor.getExamDate().ifPresent(examDate + -> sb.append(PREFIX_EXAM_DATE).append(examDate).append(" ")); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/ModuleBuilder.java b/src/test/java/seedu/address/testutil/ModuleBuilder.java new file mode 100644 index 00000000000..af10edbbe9a --- /dev/null +++ b/src/test/java/seedu/address/testutil/ModuleBuilder.java @@ -0,0 +1,66 @@ +package seedu.address.testutil; + +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleCredit; +import seedu.address.model.module.ModuleName; + +/** + * A utility class to help with building Module objects. + */ +public class ModuleBuilder { + + public static final String DEFAULT_MODULE_CODE = "CS2103T"; + public static final String DEFAULT_MODULE_NAME = "Software Engineering"; + public static final int DEFAULT_MODULE_CREDIT = 4; + + private ModuleCode moduleCode; + private ModuleName moduleName; + private ModuleCredit moduleCredit; + + /** + * Creates a {@code ModuleBuilder} with the default details. + */ + public ModuleBuilder() { + moduleCode = new ModuleCode(DEFAULT_MODULE_CODE); + moduleName = new ModuleName(DEFAULT_MODULE_NAME); + moduleCredit = new ModuleCredit(DEFAULT_MODULE_CREDIT); + } + + /** + * Initializes the ModuleBuilder with the data of {@code moduleToCopy}. + */ + public ModuleBuilder(Module moduleToCopy) { + moduleCode = moduleToCopy.getModuleCode(); + moduleName = moduleToCopy.getModuleName(); + moduleCredit = moduleToCopy.getModuleCredit(); + } + + /** + * Sets the {@code ModuleCode} of the {@code Module} that we are building. + */ + public ModuleBuilder withModuleCode(String moduleCode) { + this.moduleCode = new ModuleCode(moduleCode); + return this; + } + + /** + * Sets the {@code ModuleName} of the {@code Module} that we are building. + */ + public ModuleBuilder withModuleName(String moduleName) { + this.moduleName = new ModuleName(moduleName); + return this; + } + + /** + * Sets the {@code ModuleCredit} of the {@code Module} that we are building. + */ + public ModuleBuilder withModuleCredit(int moduleCredit) { + this.moduleCredit = new ModuleCredit(moduleCredit); + return this; + } + + public Module build() { + return new Module(moduleCode, moduleName, moduleCredit); + } +} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java deleted file mode 100644 index 6be381d39ba..00000000000 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ /dev/null @@ -1,96 +0,0 @@ -package seedu.address.testutil; - -import java.util.HashSet; -import java.util.Set; - -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.model.util.SampleDataUtil; - -/** - * A utility class to help with building Person objects. - */ -public class PersonBuilder { - - public static final String DEFAULT_NAME = "Amy Bee"; - public static final String DEFAULT_PHONE = "85355255"; - public static final String DEFAULT_EMAIL = "amy@gmail.com"; - public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; - - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - /** - * Creates a {@code PersonBuilder} with the default details. - */ - public PersonBuilder() { - name = new Name(DEFAULT_NAME); - phone = new Phone(DEFAULT_PHONE); - email = new Email(DEFAULT_EMAIL); - address = new Address(DEFAULT_ADDRESS); - tags = new HashSet<>(); - } - - /** - * Initializes the PersonBuilder with the data of {@code personToCopy}. - */ - public PersonBuilder(Person personToCopy) { - name = personToCopy.getName(); - phone = personToCopy.getPhone(); - email = personToCopy.getEmail(); - address = personToCopy.getAddress(); - tags = new HashSet<>(personToCopy.getTags()); - } - - /** - * Sets the {@code Name} of the {@code Person} that we are building. - */ - public PersonBuilder withName(String name) { - this.name = new Name(name); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. - */ - public PersonBuilder withTags(String ... tags) { - this.tags = SampleDataUtil.getTagSet(tags); - return this; - } - - /** - * Sets the {@code Address} of the {@code Person} that we are building. - */ - public PersonBuilder withAddress(String address) { - this.address = new Address(address); - return this; - } - - /** - * Sets the {@code Phone} of the {@code Person} that we are building. - */ - public PersonBuilder withPhone(String phone) { - this.phone = new Phone(phone); - return this; - } - - /** - * Sets the {@code Email} of the {@code Person} that we are building. - */ - public PersonBuilder withEmail(String email) { - this.email = new Email(email); - return this; - } - - public Person build() { - return new Person(name, phone, email, address, tags); - } - -} diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java deleted file mode 100644 index 90849945183..00000000000 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.testutil; - -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Person; -import seedu.address.model.tag.Tag; - -/** - * A utility class for Person. - */ -public class PersonUtil { - - /** - * Returns an add command string for adding the {@code person}. - */ - public static String getAddCommand(Person person) { - return AddCommand.COMMAND_WORD + " " + getPersonDetails(person); - } - - /** - * Returns the part of command string for the given {@code person}'s details. - */ - public static String getPersonDetails(Person person) { - StringBuilder sb = new StringBuilder(); - sb.append(PREFIX_NAME + person.getName().fullName + " "); - sb.append(PREFIX_PHONE + person.getPhone().value + " "); - sb.append(PREFIX_EMAIL + person.getEmail().value + " "); - sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); - person.getTags().stream().forEach( - s -> sb.append(PREFIX_TAG + s.tagName + " ") - ); - return sb.toString(); - } - - /** - * Returns the part of command string for the given {@code EditPersonDescriptor}'s details. - */ - public static String getEditPersonDescriptorDetails(EditPersonDescriptor descriptor) { - StringBuilder sb = new StringBuilder(); - descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); - descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); - descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" ")); - descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" ")); - if (descriptor.getTags().isPresent()) { - Set tags = descriptor.getTags().get(); - if (tags.isEmpty()) { - sb.append(PREFIX_TAG); - } else { - tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); - } - } - return sb.toString(); - } -} diff --git a/src/test/java/seedu/address/testutil/PriorityTagBuilder.java b/src/test/java/seedu/address/testutil/PriorityTagBuilder.java new file mode 100644 index 00000000000..53397742225 --- /dev/null +++ b/src/test/java/seedu/address/testutil/PriorityTagBuilder.java @@ -0,0 +1,43 @@ +package seedu.address.testutil; + +import seedu.address.model.tag.PriorityTag; + +/** + * PriorityTagBuilder builds the priority tag + */ +public class PriorityTagBuilder { + private static final String DEFAULT_STATUS = "HIGH"; + private String status; + + /** + * Initialises the PriorityTagBuilder with the default status. + */ + public PriorityTagBuilder() { + status = DEFAULT_STATUS; + } + + /** + * Initialises the status of the PriorityTagBuilder with the given + * priority tag. + * + * @param priorityTag The priority tag given. + */ + public PriorityTagBuilder(PriorityTag priorityTag) { + this.status = priorityTag.status; + } + + /** + * Sets the status of the priority tag to the provided status. + * + * @param status The new priority status provided. + * @return PriorityTagBuilder containing the new priority status. + */ + public PriorityTagBuilder withStatus(String status) { + this.status = status; + return this; + } + + public PriorityTag build() { + return new PriorityTag(status); + } +} diff --git a/src/test/java/seedu/address/testutil/TaskBuilder.java b/src/test/java/seedu/address/testutil/TaskBuilder.java new file mode 100644 index 00000000000..c381b12f7fe --- /dev/null +++ b/src/test/java/seedu/address/testutil/TaskBuilder.java @@ -0,0 +1,116 @@ +package seedu.address.testutil; + +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskStatus; + +/** + * A utility class to help with building Task objects. + */ +public class TaskBuilder { + + public static final String DEFAULT_TASK_DESCRIPTION = "task description"; + public static final String DEFAULT_MODULE = "CS2103T"; + + private Module module; + private TaskDescription taskDescription; + private PriorityTag priorityTag; + private DeadlineTag deadlineTag; + private TaskStatus taskStatus; + private Exam linkedExam; + + /** + * Creates a {@code TaskBuilder} with the default details. + */ + public TaskBuilder() { + module = new Module(new ModuleCode(DEFAULT_MODULE)); + taskDescription = new TaskDescription(DEFAULT_TASK_DESCRIPTION); + priorityTag = null; + deadlineTag = null; + taskStatus = TaskStatus.INCOMPLETE; + linkedExam = null; + } + + /** + * Initializes the TaskBuilder with the data of {@code taskToCopy}. + */ + public TaskBuilder(Task taskToCopy) { + module = taskToCopy.getModule(); + taskDescription = taskToCopy.getDescription(); + priorityTag = taskToCopy.getPriorityTag(); + deadlineTag = taskToCopy.getDeadlineTag(); + taskStatus = taskToCopy.getStatus(); + linkedExam = taskToCopy.getExam(); + } + + /** + * Sets the {@code TaskDescription} of the {@code Task} that we are building. + */ + public TaskBuilder withTaskDescription(String description) { + this.taskDescription = new TaskDescription(description); + return this; + } + + /** + * Sets the {@code Module} of the {@code Task} that we are building. + */ + public TaskBuilder withModule(String module) { + this.module = new Module(new ModuleCode(module)); + return this; + } + + /** + * Sets the {@code TaskStatus} of the {@code Task} that we are building. + */ + public TaskBuilder withStatus(String complete) { + this.taskStatus = TaskStatus.of(complete); + return this; + } + + /** + * Sets the {@code Exam} of the {@code Task} that we are building. + */ + public TaskBuilder withExam(Exam exam) { + this.linkedExam = exam; + return this; + } + + /** + * Removes the {@code Exam} of the {@code Task} that we are building. + */ + public TaskBuilder withNoExam() { + this.linkedExam = null; + return this; + } + + /** + * Sets the {@code PriorityTag} of the {@code Task} which is being built. + * + * @param priorityTag The priority tag being set. + * @return The task builder which now contains the priority tag. + */ + public TaskBuilder withPriorityTag(PriorityTag priorityTag) { + this.priorityTag = priorityTag; + return this; + } + + /** + * Sets the {@code DeadlineTag} of the {@code Task} which is being built. + * + * @param deadlineTag The deadline tag being set. + * @return The task builder which now contains the deadline tag. + */ + public TaskBuilder withDeadlineTag(DeadlineTag deadlineTag) { + this.deadlineTag = deadlineTag; + return this; + } + + public Task build() { + return new Task(module, taskDescription, taskStatus, priorityTag, deadlineTag, linkedExam); + } +} diff --git a/src/test/java/seedu/address/testutil/TaskUtil.java b/src/test/java/seedu/address/testutil/TaskUtil.java new file mode 100644 index 00000000000..c8b49dbcfaf --- /dev/null +++ b/src/test/java/seedu/address/testutil/TaskUtil.java @@ -0,0 +1,45 @@ +package seedu.address.testutil; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.model.task.Task; + + + +/** + * A utility class for Task. + */ +public class TaskUtil { + + /** + * Returns an add task command string for adding the {@code task}. + */ + public static String getAddTaskCommand(Task task) { + return "t " + AddTaskCommand.COMMAND_WORD + " " + getTaskDetails(task); + } + + /** + * Returns the part of command string for the given {@code task}'s details. + */ + public static String getTaskDetails(Task task) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_MODULE + task.getModule().getModuleCode().moduleCode + " "); + sb.append(PREFIX_DESCRIPTION + task.getDescription().description + " "); + return sb.toString(); + } + + /** + * Returns the part of command string for the given {@code EditTaskDescriptor}'s details. + */ + public static String getEditTaskDescriptorDetails(EditTaskCommand.EditTaskDescriptor descriptor) { + StringBuilder sb = new StringBuilder(); + descriptor.getDescription().ifPresent(description-> + sb.append(PREFIX_DESCRIPTION).append(description).append(" ")); + descriptor.getModule().ifPresent(module -> + sb.append(PREFIX_MODULE).append(module).append(" ")); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/address/testutil/TestUtil.java index 896d103eb0b..fbdd06d1d66 100644 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ b/src/test/java/seedu/address/testutil/TestUtil.java @@ -5,10 +5,6 @@ import java.nio.file.Path; import java.nio.file.Paths; -import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - /** * A utility class for test cases. */ @@ -32,24 +28,4 @@ public static Path getFilePathInSandboxFolder(String fileName) { return SANDBOX_FOLDER.resolve(fileName); } - /** - * Returns the middle index of the person in the {@code model}'s person list. - */ - public static Index getMidIndex(Model model) { - return Index.fromOneBased(model.getFilteredPersonList().size() / 2); - } - - /** - * Returns the last index of the person in the {@code model}'s person list. - */ - public static Index getLastIndex(Model model) { - return Index.fromOneBased(model.getFilteredPersonList().size()); - } - - /** - * Returns the person in the {@code model}'s person list at {@code index}. - */ - public static Person getPerson(Model model, Index index) { - return model.getFilteredPersonList().get(index.getZeroBased()); - } } diff --git a/src/test/java/seedu/address/testutil/TypicalExams.java b/src/test/java/seedu/address/testutil/TypicalExams.java new file mode 100644 index 00000000000..cbf89e3f35a --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalExams.java @@ -0,0 +1,47 @@ +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.exam.Exam; + + +/** + * A utility class containing a list of {@code Exam} objects to be used in tests. + */ +public class TypicalExams { + public static final Exam MIDTERM_EXAM = new ExamBuilder().withModule("cs2040") + .withDescription("Midterm Exam") + .withDate("29-12-2022") + .build(); + public static final Exam FINAL_EXAM = new ExamBuilder().withModule("cs2030") + .withDescription("Final Exam") + .withDate("28-12-2022") + .build(); + public static final Exam EXAMONE = new ExamBuilder() + .withDescription("Exam one").withModule("CS2030S") + .withDate("20-08-2023").build(); + public static final Exam EXAMTWO = new ExamBuilder() + .withDescription("Exam two") + .withModule("CS2040S").withDate("20-10-2023").build(); + public static final Exam EXAMTHREE = new ExamBuilder() + .withDescription("Exam three") + .withDate("27-11-2022") + .build(); + + public static final Exam CS2040_FINAL = new ExamBuilder() + .withModule("cs2040").withDescription("CS2040 Finals") + .withDate("29-11-2022").build(); + public static final Exam MA1521_MIDTERM = new ExamBuilder() + .withDescription("Midterm") + .withModule("MA1521").withDate("20-11-2023").build(); + + private TypicalExams() {} // prevents instantiation + + public static List getTypicalExams() { + return new ArrayList<>(Arrays.asList(MIDTERM_EXAM, FINAL_EXAM, EXAMONE, EXAMTWO, CS2040_FINAL, MA1521_MIDTERM)); + } + +} + diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e613937657..a7812fed2bf 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -6,7 +6,42 @@ * A utility class containing a list of {@code Index} objects to be used in tests. */ public class TypicalIndexes { + public static final Index FIRST_INDEX = Index.fromOneBased(1); + public static final Index SECOND_INDEX = Index.fromOneBased(2); + public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); - public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); - public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + + public static final Index INDEX_FIRST_TASK = Index.fromOneBased(1); + public static final Index INDEX_SECOND_TASK = Index.fromOneBased(2); + public static final Index INDEX_UNMARKED_TASK = Index.fromOneBased(1); + public static final Index INDEX_MARKED_TASK = Index.fromOneBased(6); + public static final Index INDEX_LINKED_TASK = Index.fromOneBased(4); + public static final Index INDEX_UNLINKED_TASK = Index.fromOneBased(1); + public static final Index INDEX_THIRD_TASK = Index.fromOneBased(3); + public static final Index INDEX_TWELVE_TASK = Index.fromOneBased(12); + public static final Index INDEX_THIRTEENTH_TASK = Index.fromOneBased(13); + public static final Index INDEX_FOURTEENTH_TASK = Index.fromOneBased(14); + public static final Index INDEX_PRIORITY_TAG_TASK = Index.fromOneBased(7); + public static final Index INDEX_DEADLINE_TAG_TASK = Index.fromOneBased(8); + + public static final Index INDEX_LAST_TASK = Index.fromOneBased(9); + + public static final Index INDEX_FIRST_EXAM = Index.fromOneBased(1); + public static final Index INDEX_SECOND_EXAM = Index.fromOneBased(2); + public static final Index INDEX_THIRD_EXAM = Index.fromOneBased(3); + public static final Index INDEX_FORTH_EXAM = Index.fromOneBased(4); + public static final Index INDEX_CS2040_FINAL = Index.fromOneBased(5); + + public static final Index INDEX_FIRST_MODULE = Index.fromOneBased(1); + public static final Index INDEX_SECOND_MODULE = Index.fromOneBased(2); + public static final Index INDEX_THIRD_MODULE = Index.fromOneBased(3); + public static final Index INDEX_FOURTH_MODULE = Index.fromOneBased(4); + public static final Index INDEX_FIFTH_MODULE = Index.fromOneBased(5); + public static final Index INDEX_SIXTH_MODULE = Index.fromOneBased(6); + public static final Index INDEX_MODULE_UNRELATED_TO_ANY_TASK_OR_EXAM = Index.fromOneBased(8); + public static final Index INDEX_MODULE_RELATED_TO_TASKS_NOT_EXAMS = Index.fromOneBased(4); + public static final Index INDEX_MODULE_RELATED_TO_EXAMS_NOT_TASKS = Index.fromOneBased(5); + public static final Index INDEX_MODULE_RELATED_TO_EXAMS_AND_TASKS = Index.fromOneBased(7); + } + diff --git a/src/test/java/seedu/address/testutil/TypicalModules.java b/src/test/java/seedu/address/testutil/TypicalModules.java new file mode 100644 index 00000000000..d1cb6afe437 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalModules.java @@ -0,0 +1,48 @@ +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.module.Module; + +/** + * A utility class containing a list of {@code Module} objects to be used in tests. + */ +public class TypicalModules { + + public static final Module CS2030 = new ModuleBuilder().withModuleCode("cs2030") + .withModuleName("Programming Methodology") + .withModuleCredit(4) + .build(); + public static final Module CS2040 = new ModuleBuilder().withModuleCode("cs2040") + .withModuleName("Data and Algorithms") + .withModuleCredit(4) + .build(); + public static final Module CS2100 = new ModuleBuilder().withModuleCode("cs2100") + .withModuleName("Computer Organisation") + .withModuleCredit(4) + .build(); + public static final Module CS2103T = new ModuleBuilder().withModuleCode("cs2103t") + .withModuleName("Software Engineering") + .withModuleCredit(4) + .build(); + public static final Module MA1521 = new ModuleBuilder().withModuleCode("MA1521") + .withModuleName("Math") + .withModuleCredit(4) + .build(); + public static final Module MA2001 = new ModuleBuilder().withModuleCode("MA2001") + .withModuleName("Linear Algebra") + .withModuleCredit(4) + .build(); + + public static final Module CS2030S = new ModuleBuilder().withModuleCode("CS2030S") + .withModuleName("Programming Methodology for CS students").withModuleCredit(4).build(); + public static final Module CS2040S = new ModuleBuilder().withModuleCode("CS2040S") + .withModuleName("Data and Algorithms for CS students").withModuleCredit(4).build(); + + + public static List getTypicalModules() { + return new ArrayList<>(Arrays.asList(CS2030, CS2040, CS2100, CS2103T, MA1521, CS2030S, CS2040S, MA2001)); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java deleted file mode 100644 index fec76fb7129..00000000000 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.testutil; - -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class containing a list of {@code Person} objects to be used in tests. - */ -public class TypicalPersons { - - public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") - .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") - .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); - public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); - public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); - public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); - public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); - public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); - - // Manually added - public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); - public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); - - // Manually added - Person's details found in {@code CommandTestUtil} - public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) - .build(); - - public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER - - private TypicalPersons() {} // prevents instantiation - - /** - * Returns an {@code AddressBook} with all the typical persons. - */ - public static AddressBook getTypicalAddressBook() { - AddressBook ab = new AddressBook(); - for (Person person : getTypicalPersons()) { - ab.addPerson(person); - } - return ab; - } - - public static List getTypicalPersons() { - return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); - } -} diff --git a/src/test/java/seedu/address/testutil/TypicalTasks.java b/src/test/java/seedu/address/testutil/TypicalTasks.java new file mode 100644 index 00000000000..c3cad5d0477 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalTasks.java @@ -0,0 +1,106 @@ +package seedu.address.testutil; + +import static seedu.address.testutil.TypicalExams.getTypicalExams; +import static seedu.address.testutil.TypicalModules.getTypicalModules; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.exam.Exam; +import seedu.address.model.module.Module; +import seedu.address.model.tag.DeadlineTag; +import seedu.address.model.tag.PriorityTag; +import seedu.address.model.task.Task; + +/** + * A utility class containing a list of {@code Task} objects to be used in tests. + */ +public class TypicalTasks { + public static final Task TASK_A = new TaskBuilder().withModule("cs2030") + .withTaskDescription("Task A") + .withStatus("incomplete") + .build(); + public static final Task TASK_B = new TaskBuilder().withModule("cs2030") + .withTaskDescription("Task B") + .withStatus("incomplete") + .withPriorityTag(new PriorityTag("low")) + .build(); + public static final Task TASK_C = new TaskBuilder().withModule("cs2040") + .withTaskDescription("Task C") + .withStatus("incomplete") + .build(); + public static final Task TASK_D = new TaskBuilder().withModule("cs2040") + .withTaskDescription("Task D") + .withStatus("complete") + .withExam(getTypicalExams().get(0)) + .withPriorityTag(new PriorityTag("high")) + .withDeadlineTag(new DeadlineTag(LocalDate.parse("29-12-2022", DateTimeFormatter.ofPattern("dd-MM-yyyy")))) + .build(); + public static final Task TASK_E = new TaskBuilder().withModule("cs2100") + .withTaskDescription("Task E") + .withStatus("complete") + .build(); + public static final Task TASK_F = new TaskBuilder().withModule("cs2100") + .withTaskDescription("Task F") + .withStatus("complete") + .build(); + public static final Task TASK_G = new TaskBuilder().withModule("cs2100") + .withTaskDescription("Task G") + .withPriorityTag(new PriorityTagBuilder().build()) + .build(); + + public static final Task TASK_H = new TaskBuilder().withModule("cs2103t") + .withTaskDescription("Task H") + .withDeadlineTag(new DeadlineTagBuilder().build()) + .build(); + + public static final Task TASK_I = new TaskBuilder().withModule("cs2103t") + .withTaskDescription("Task I") + .build(); + + public static final Task TASK_J = new TaskBuilder().withTaskDescription("Task one").withModule("CS2030S").build(); + public static final Task TASK_K = new TaskBuilder().withTaskDescription("Task two").withModule("CS2030S").build(); + public static final Task TASK_L = new TaskBuilder().withTaskDescription("HW1").withModule("CS2030S").build(); + public static final Task TASK_M = new TaskBuilder().withTaskDescription("HW2").withModule("CS2040S").build(); + public static final Task TASK_N = new TaskBuilder().withTaskDescription("HW3").withModule("CS2030S").build(); + public static final Task TASK_O = new TaskBuilder().withTaskDescription("homework").withModule("CS2030s").build(); + public static final Task TASK_P = new TaskBuilder() + .withTaskDescription("PAST YEAR PAPER").withModule("CS2030s").build(); + public static final Task TASK_Q = new TaskBuilder().withModule("cs2030") + .withTaskDescription("Task Q") + .withStatus("complete") + .withExam(getTypicalExams().get(1)) + .withPriorityTag(new PriorityTag("high")) + .withDeadlineTag(new DeadlineTag(LocalDate.parse("29-12-2022", DateTimeFormatter.ofPattern("dd-MM-yyyy")))) + .build(); + + private TypicalTasks() { + } // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical tasks, typical exams and typical modules. + */ + + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (Module module: getTypicalModules()) { + ab.addModule(module); + } + for (Exam exam: getTypicalExams()) { + ab.addExam(exam); + } + for (Task task : getTypicalTasks()) { + ab.addTask(task); + } + return ab; + } + + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(TASK_A, TASK_B, TASK_C, TASK_D, TASK_E, TASK_F, TASK_G, TASK_H, TASK_I, + TASK_J, TASK_K, TASK_L, TASK_M, TASK_N, TASK_O, TASK_P)); + } +}