diff --git a/README.md b/README.md
index 13f5c77403f..003b4efd89b 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,32 @@
-[](https://github.com/se-edu/addressbook-level3/actions)
+[](https://github.com/AY2122S2-CS2103T-T12-4/tp/actions)

-* This is **a sample project for Software Engineering (SE) students**.
- 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.
- * 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.
+# NUS Classes
+**NUS Classes** is an app that enables the professors to better manage contacts from large numbers of students and staff,
+and allows the professors to document the tasks that they need to do.
+
+### Features
+
+Features added on top of AB3.
+
+| Feature | Description |
+|----------------|----------------------------------------------------------------------------------------------------------------------------------|
+| Create Task | Add and schedule (*recurring*) tasks on NUS Classes easily with a single command. |
+| Update Task | Edit tasks which are outdated or when mistakes were made. |
+| Delete Task | Delete tasks which are unnecessary or when you are done with it. |
+| Un/Assign Task | Assign and unassign contacts to a task. Help with organization and planning! |
+| View Task | View the people assigned to a task. Help with remembering who you are suppose to meet! |
+| Tag Task | Tag a task with different tags. Help with categorizing tasks! |
+| Filter Task | Too many tasks? Filter them based on keywords! |
+| Alert | Forgetful? No worries, tasks in NUS Classes are color coded to allow you to quickly identify tasks that are overdue or due soon! |
+
+### Others
+
+* **User Guide** for NUS Classes can be found here: [User Guide](https://ay2122s2-cs2103t-t12-4.github.io/tp/UserGuide.html).
+* **Developer Guide** for NUS Classes can be found here: [Developer Guide](https://ay2122s2-cs2103t-t12-4.github.io/tp/DeveloperGuide.html).
+
+### Acknowledgements
+This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+Additionally, this project was adopted from the existing AB3 se-education.org project. Please refer
+to the AB3 product website **[here](https://se-education.org/addressbook-level3/)** for more information.
diff --git a/build.gradle b/build.gradle
index be2d2905dde..88545de9b64 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,10 +16,18 @@ repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
+run {
+ enableAssertions = true
+}
+
checkstyle {
toolVersion = '8.29'
}
+run {
+ enableAssertions = true
+}
+
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
@@ -66,7 +74,7 @@ dependencies {
}
shadowJar {
- archiveName = 'addressbook.jar'
+ archiveName = 'NUSClasses.jar'
}
defaultTasks 'clean', 'test'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..28c773f2109 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -5,55 +5,51 @@ title: About Us
We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
-You can reach us at the email `seer[at]comp.nus.edu.sg`
+You can reach us through our Github usernames.
## Project team
-### John Doe
+### Brian
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/brian16600)]] [[portfolio](team/brian16600.md)]
-* Role: Project Advisor
+* Role: Developer
+* Responsibilites: Code Quality + Documentation
-### Jane Doe
+### Sean Ng
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/snss231)]] [[portfolio](team/snss231.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
+* Responsibilities: Data
-### Johnny Doe
+### Ong Jun Jie
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/junjunjieong)]] [[portfolio](team/junjunjieong.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Code quality + Deliverables and deadlines
-### Jean Doe
+### Tan Jun Rong
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/junrong98)]] [[portfolio](team/junrong98.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Code Quality, In Charge of Tagging
-### James Doe
+### Adrian Ong
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/AdrianOngJJ)]] [[portfolio](team/adrianongjj.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Glossary + Documentation
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 46eae8ee565..78837e59b99 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,14 +2,46 @@
layout: page
title: Developer Guide
---
-* Table of Contents
-{:toc}
+## Table of Contents
+* [Acknowledgements](#acknowledgements)
+* [Setting up, getting started](#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)
+ * [Delete person feature](#delete-person-feature)
+ * [Delete task feature](#delete-task-feature)
+ * [Edit task feature](#edit-task-feature)
+ * [View task feature](#view-task-feature)
+ * [Find task feature](#find-task-feature)
+* [Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops)
+* [Appendix: Requirements](#appendix-requirements)
+ * [Product scope](#product-scope)
+ * [User stories](#user-stories)
+ * [Use cases](#use-cases)
+ * [Non-Functional requirements](#non-functional-requirements)
+* [Appendix: Instructions for manual testing](#appendix-instructions-for-manual-testing)
+ * [Launch and shutdown](#launch-and-shutdown)
+ * [Deleting a person](#deleting-a-person)
+ * [Saving data](#saving-data)
+* [Prefix Summary](#prefix-summary)
+* [Glossary](#glossary)
+
+
--------------------------------------------------------------------------------------------------------------------
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* [PlantUML](https://plantuml.com/) - open-source diagramming tool used for our architecture, class, sequence and activity diagrams.
+* [JUnit](https://junit.org/junit5/) - Java testing framework used for most of our testing
+* [Gradle](https://gradle.org/) - Build automation tool
+* [Shadowjar](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) - "A Gradle plugin for collapsing all dependencies and project code into a single Jar file."
+* [Jackson](https://github.com/FasterXML/jackson) - "JSON for Java" library
--------------------------------------------------------------------------------------------------------------------
@@ -17,15 +49,31 @@ title: Developer Guide
Refer to the guide [_Setting up and getting started_](SettingUp.md).
+
+
+
--------------------------------------------------------------------------------------------------------------------
+## **Description**
+NUS Classes is a desktop app for NUS Computing professors to manage their tasks and contacts. It includes task management features such as creating tasks, tagging tasks, assigning contacts to tasks, and marking tasks as complete or incomplete. It also includes contact management features such as finding contacts, assigning contacts to specific tasks and tagging contacts.
+
+NUS Classes also provides a simple alert feature for tasks by displaying tasks in different color based on the urgency of the task. Tasks that are overdue are marked as red, whereas, tasks that are nearing deadline are marked as yellow.
+
+NUS Classes is optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). Using NUS Classes can get your contact management tasks done faster than traditional GUI apps, saving time on otherwise tedious administrative tasks.
+
+This Developer Guide is documented with the approach of developer-as-maintainer, explaining how the architecture and implementation of NUS Classes is done
+to allow for easy maintenance and modification if necessary. In this Developer Guide, you will find explanations as well as diagrams for the main components of NUS Classes at the high-level design as well as in-depth explanations
+on certain key features.
+
## **Design**
-: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.
+:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](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.
+
+
### Architecture
@@ -36,7 +84,7 @@ 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/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2122S2-CS2103T-T12-4/tp/tree/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.
@@ -67,15 +115,17 @@ For example, the `Logic` component defines its API in the `Logic.java` interface
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/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/java/seedu/address/ui/Ui.java)

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` 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/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
@@ -84,9 +134,11 @@ The `UI` component,
* 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`.
+
+
### 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/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/java/seedu/address/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
@@ -113,8 +165,10 @@ 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.
+
+
### 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/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/java/seedu/address/model/Model.java)
@@ -122,122 +176,221 @@ How the parsing works:
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 task list data i.e., all 'Task' objects (which are contained in a `TaskList` 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 some `Person`'s data in the list changes.
+* stores the currently 'selected' `Task` 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 some `Task`'s data in the list changes.
* 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/AY2122S2-CS2103T-T12-4/tp/tree/master/src/main/java/seedu/address/storage/Storage.java)
The `Storage` component,
-* can save both address book data and user preference data in json format, and read them back into corresponding objects.
-* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
+* can save address book, task list and user preference data in json format, and read them back into corresponding objects.
+* inherits from `AddressBookStorage`, `TaskListStorage` and `UserPrefStorage`, which means it can be treated as any one of them (if only the functionality of one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
### Common classes
Classes used by multiple components are in the `seedu.addressbook.commons` package.
+
+
+
--------------------------------------------------------------------------------------------------------------------
## **Implementation**
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Add Task feature
-#### Proposed Implementation
+In NUS Classes, `Task`s represent NUS Computing professor's tasks that they have scheduled. Tasks include relevant information
+such as `TASKNAME`, `DATETIME`, `LINK` for links to meetings/lectures/tutorials and `TAG`s for professors to keep their tasks organised.
+When a `Task` is added to NUS Classes, the following features are implemented:
+- `AddTaskCommandParser#parse()` - Parses the compulsory fields of `TASKNAME` and `DATETIME` as well as optional fields such as `ENDDATETIME`, `TAG`, `LINK` and `INTERVAL` with `REUCRRENCE`.
+The parser obtains the relevant information to be added and creates a `Task` with the information parsed.
+- `AddTaskCommand` which contains the relevant information for the newly added `Task`.
+- `AddTaskCommand#execute()` - Execute `ModelManager#addTask()` by parsing in the task to be added and passing it to `TaskList`.
+- `TaskList#addTask()` - Adds the `Task` to `TaskList`.
-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:
+Step 1: User enters the command, e.g. `addt tn/Lesson dt/12-03-2022 1200`. Once this command is parsed, it is handled by `AddressBookParser#parseCommand()`, which creates a `AddTaskCommandParser` object.
-* `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.
+Step 2: `AddTaskCommandParser#parse()` is then called which parses the command. The parameters entered by the user, such as `TASKNAME`, `DATETIME` and optional parameters are parsed, and a new `AddTaskCommand` object is created which contains the parameters entered by the user.
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+Step 3: `AddTaskCommand` will then call `AddTaskCommand#execute()` which will execute the command. It will first create a `Task` based on the information contained and then it will add the newly created `Task` to `TaskList` via `TaskList#addTask()`.
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+Step 4: Return either a success message with the added `Task` or a `CommandException` due to missing parameters or invalid parameters.
-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.
+
-
+
:information_source: **Note:** The lifeline for `AddTaskCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
-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.
+
-
+### Delete person feature
-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`.
+In NUS Classes, `Task`s are entities that maintain a list of `People` that are associated with the task.
+When a contact is deleted from the `AddressBook`, it is essential that the `Task`s that contain that contact are updated to also remove the contact.
+To implement this, upon every `DeleteCommand` execution, we call the `TaskList::removePerson` which iterate through all the tasks and remove the relevant `Person` from the tasks if present.
-
+
-
: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`.
+Design considerations:
-
+**Aspect:** how relevant tasks are updated when a person is removed from the address book
+
+* **Alternative 1 (current choice):** Iterate through all tasks to remove the relevant person.
+ * Pros: Easy to implement.
+ * Cons: _May_ have performance issues given a large list of tasks
+
+* **Alternative 2:** Add a reference from each Person to the Tasks they are associated with. When a person is deleted, reference all the tasks through the `Person` object to update the tasks.
+ * Pros: _May_ see some performance benefit (not necessary to iterate through all the tasks upon each `DeleteCommand`)
+ * Cons: More fragile code due to circular dependency (`Person` depends on `Task`). Not often that a Professor will delete a contact (student or tutor) in the course of a module.
-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.
+
-
+### Delete Task feature
+Delete task feature implements the following operations:
+* `DeleteTaskCommandParser#parse()` — Parse the index number from user command to `DeleteTaskCommand` to get the task to be deleted.
+* `DeleteTaskCommand#execute()` — Execute `ModelManager#deleteTask()` by parsing in the task to be deleted.
+* `ModelManager#deleteTask()` — Execute `TaskList#deleteCurrTask()` by parsing in the task to be deleted.
+* `TaskList#deleteCurrTask()` — Deletes the task from the TaskList stored here.
-
: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.
+Step 1: User will enter the command `deletet 1` to delete the first task.
+Once user parses in the command, it will be handled by `AddressBookParser#parseCommand()`, then calling of `DeleteTaskCommandParser#parse()`
+to create `DeleteTaskCommand` and execute to delete the task from the task list.
+The Sequence Diagram below illustrates the interactions of how the delete task feature works.
+
+
:information_source: **Note:** The lifeline for `DeleteTaskCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-The following sequence diagram shows how the undo operation works:
+Step 2: Outcome after executing `DeleteTaskCommand`
-
+Execution flow of Activity Diagram:
-
: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.
+
-
+#### Design considerations:
+**Aspect:** How delete task executes:
-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.
+* **Alternative 1 (current choice):** Delete task based on the index shown.
+ * Pros: Easy to implement.
+ * Cons: Users have to scroll through task list to look for task index number.
-
: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.
+* **Alternative 2:** Delete task based on the task name.
+ * Pros: Users just have to enter the task name.
+ * Cons: Checks are needed to ensure that users entered the correct spelling and spacing of the task name
-
+
+
+### Edit Task feature
+Edit task feature implements the following operations:
+* `EditTaskCommandParser#parse()` — Parse the command such as index of the task to edit and which information to update.
+* `EditTaskCommand#execute()` — Execute `ModelManager#setTask()` by parsing in the task to be edited and the updated version of the task.
+* `EditTaskDescriptor#setName()` — Set the edited task name to `EditTaskDescriptor`
+* `EditTaskDescriptor#setDate()` — Set the edited datetime to `EditTaskDescriptor`
+* `EditTaskDescriptor#setTags()` — Set the edited tags to `EditTaskDescriptor`
+* `ParseUtil#parseIndex()` — Parse to get the index number of the task
+* `ModelManager#setTask()` — Update the task information.
+* `ModelManager#updateFilteredTaskList()` — Updates the filter of the filtered task list to filter by the given predicate.
-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.
+Step 1: User parses in command. For example, `editt 1 tn/Teach CS2103T dt/12-03-2022 1330 z/https://zoomlink.com t/Homework`
+Once user parses in the command, it will be handled by `AddressBookParser#parseCommand()`, then calling of `EditTaskCommandParser#parse()`
+
-
+Step 2: `EditTaskCommandParser` will call `ParseUtil#parseIndex()` to get the task index.
+Then `EditTaskCommandParser` will create `EditTaskDescriptor editTaskDescriptor`. `EditTaskCommandParser` will check if the
+task name, datetime, link or tag prefix exist. It is optional to not have all the prefixes as user may not want to change certain field.
+For each prefix in the command, it will set the value to `editTaskDescriptor`.
+
-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.
+Step 3: `EditTaskCommandParser` will create `EditTaskCommand`, parse in `index` and `editTaskDescriptor`
+`EditTaskCommand` will start to execute and call `ModelManager#setTask` and `ModelManager#updateFilteredTaskList` to update
+the task and task list.
+
-
+Step 4: Lastly return the result.
+Possible outcome from the result.
+* Outcome 1: Successfully updated task.
+* Outcome 2: Throw CommandException due to index out of range, invalid parameters or no valid changes to `Task`.
-The following activity diagram summarizes what happens when a user executes a new command:
+
-
+The Sequence Diagram below illustrates the overall interactions of how the edit task feature work.
+
+
+
+
+### View Task feature
+The view task mechanism is facilitated by `ViewCommand`, `ViewCommandParser`, `ModelManager` and `Task`. Additionally, it implements the following operation:
+
+* `ViewCommandParser#parse()` — Parses the arguments provided by the users into a command to be executed.
+* `ViewCommand#execute()` — Executes the operations required to display the people associated with a specific task.
+* `ModelManager#getFilteredTaskList()` — Gets the task list currently displayed as output to the user.
+* `ModelManager#updateFilteredPersonList()` — Updates the person list displayed as output to the user by providing the argument with a list of people.
+* `Task#getPeople()` — Gets a list of people associated to a task.
+
+Given below is an example usage scenario and how the view task mechanism behaves at each step.
+
+Step 1: The user will enter the command `view 1` to view the people associated with the first task. The command will be handled by
+`AddressBookParser#parseCommand()` which will create a `ViewCommandParser` object.
+
+Step 2. The `AddressBookParser` will call `ViewCommandParser#parse()` which will parse the command, returning a `ViewCommand` to be executed.
+
+Step 3. The `LogicManager` will call `ViewCommand#execute()` which will execute the command. It will retrieve the task list
+
+by calling `ModelManager#getFilteredTaskList()` and retrieve the first `Task` from this list.
+
+Step 4: Afterwards, the `ViewCommand` will call `Task#getPeople()` to obtain the list of people associated with the `Task` and pass this list as an argument to
+`ModelManager#updateFilteredPersonList()` which will proceed to update the UI.
+
+The following sequence diagram shows how the view task operation works:
+
+
+
:information_source: **Note:** The lifeline for `EditTaskCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
#### Design considerations:
-**Aspect: How undo & redo executes:**
+**Aspect:** How should the results be displayed in the *Contact* column when no contacts are associated with the task:
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+* **Alternative 1 (current choice):** Continue displaying the current list of people.
+ * Pros: Reduce commands required by user to populate and use the column for input.
+ * Cons: May be confusing to user.
+
+* **Alternative 2:** Display an empty list.
+ * Pros: Clearly inform the users that the task has no people associate with it.
+ * Cons: Requires more commands by the user in order to use the column again.
+
+### Find Task feature
+The find task mechanism is facilitated by `FindTaskCommand`, `FilterCommandParser`, `ModelManager` and `Task`. Additionally, it implements the following operation:
+* `FilterCommandParser#parse()` — Parses the arguments provided by the users into a command to be executed.
+* `FindTaskCommand#execute()` — Executes the operations required to display the task that matches the search keywords.
+* `ModelManager#getFilteredTaskList()` — Gets the task list currently displayed as output to the user.
+* `ModelManager#updateFilteredTaskList()` — Updates the task list displayed as output to the user by providing the argument with a list of task.
-* **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.
+Given below is an example usage scenario and how the view task mechanism behaves at each step.
-_{more aspects and alternatives to be added}_
+Step 1. The user will enter the command `findt brush` to find all tasks that have the keyword `brush`. The command will be handled by
+`AddressBookParser#parseCommand()` which will create a `FilterCommandParser` object.
-### \[Proposed\] Data archiving
+Step 2. The `AddressBookParser` will call `FilterCommandParser#parse()` which will parse the command, returning a `FindTaskCommand` to be executed.
-_{Explain here how the data archiving feature will be implemented}_
+Step 3. The `LogicManager` will call `FindTaskCommand#execute()` which will execute the command. It will update the existing task list to only show
+the task with the search keywords by calling `ModelManager#updateFilteredTaskList()`.
+Step 4. Finally, the `FindTaskCommand` will return the command result of how many tasks were found, by calling `ModelManager#getFilteredTaskList().size()`
+
+
+
--------------------------------------------------------------------------------------------------------------------
@@ -256,100 +409,222 @@ _{Explain here how the data archiving feature will be implemented}_
### Product scope
**Target user profile**:
+* NUS computing professors that:
+ * need to manage a large number of contacts
+ * need to categorise these contacts (e.g. by role - Teaching Assistant, Student - or by class groups)
+ * need to keep track of relevant contact details (e.g. Github username, email)
+ * need to keep track of the people involved in each task
+ * prefer desktop apps over other types
+ * prefers typing to mouse interactions
+ * is reasonably comfortable using CLI apps
-* has a need to manage a significant number of contacts
-* 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**: NUS computing professors can easily organise their module-related tasks and relevant contact details in one place, boosting their efficiency and productivity.
### 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 |
-*{More to be added}*
+| 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 | manage my contacts more efficiently |
+| `* * *` | user | add a new task | manage my schedule more efficiently |
+| `* * *` | user | delete a person | remove entries that I no longer need |
+| `* * *` | user | delete a task | remove the task I no longer need |
+| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
+| `* * *` | user | assign people to tasks | easily keep track of the people involved in a task |
+| `* * *` | user | unassign people from tasks | easily maintain the list of people involved in a task |
+| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
+| `* * *` | user | tag my contacts | organize the contacts to look neater |
+| `* * *` | user | filter my tasks by name or date | locate tasks without having to go through the entire list |
+| `* * *` | user | view contact details | lookup important contact information |
+| `* * *` | user | tag tasks | organise my tasks |
+| `* * *` | user | assign and remove the task to/from my contacts | allocate my tasks to the specific contact as needed |
+| `* * *` | user | view the contacts assigned to a task | lookup the information of the people assigned to a task |
+| `* *` | lecturer | store the meeting links of my tasks | easily access the meetings when I need to |
+| `* *` | user | import contact data from a csv file | easily initialize my contacts without having to type hundreds of commands |
+| `* *` | user | generate emails of all the contacts assigned to a task | easily transfer the emails to my preferred email client to contact them |
+| `* *` | computing professor | get a contact's Github username | lookup their Github profiles |
+| `* *` | professor | group the students based on module | know which student is under which module |
+| `*` | professor | add graded component of the module | track students' performance of the module |
+
+
### 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 `NUS Classes` and the **Actor** is the `user`, unless specified otherwise)
-**Use case: Delete a person**
+### Use case: UC01 - Delete a person
**MSS**
-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
+1. User requests to list persons
+2. NUS Classes shows a list of persons
+3. User requests to delete a specific person in the list
+4. NUS Classes deletes the person
- Use case ends.
+Use case ends.
**Extensions**
* 2a. The list is empty.
-
- Use case ends.
+ * Use case ends.
* 3a. The given index is invalid.
+ * 3a1. NUS Classes shows an error message.
+ * Use case resumes from step 3.
+
- * 3a1. AddressBook shows an error message.
+### Use case: UC02 - Import contacts
- Use case resumes at step 2.
+**MSS**
+1. User requests to import contacts, providing the filepath of the source data file.
+2. NUS Classes adds the contacts to the contact list.
-*{More to be added}*
+Use case ends.
-### Non-Functional Requirements
+**Extensions**
-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.
-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.
+* 1a. NUS Classes can't find the file
+ * 1a1. NUS Classes shows an error message.
+ * Use case ends.
+* 1b. NUS Classes detects that the file is of invalid format or is a directory
+ * 1b1. NUS Classes shows an error message.
+ * Use case ends.
+* 1c. NUS Classes detects that some entries have invalid fields
+ * 1c1. NUS Classes informs the user of the invalid fields and the reason they are invalid
+ * 1c2. User fixes these fields
+ * Use case continues from step 1.
-*{More to be added}*
-### Glossary
+### Use case: UC03 - See all scheduled tasks
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+**MSS**
+1. User requests to see all tasks
+2. NUS Classes shows a list of scheduled tasks
---------------------------------------------------------------------------------------------------------------------
+Use case ends.
-## **Appendix: Instructions for manual testing**
-Given below are instructions to test the app manually.
+
+### Use case: UC04 - Schedule a task with a group
-
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
-testers are expected to do more *exploratory* testing.
+**MSS**
-
+1. User requests to create a task
+2. NUS Classes creates the task
+3. User requests to assign a contact to the task
+4. NUS Classes assigns the contact to the task
-### Launch and shutdown
+Use case ends.
-1. Initial launch
+**Extensions**
- 1. Download the jar file and copy into an empty folder
+* 1a. NUS Classes detects that compulsory arguments are omitted (e.g. name or date-time)
+ * 1a1. NUS Classes shows an error message.
+ * Use case ends.
+
+* 3a. NUS Classes detects that invalid task or contact index is provided
+ * 3a1. NUS Classes shows an error message.
+ * Use case continues from step 3.
+
+* 4a. User wishes to assign more contacts to the task
+ * Use case continues from step 3.
+
+
+### Use case: UC05 - Update a task
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+**MSS**
+
+1. User requests to see all tasks (UC03)
+2. User requests to update a task, providing the details of the fields to be edited and the index of the task
+3. NUS Classes updates the task
-1. Saving window preferences
+Use case ends.
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+**Extensions**
+
+* 2a. NUS Classes detects that the index provided is out of bounds/invalid
+ * 2a1. NUS Classes shows an error message.
+ * Use case ends.
+* 2b. NUS Classes detects that the fields provided are invalid
+ * 2b1. NUS Classes shows an error message.
+ * Use case ends.
+* 2c. NUS Classes detects that no optional arguments are provided
+ * 2c1. NUS Classes shows an error message.
+ * Use case ends.
+
+### Use case: UC06 - Generate emails of all contacts assigned to a task
+
+**MSS**
+
+1. User requests to see all tasks (UC03)
+2. User requests to generate the emails of all contacts a task, and provides the index of the task
+3. NUS Classes displays the emails and provides a button to copy the emails.
+
+Use case ends.
+
+**Extensions**
+
+* 1a. NUS Classes doesn't have any tasks created
+ * 1a1. NUS Classes shows an error message.
+ * Use case ends.
+* 2a. NUS Classes detects that the index provided is invalid
+ * 2a1. NUS Classes shows an error message.
+ * Use case ends.
+* 3a. User clicks the button to copy the emails.
+ * 3a1. User pastes the emails into their preferred email application
+ * Use case ends.
+
+### Use case: UC07 - Searching for tasks by name and/or tags
+
+**MSS**
- 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
+1. User specifies the keyword to search for.
+2. NUS Classes displays the tasks whose names and/or tags match the keyword.
-1. _{ more test cases … }_
+Use case ends.
+
+### Use case: UC08 - Searching for tasks by date range
+
+**MSS**
+
+1. User specifies the date range to search for.
+2. NUS Classes displays the tasks whose date(s) fit within the range.
+
+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 and/or tasks without a noticeable sluggishness in performance for 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.
+4. App should not exit or shut down without user explicit command.
+5. App should display relevant information within 2 seconds after user enter command.
+6. The information stored should not change without user explicit command.
+7. Should be able to download and use without installer.
+8. Should be able to work and store information without any third party database system.
+
+## **Appendix: Instructions for manual testing**
+
+Given below are instructions to test the app manually.
+
+### Launch and shutdown
+
+1. Initial launch
+
+ 1. Download the jar file and copy into an empty folder
+
+ 2. Double-click the jar file Expected: Shows the GUI with a set of sample contacts and tasks.
+
+2. Shutdown
+ 1. Click the 'close' button or execute the `exit` command Expected: The app shuts down
+
### Deleting a person
@@ -358,20 +633,65 @@ testers are expected to do more *exploratory* testing.
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.
+ Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message.
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 … }_
+1. Data (contacts and tasks) should be automatically saved when the app is shut down.
+ 1. Test case: `addc n/Test Person p/123456 e/e1234@u.nus.edu u/test123`, then `exit` and restart the app. Expected: The created contact is saved and is shown.
+ 2. Test case: `addt tn/Test task dt/12-12-2020 1234`, then `exit` and restart the app. Expected: The created task is saved and is shown.
+
+### Importing contacts from a .csv file
+1. The import feature should work with a valid .csv file.
+ 1. Test case:
+ 1. Create a .csv file with the filename `test.csv` using a text editor and place it into the NUS Classes folder.
+ 2. The .csv file should contain the headers `Name,Phone,Github,Email,Tags`
+ 3. On the next line, add the sample contact `Alex Bean,91234567,alexbean@gmail.com,Lab 12F/Student`
+ 4. Execute the command `import fp/test.csv`
+ 5. The NUS Classes should inform you that the contact has been successfully imported, and the contact is displayed in the contact list.
+ 2. For advanced testing, you may rename the file and place it in a subdirectory.
+ * make sure to specify the filepath relative to the jar directory, specified according your OS's file system.
+
+
+
+## Prefix summary
+
+| Prefix | Meaning | Constraints | Used in these commands |
+|-----------|-------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
+| `n/` | Name of contact | Can only contain alphabetical letters and selected symbols like `/`, `-`, `SPACE` and `'`. | `addc`, `editc` |
+| `p/` | Phone number of contact OR Index of person assigned | For `PHONENUMBER`, it is only used in `addc` and `editc`. Only numbers are allowed and must contain at least 3 digits.
For `PERSONINDEX`, it is only used in `assign` and `unassign`. Only valid, positive non-zero integers are allowed. | `addc`, `editc` (as `PHONENUMBER`)
`assign`, `unassign` (as `PERSONINDEX`) |
+| `e/` | Email of contact | An email address should begin with a local part containing alphanumeric characters and these special characters: +_.-. The local part cannot start with a special character. This should be followed by a '@' and then a domain name.
The domain name should be made up of domain labels separated by periods, and must end with a domain label at least 2 characters long and each domain label can only consist of alphanumeric characters, separated only by hyphens, if any. | `addc`, `editc` |
+| `u/` | Github Username of contact | Github usernames can only consist of alphanumeric characters or hyphens as per Github conventions. | `addc`, `editc` |
+| `t/` | Tag of either contact or task | A Task cannot contain two duplicate tags. | `addc`, `editc`, `addt`, `editt` |
+| `INDEX` | Index of the task or contact specified | `INDEX` refers to the index number as listed in NUS Classes, e.g. `INDEX` of `1` would mean the first task/contact. | `editc`, `deletec`, `editt`, `assign`, `unassign`, `view`, `mark`, `unmark`, `deletet`, `gen` |
+| `KEYWORD` | Keyword of the task or contact. This can be the name or tag or tasks/contacts | `KEYWORD` is case-insensitive. Orders of `KEYWORD`s do not matter. Only full words will be matched. | `findc`, `findt` |
+| `tn/` | Task name of the task | Two tasks with the same `TASKNAME` is valid. | `addc`, `editc` |
+| `dt/` | Date and time of the task. Can include both start and end times. | Needs to be in the format dd-mm-yyyy hhmm | `addt`, `editt`, `findt` |
+| `z/` | Link to online meeting/video conferencing. | Only valid links are accepted, with `https://` or `http://`. | `addt`, `editt` |
+| `r/` | Interval and recurrence of the task. | Pre-set values like `daily`, `weekly`, `monthly`, `quarterly` and `annually` are accepted. Else, only positive non-zero integers are valid. | `addt` |
+| `all/` | Shows all tasks in the list. | Only used in `listt`. | `listt` |
+| `c/` | Shows all completed tasks in the list. | Only used in `listt`. | `listt` |
+| `nc/` | Shows all uncompleted tasks in the list. | Only used in `listt`. | `listt` |
+
+
+## **Glossary**
+
+| Term | Meaning |
+|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| API | Application Programming Interface. Enables different systems to interact with each other programmatically |
+| .csv | A plain text file containing a list of data, separated by commas |
+| MSS | Main Success Scenario, the action steps of a typical scenario where the goal is delivered |
+| NUS | National University of Singapore |
+| NUS Classes | The name of the application |
+| Mainstream OS | Windows, Linux, MacOS |
+| UI | User Interface, the means by which the user interacts with the system |
+| CLI | Command-line interface, which processes text-based commands from the user |
+| GUI | Graphical user interface, a visual way of interacting with a computer using items such as windows, icons and menus |
+| Jar | A JAR (Java ARchive) is a package file format typically used to aggregate many Java class files and associated metadata and resources (text, images, etc.) into one file for distribution. |
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..a08128fa17c 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -1,10 +1,6 @@
----
-layout: page
-title: Setting up and getting started
----
-
-* Table of Contents
-{:toc}
+## Table of Contents
+* [Setting up the project in your computer](#setting-up-the-project-in-your-computer)
+* [Before writing code](#before-writing-code)
--------------------------------------------------------------------------------------------------------------------
@@ -12,7 +8,6 @@ title: Setting up and getting started
## Setting up the project in your computer
:exclamation: **Caution:**
-
Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps.
@@ -23,7 +18,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
1. **Verify the setup**:
- 1. Run the `seedu.address.Main` and try a few commands.
+ 1. Run `seedu.address.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
--------------------------------------------------------------------------------------------------------------------
@@ -45,7 +40,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Learn the design**
- When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture).
+ When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [NUS Classes’ architecture](DeveloperGuide.md#architecture).
1. **Do the tutorials**
These tutorials will help you get acquainted with the codebase.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..ca14dd17724 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -2,11 +2,58 @@
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.
-
-* Table of Contents
-{:toc}
+NUS Classes is a desktop app for NUS Computing professors to manage their tasks and contacts. It includes task management features such as
+creating tasks, tagging tasks, assigning contacts to tasks, and marking tasks as complete or incomplete. It also includes contact management features such as finding contacts, assigning contacts to specific tasks and tagging contacts.
+
+NUS Classes also provides a simple alert feature for tasks by displaying tasks in different color based on the urgency of the task. Tasks that are overdue are marked as red, whereas, tasks that are nearing deadline are marked as yellow.
+
+NUS Classes is optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI).
+Using NUS Classes can get your contact management tasks done faster than traditional GUI apps, saving time on otherwise tedious administrative tasks.
+
+This User Guide has been conveniently catergorised into **Contact Features**, **Task Features** and **Other Features**. This will help you locate quickly the command that you want to know more about.
+
+Blue text boxes are additional tips and notes to enhance your experience with NUSClasses, while yellow text boxes are warnings that will cause NUSClasses to not function as intended. Graphics are also available to further aid us visually.
+
+Hope you'll be satisfied using NUSClasses :smile:
+
+
+
+## Table of Contents
+
+* [Quick Start](#quick-start)
+* [Features](#features)
+ * [Help](#viewing-help--help)
+ * [Contact Features](#contact-features)
+ * [Adding a contact](#adding-a-contact-addc)
+ * [Listing all contacts](#listing-all-contacts--listc)
+ * [Editing a contact](#editing-a-contact--editc)
+ * [Finding contacts](#finding-contacts-findc)
+ * [Deleting a contact](#deleting-a-contact--deletec)
+ * [Task Features](#task-features)
+ * [Adding a task](#adding-a-task-addt)
+ * [Listing tasks](#listing-tasks--listt)
+ * [Editing a task](#editing-a-task-editt)
+ * [Finding tasks](#finding-tasks-findt)
+ * [Finding tasks by name or tag](#1-finding-tasks-by-name-or-tag-findt)
+ * [Finding tasks by date](#2-finding-tasks-by-date-findt)
+ * [Assigning a contact to a task](#assigning-a-contact-to-a-task-assign)
+ * [Viewing contacts assigned to a task](#viewing-contacts-assigned-to-a-task-view)
+ * [Unassigning a contact from a task](#unassigning-a-contact-from-a-task-unassign)
+ * [Mark a task as done](#marking-a-task-as-done-mark)
+ * [Unmark a task as not done](#unmarking-a-task-as-not-done-unmark)
+ * [Deleting tasks](#deleting-a-task-deletet)
+ * [Generating emails of all contacts assigned to a task](#generating-the-emails-of-all-the-contacts-assigned-to-a-task-gen)
+ * [Other Features](#other-features)
+ * [Importing contacts from a data file](#importing-contacts-from-a-data-file--import)
+ * [Clearing all data](#clearing-all-data--clear)
+ * [Exiting the program](#exiting-the-program--exit)
+ * [Saving the data](#saving-the-data)
+ * [Editing the data file](#editing-the-data-file)
+* [FAQ](#faq)
+* [Prefix Summary](#prefix-summary)
+* [Command Summary](#command-summary)
+
+
--------------------------------------------------------------------------------------------------------------------
@@ -14,41 +61,46 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
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).
+1. Download the latest `NUSClasses.jar` from [here](https://github.com/AY2122S2-CS2103T-T12-4/tp/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Copy the file to the folder you want to use as the _main folder_ for your NUS Classes manager.
-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.
+1. Double-click the file to start the app. The GUI similar to below should appear in a few seconds. Note how the app contains some sample data.

-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.
+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.
+ * **`listc`** : Lists all contacts.
+
+ * **`addc`**`n/John Doe p/98765432 e/johnd@u.nus.edu u/john123 t/Schoolmate` : Adds a contact named `John Doe` to NUS Classes,
+ with email `johnd@u.nus.edu`, Github Username `john123` and tag `Schoolmate`
- * **`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.
+ * **`deletec`**`3` : Deletes the 3rd contact shown in the current contact list.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ * **`assign`**`1 p/ 2` : Assigns the contact at index 2 to the task at index 1.
- * **`clear`** : Deletes all contacts.
+ * **`clear`** : Clears all contacts and tasks from NUS Classes
* **`exit`** : Exits the app.
1. Refer to the [Features](#features) below for details of each command.
+
+
--------------------------------------------------------------------------------------------------------------------
-## Features
+# Features
**: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`.
+* Words in `UPPER_CASE` are the parameters to be supplied by you.
+ e.g. in `addc n/CONTACTNAME`, `CONTACTNAME` is a parameter which can be used as `add n/John Doe`.
* 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. `addc n/CONTACTNAME p/PHONENUMBER e/EMAIL u/GIT_USERNAME [t/TAG]…` can be used as `n/John Doe t/friend` or as `n/John Doe`.
* 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.
@@ -59,134 +111,572 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* 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.
+* Extraneous parameters for commands that do not take in parameters (such as `help`, `listc`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+* `INDEX` refers to a single non-zero positive integer only, and within the bounds of the number of contacts/number of tasks. Values such as `-1`, `abc`, or `2 abcdefg` are invalid.
+
+* Some examples have images of the usage of the commands in the User Interface. This is to allow you to easily visualize and understand what the commands do in that context.
+
+
+
### Viewing help : `help`
-Shows a message explaning how to access the help page.
+Shows a message with the link to the User Guide for reference.
+
+
+
+**Format**: `help`
+
+## Contact Features
+
+### Adding a contact: `addc`
+
+Adds a contact with basic details like name, phone number, email and Github Username. Tags are optional but can be added as well for easier contact management.
-
+**Format**: `addc n/CONTACTNAME p/PHONENUMBER e/EMAIL u/GITHUB_USERNAME [t/TAG]…`
-Format: `help`
+**Examples**:
+* `addc n/john p/12345678 e/john@nus.edu.sg u/john123 t/Schoolmate`
+* `addc n/mary p/87654321 e/mary@gmail.com u/maryCS t/Teammate t/Classmate`
-### Adding a person: `add`
-Adds a person to the address book.
+
:bulb: **Notes:**
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+* Contacts with the same names are allowed. Some modules may be big, so there may be students with the same name!
+
+* You can add multiple tags to a contact for easier management, as some students/TAs might be taking your other modules too. Just put t/ before every tag!
+
+* Make sure the contact's email is in the correct format, i.e. abd@gmail.com :white_check_mark:, abd@yahoo :x:
+
+* Make sure the contact's phone number is at least 3 digits long! Phone numbers with > 8 digits are allowed since international phone numbers have different numbers of digits.
+
+* Github usernames can only consist of alphanumeric characters or single hyphens, and cannot begin or end with a hyphen. Usernames such as `john123` and `jo-hn123` are valid, but usernames such as `john123-`, `-john123` and `jo--hn123` are invalid.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-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 contacts : `listc`
-### Listing all persons : `list`
+Shows a list of all contacts in NUS Classes.
-Shows a list of all persons in the address book.
+**Format**: `listc`
-Format: `list`
+
:bulb: **Tip:**
+You will find this command useful after using [`findc`](#finding-contacts-by-name-findc)
+
-### Editing a person : `edit`
+### Editing a contact : `editc`
-Edits an existing person in the address book.
+Edits an existing contact in NUS Classes.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+**Format**: `editc INDEX [n/NAME] [p/PHONE] [e/EMAIL] [u/GITHUB_USERNAME] [t/TAG]…`
-* 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, …
+* Edits the contact at the specified `INDEX`.
+* The index refers to the index number shown in the displayed contact list.
* 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.
-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.
+
:exclamation: **Caution:**
+* The index **must be a positive integer** 1, 2, 3, …
+
+* When editing tags, the existing tags of the contact will be removed i.e. adding of tags is not cumulative.
+
+
+**Examples**:
+* `editc 2 n/Betsy Crower t/` Edits the name of the 2nd contact to be `Betsy Crower` and clears all existing tags.
+* `editc 1 p/82223333 e/Joseph@comp.nus.edu.sg` Edits the phone number and email address of the 1st contact to be `82223333` and `Joseph@comp.nus.edu.sg` respectively.
+
+ 
+
+
:bulb: **Tip:**
+You can remove all the contact’s tags by typing `t/` without specifying any tags after it. Useful when removing outdated tags from a contact.
+
-### Locating persons by name: `find`
+### Finding contacts: `findc`
-Finds persons whose names contain any of the given keywords.
+Find contacts whose names and/or tags contain any of the given keywords.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+**Format**: `findc KEYWORD [MORE_KEYWORDS]…`
-* The search is case-insensitive. e.g `hans` will match `Hans`
+* `KEYWORD` can be used to search for matching words in both tags and names of contacts.
+* The search is case-insensitive. e.g `john` will match `John`
* 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`
+ e.g. `hans mary` will return `Hans Bo`, `Mary Sue`
+
+**Examples**:
+* `findc John` returns `john` and `John Doe`.
+* `findc brian lecturer` returns `Brian Chow`, `Joseph` who has a tag of `Lecturer`.
+
+ 
+
+
+### Deleting a contact : `deletec`
+
+Deletes the specified contact from NUS Classes.
+
+**Format**: `deletec INDEX`
+
+* Deletes the contact at the specified `INDEX`.
+* The index refers to the index number shown in the displayed contact list.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+**Examples**:
+* `listc` followed by `deletec 2` deletes the 2nd contact in NUS Classes.
+* `findc Joseph` followed by `deletec 1` deletes the 1st contact in the results of the `findc` command.
+
+ 
+ 
+
+
+
+## Task Features
+
+### Adding a task: `addt`
+
+Adds a task for a datetime with a tag.
+
+**Format**: `addt tn/TASKNAME dt/DATETIME [ENDDATETIME] [t/TAG]… [z/LINK] [r/INTERVAL RECURRENCE]`
+
+* The format for TIME is in `dd-MM-yyyy HHmm`.
+
+
:exclamation: **Caution:**
+The task that you are creating cannot have duplicate tags, tags are unique for that specific task e.g. `addt tn/Lecture dt/12-12-2022 1200 t/Weekly Lecture t/Weekly Lecture` would be invalid.
+
+
+
+
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- 
+:bulb: **Notes about `TASK`:**
+* Two Tasks with the same `TASKNAME` is valid. This is to allow for multiple tasks with different dates to be valid, such as `CS2103T Lecture`
+at `04/04/2022` and another `CS2103T Lecture` at `11/04/2022`.
+
+* You can create overdue Tasks (with `DATETIME` before today) if you wish to remind yourself of overdue tasks! :smiley:
+
+
+
+
+:bulb: **Notes about `INTERVAL RECURRENCE`:**
+
+* `INTERVAL` refers to the number of days for next occurrence of the task:
+_e.g. `INTERVAL` = 5 (days) for a task on 01-01-2022 would next occur on 05-01-2022._
+* For `INTERVAL`, the values `daily`,`weekly`,`monthly`, `quarterly` and `annually` are accepted.
+* `RECURRENCE` refers to how many cycles the task is to be repeated:
+_e.g. `RECURRENCE` = 5 (cycles) with an `INTERVAL` = 7 (equivalent to `weekly`) means that the task repeats weekly for 5 cycles._
+
+
+
+**Examples**:
+* `addt tn/Meeting dt/17-03-2022 1800 t/School` Adds a task called Meeting for `17th March 2022, 6pm` at School.
+* `addt tn/Consultation dt/19-03-2022 1500, 19-03-2022 1600` Adds a task called Consultation taking place from `19th March 2022 3-4pm`.
+* `addt tn/CS2103 Lecture dt/19-03-2022 1500, 19-03-2022 1600 z/https://nus-sg.zoom.us… r/weekly 12`
+Adds a task called CS2103 Lecture taking place from `19th March 2022 3-4pm` that occurs `weekly` for `12 cycles` with the `meeting link`.
+* `addt tn/Running dt/12-02-2022 1900 t/Track r/3 5` Adds a task called Running for `12th February 2022, 7pm` that occurs `every 3 days` for `5 cycles`.
+
+### Editing a task: `editt`
+Edits an existing task in the task list.
+
+**Format**: `editt INDEX [tn/TASKNAME] [dt/DATETIME, ENDDATETIME*] [z/LINK] [t/TAG]…`
+
+* Edits the task at the specified `INDEX`. The index refers to the index number shown in the displayed task list.
+* At least one of the optional fields must be provided.
+* Existing values will be updated to the input values.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+**Examples**:
+* `editt 1 tn/Meeting with TAs` Edits the name of the 1st displayed task to be `Meeting with TAs`.
+* `editt 2 tn/Meeting with Prof Tan dt/01-12-2022 1200, 01-12-2022 1300` Edits the name of the 2nd displayed task to be `Meeting with Profs Tan` and the date to be `01 Dec 2022, 12pm-1pm`.
+
+ 
+
+
+* `editt 1 dt/12-12-2022 1200, 12-12-2022 1400` Edits the datetime of the 1st displayed task to be on `12 Dec 2022, 12-2pm`.
+
+ 
+
+
+
+:bulb: **Tips:**
+
+* If there's no need to change a certain field you can leave it out!
+* The `ENDDATETIME` field is optional.
+
+
+### Finding tasks: `findt`
+
+You can find tasks either by 1) name and/or tags **OR** 2) date time range
+
+
:exclamation: **Caution:**
+
+If your `findt` command contains both name `KEYWORD` and date time prefix `dt/`,
+ the command will ignore the `KEYWORD` and `findt dt/` will take higher precedence.
+
+e.g. `findt meeting dt/12-12-2022 1200, 13-12-2022 1200` will be equivalent to
+`findt dt/12-12-2022 1200, 13-12-2022 1200`
+
+
+#### 1. Finding tasks by name or tag: `findt`
+
+Find tasks whose task names or tags contain any of the given keywords.
+
+**Format**: `findt KEYWORD [MORE_KEYWORDS]…`
+* `KEYWORD` can be used to search for matching words in both tags and names of tasks.
+* The search is case-insensitive. e.g `lecture` will match `Lecture`
+* The order of the keywords does not matter. e.g. `Lecture CS2103T` will match `CS2103T Lecture`
+* Only full words will be matched e.g. `Tut` will not match `Tutorial`
+* Tasks matching at least one keyword will be returned (i.e. `OR` search).
+ e.g. `Lecture Tutorial` will return `CS2103T Lecture`, `Tutorial 12`
+
+**Examples**:
+* `findt with` returns `Consultation with students` and `Meeting with invigilators`.
+* `findt week` returns all recurring instances of `CS2103T Lecture` as their tags contain `Week XX Lecture`.
+* `findt TAs lecture` returns `Meeting with TAs` and all recurrences of `CS2103T lecture`.
+
+ 
+
+
+:bulb: **Tip:**
+
+If you have many tasks, using `findt` will help you to keep organized!
+
+
+
+#### 2. Finding tasks by date: `findt`
+Find tasks whose task falls in between the given dates (inclusive).
+
+**Format**: `findt dt/DATETIME1, DATETIME2`
+
+* The ordering of date doesn't matter . e.g `dt/12-02-2022 0800, 13-03-2022 0800` will match `dt/13-02-2022 0800, 12-03-2022 0800`
+* `DATETIME1` and `DATETIME2` follows this format: _dd-MM-yyyy HHmm_
+* _dd_: Day; _MM_: Month; _yyyy_: Year; _HH_: Hour; _mm_: Minutes
+* `HHmm` is in 24-Hour format
+* Requires 2 date time inputs
+
+**Examples**:
+* `findt dt/14-04-2022 0900, 15-04-2022 0900` Finds all tasks in between `14th April 2022, 9am` and `15th April 2022, 9am`, inclusive.
+* `findt dt/15-02-2022, 13-02-2022` Finds all tasks in between `13th February 2022, 12mn` and `15th February 2022, 11:59pm`, inclusive.
+* `findt dt/20-12-2022, 21-12-2022 0900` Finds all tasks in between `20th December 2022 12mn` and `21st December 2022 9am`, inclusive.
+
+
+:bulb: **Tip:**
+
+`DATETIME1` and `DATETIME2` do not require `HHmm` if you wish to find tasks that fall on/in between the dates.
+
+
-### Deleting a person : `delete`
+### Assigning a contact to a task: `assign`
+Assigns a contact in the contact list to a task.
-Deletes the specified person from the address book.
+**Format**: `assign INDEX p/CONTACTINDEX`
-Format: `delete INDEX`
+* Assigns the contact at the specified `CONTACTINDEX` to the task at `INDEX`.
+* The indices refer to the index numbers shown in the corresponding displayed task/contact list.
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+**Examples**:
+* `assign 1 p/2` Assigns the 2nd contact in the contact list to the 1st task in the task list.
+
+ 
+
+### Viewing contacts assigned to a task: `view`
+
+Display all contacts assigned to a given task.
+
+**Format**: `view TASKINDEX`
+
+* View all the contact assigned to the task located the specified `INDEX`
+* The index refers to the index number shown in the displayed task list.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+**Examples**:
+* `view 2` will display all contacts assigned to the 1st task in the task list.
+
+ 
+
+
+### Unassigning a contact from a task: `unassign`
+Unassigns a contact in the contact list from a task.
+
+**Format**: `unassign INDEX p/CONTACTINDEX`
+
+* Unassigns the contact at the specified `CONTACTINDEX` to the task at `INDEX`.
+* The indices refer to the index numbers shown in the corresponding displayed task/contact list.
+* The `view` command can help you quickly identify which contacts are already assigned to a task.
+
+
+
+:exclamation: **Caution:**
* The index **must be a positive integer** 1, 2, 3, …
+* If the contact is not already assigned to the task, the operation will fail.
+
+
+**Examples**:
+* `unassign 1 p/2` Unassigns the 2nd contact in the contact list from the 1st task in the task list.
+
+ 
+
+
+### Marking a task as done: `mark`
+
+Marks the specified task from the task list as done.
+
+**Format**: `mark INDEX`
+
+* Marks the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+* Icon will display a green tick to show the task is marked.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+**Examples**:
+* `mark 2` marks the task at index 2 as done.
+
+ 
+
+### Unmarking a task as not done: `unmark`
+
+Unmarks the specified task from the task list as not done.
+
+**Format**: `unmark INDEX`
+
+* Unmarks the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+* Icon will display an empty white box to show the task is unmarked.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+**Examples**:
+* `unmark 2` unmarks the task at index 2 as not done.
+
+ 
+
+### Listing tasks : `listt`
+
+Shows a list of all the tasks in the task list as per the specified filtering options. `listt` has the three following formats:
+
+**Format**:
+
+`listt all/`
+Shows a list of all tasks in the task list.
+
+`listt c/`
+Shows a list of tasks that is marked as completed in the task list.
+
+`listt nc/`
+Shows a list of tasks that is not mark as completed in the task list.
+
+
+
+**:information_source: Information:**
+
+
If more than 1 prefixes is present, system will prioritise `all/` -> `nc/` -> `c/` .
+
+
+
+### Deleting a task: `deletet`
-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.
+Deletes the specified task from the task list.
-### Clearing all entries : `clear`
+**Format**: `deletet INDEX`
-Clears all entries from the address book.
+* Deletes the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+
+**Examples**:
+* `listt all/` followed by `deletet 1` lists out all the tasks in NUS Classes, then deletes the task at index 1.
+* `findt lecture` followed by `deletet 2` lists out all tasks with the keyword `lecture`, then deletes the task at index 2.
+ Finding the tasks by keyword `lecture`:
+ 
+
+ Deleting the lecture at index 2 `deletet 2`:
+ 
+
+
+### Generating the emails of all the contacts assigned to a task: `gen`
+
+Displays all the emails of all the contacts assigned to the specified task and displays a button to copy the emails into your clipboard.
+
+**Format**: `gen INDEX`
+
+* Displays all the emails of the contacts assigned to the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+
+
:exclamation: **Caution:**
+The index **must be a positive integer** 1, 2, 3, …
+
+
+
+**Examples**:
+* `gen 1` displays all the emails of the contacts assigned to the task at index 1.
+
+ 
+
+
+
+
+# Other Features
+
+### Clearing all data : `clear`
+
+Clears all contacts and tasks from NUS Classes.
+
+**Format**: `clear`
+
+
:exclamation: **Caution:**
+Be careful! This action is irreversible.
+
-Format: `clear`
### Exiting the program : `exit`
Exits the program.
-Format: `exit`
+**Format**: `exit`
### Saving the data
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+NUS Classes data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
### Editing the data file
-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.
+NUS Classes data are saved into two JSON files:
+* `[JAR file location]/data/addressbook.json` for contact data
+* `[JAR file location]/data/tasklist.json` for task data
+
+Advanced users are welcome to update data directly by editing these data files.
: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.
+If your changes to the data file makes its format invalid, NUS Classes will discard all data and start with an empty data file at the next run.
-### Archiving data files `[coming in v2.0]`
+### Importing contacts from a data file : `import`
+
+Imports a list of contacts from a .csv file.
+
+**Format**: `import fp/FILEPATH`
+
+* The .csv file must contain the following 5 headers (Name, Phone, Email, Github, Tags). Any other headers will be ignored.
+* If there are repeated headers, the first one will be considered.
+* Tags in the .csv file should be separated with a `/` character.
+* Invalid csv entries (e.g. due to invalid or duplicate fields) will be skipped, but valid entries will still be added.
+
+**Examples**:
+* `import fp/data/data.csv` (macOS/Linux) / `import fp/data\data.csv` (Windows) will import all valid entries from the `data.csv` folder in the `/data` directory of the NUS Classes folder.
+* `import fp/contacts.csv` (all supported platforms) will import all valid entries from the `contacts.csv file` in the NUS Classes root folder.
+
+ 
+
+ 
+
+
+
+### User-friendly date display
+
+Instead of always displaying dates in full (e.g. DD MM YYYY), our dates will be displayed based on the current day to be more user-friendly. Here's a reference if you get confused:
+
+| Displayed date | Explanation |
+|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `Today` | The current calendar day. |
+| `Tomorrow` | The next calendar day. |
+| Day of week (e.g. `Mon`, `Sat`) | The next occurrence of that day of week. Examples: - If today is `7 Apr, Thu`, `Tue` will refer to the _next_ Tuesday `12 Apr, Tue` - If today is `4 Apr, Mon`, `Mon` will refer to the _next_ Monday `11 Apr, Mon` |
+| Day and month (without year) | The day of the current calendar year (e.g. if the current calendar year is 2022, `11 Apr` refers to `11 Apr 2022` |
+| Day, month and year | - |
+
-_Details coming soon ..._
+
--------------------------------------------------------------------------------------------------------------------
## FAQ
**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.
+**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 NUS Classes home folder.
+
+**Q**: Do I need to have extensive knowledge about Command Line Interface (CLI) to use this?
+**A**: Nope! Simply refer to the format given in our [Command Summary](#command-summary) and our [Features Section](#features) for guidance!
+
+**Q**: I have further questions about NUS Classes. Where do I ask?
+**A**: Open an Issue in our team's [Issue Tracker](https://github.com/AY2122S2-CS2103T-T12-4/tp/issues) and we'll look into it! :smiley:
+
+**Q**: Who do I contact if I want to be part of the Developer team?
+**A**: You can email us at _e0544441@u.nus.edu_ with the subject `Interest in joining NUSClasses Developer Team`
+
+**Q**: What do I do to update the app?
+**A**: Uninstall the current version of the app. Then, download the latest version of `NUSClasses.jar` from our [GitHub releases](https://github.com/AY2122S2-CS2103T-T12-4/tp/releases)
+
+
--------------------------------------------------------------------------------------------------------------------
+## Prefix summary
+
+| Prefix | Meaning | Constraints | Used in these commands: |
+|-----------|-------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
+| `n/` | Name of contact | Can only contain alphabetical letters and selected symbols like `/`, `-`, `SPACE` and `'`. | `addc`, `editc` |
+| `p/` | Phone number of contact OR Index of person assigned | For `PHONENUMBER`, it is only used in `addc` and `editc`. Only numbers are allowed and must contain at least 3 digits.
For `PERSONINDEX`, it is only used in `assign` and `unassign`. Only valid, positive non-zero integers are allowed. | `addc`, `editc` (as `PHONENUMBER`)
`assign`, `unassign` (as `PERSONINDEX`) |
+| `e/` | Email of contact | An email address should begin with a local part containing alphanumeric characters and these special characters: +_.-. The local part cannot start with a special character. This should be followed by a '@' and then a domain name.
The domain name should be made up of domain labels separated by periods, and must end with a domain label at least 2 characters long and each domain label can only consist of alphanumeric characters, separated only by hyphens, if any. | `addc`, `editc` |
+| `u/` | Github Username of contact | Github usernames can only consist of alphanumeric characters or hyphens as per Github conventions. | `addc`, `editc` |
+| `t/` | Tag of either contact or task | A Task cannot contain two duplicate tags. | `addc`, `editc`, `addt`, `editt` |
+| `INDEX` | Index of the task or contact specified | `INDEX` refers to the index number as listed in NUS Classes, e.g. `INDEX` of `1` would mean the first task/contact. | `editc`, `deletec`, `editt`, `assign`, `unassign`, `view`, `mark`, `unmark`, `deletet`, `gen` |
+| `KEYWORD` | Keyword of the task or contact. This can be the name or tag or tasks/contacts | `KEYWORD` is case-insensitive. Orders of `KEYWORD`s do not matter. Only full words will be matched. | `findc`, `findt` |
+| `tn/` | Task name of the task | Two tasks with the same `TASKNAME` is valid. | `addc`, `editc` |
+| `dt/` | Date and time of the task. Can include both start and end times. | Needs to be in the format dd-mm-yyyy hhmm | `addt`, `editt`, `findt` |
+| `z/` | Link to online meeting/video conferencing. | Only valid links are accepted, with `https://` or `http://`. | `addt`, `editt` |
+| `r/` | Interval and recurrence of the task. | Pre-set values like `daily`, `weekly`, `monthly`, `quarterly` and `annually` are accepted. Else, only positive non-zero integers are valid. | `addt` |
+| `all/` | Shows all tasks in the list. | Only used in `listt`. | `listt` |
+| `c/` | Shows all completed tasks in the list. | Only used in `listt`. | `listt` |
+| `nc/` | Shows all uncompleted tasks in the list. | Only used in `listt`. | `listt` |
+
+
+
+
## 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`
+| Action | Format |
+|------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
+| [**Add Contact**](#adding-a-contact-addc) | `addc n/NAME p/PHONE_NUMBER e/EMAIL u/GIT_USERNAME [t/TAG]…` |
+| [**List Contacts**](#listing-all-contacts--listc) | `listc` |
+| [**Edit Contact**](#editing-a-contact--editc) | `editc INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [u/GITHUBUSERID] [t/TAG]…` |
+ | [**Find Contacts**](#finding-contacts-findc) | `findc KEYWORD [MORE_KEYWORDS]…` |
+| [**Delete**](#deleting-a-contact--deletec) | `deletec INDEX` |
+| [**Add Task**](#adding-a-task-addt) | `addt tn/TASKNAME dt/DATETIME[, ENDDATETIME] [t/TAG]… [z/LINK] [r/INTERVAL RECURRENCE]` |
+| [**List Tasks**](#listing-tasks--listt) | `listt all\` or `listt nc/` or `listt c/` |
+| [**Edit Task**](#editing-a-task-editt) | `editt INDEX [tn/TASKNAME] [dt/DATETIME, ENDDATETIME*] [z/LINK] [t/TAG]` |
+| [**Find Task by name or tag**](#1-finding-tasks-by-name-or-tag-findt) | `findt KEYWORD [MORE_KEYWORDS]…` |
+| [**Find Task by Date**](#2-finding-tasks-by-date-findt) | `findt dt/DATETIME1, DATETIME2` |
+| [**Assign contact To Task**](#assigning-a-contact-to-a-task-assign) | `assign INDEX p/CONTACTINDEX` |
+| [**View contacts Assigned to Task**](#viewing-contacts-assigned-to-a-task-view) | `view INDEX` |
+| [**Unassign contact From Task**](#unassigning-a-contact-from-a-task-unassign) | `unassign INDEX p/CONTACTINDEX` |
+| [**Mark Task**](#marking-a-task-as-done-mark) | `mark INDEX` |
+| [**Unmark Task**](#unmarking-a-task-as-not-done-unmark) | `unmark INDEX` |
+| [**Delete Task**](#deleting-a-task-deletet) | `deletet INDEX` |
+| [**Generating emails of all the contacts Assigned to task**](#generating-the-emails-of-all-the-contacts-assigned-to-a-task-gen) | `gen INDEX` |
+| [**Clear all data**](#clearing-all-data--clear) | `clear` |
+| [**Import contacts**](#importing-contacts-from-a-data-file--import) | `import fp/FILEPATH` |
+| [**Exit**](#exiting-the-program--exit) | `exit` |
+
+
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..ee417a1226b 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "NUS Classes"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2122S2-CS2103T-T12-4/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..0ec3e3e4bfa 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -127,7 +127,7 @@ a {
.social-media-list &:hover {
text-decoration: none;
- .username {
+ .gitUsername {
text-decoration: underline;
}
}
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "NUS Classes";
font-size: 32px;
}
}
diff --git a/docs/diagrams/Activity Diagram/DeleteTaskOutcome.puml b/docs/diagrams/Activity Diagram/DeleteTaskOutcome.puml
new file mode 100644
index 00000000000..2da64825c51
--- /dev/null
+++ b/docs/diagrams/Activity Diagram/DeleteTaskOutcome.puml
@@ -0,0 +1,9 @@
+@startuml
+(*) --> "DeleteTask command executed"
+if "Delete task successful" then
+ -left->[true] "Message display: Deleted Task: Index"
+ -> (*)
+else
+ -right->[false] "throw CommandException"
+ -->(*)
+@enduml
diff --git a/docs/diagrams/Activity Diagram/EditTaskOutcome.puml b/docs/diagrams/Activity Diagram/EditTaskOutcome.puml
new file mode 100644
index 00000000000..dc7fe184fd8
--- /dev/null
+++ b/docs/diagrams/Activity Diagram/EditTaskOutcome.puml
@@ -0,0 +1,9 @@
+@startuml
+(*) --> "EditTask command executed"
+if "Edit task successful" then
+ -left->[true] "Message display: Update Task: 'Index'"
+ -> (*)
+else
+ -right->[false] "throw CommandException"
+ -->(*)
+@enduml
diff --git a/docs/diagrams/AddTaskSequenceDiagram.puml b/docs/diagrams/AddTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..7bb898c70e4
--- /dev/null
+++ b/docs/diagrams/AddTaskSequenceDiagram.puml
@@ -0,0 +1,69 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddTaskCommandParser" as AddTaskCommandParser LOGIC_COLOR
+participant ":AddTaskCommand" as AddTaskCommand 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("addt 1...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("addt 1...")
+activate AddressBookParser
+
+create AddTaskCommandParser
+AddressBookParser -> AddTaskCommandParser : AddTaskCommandParser()
+activate AddTaskCommandParser
+
+AddTaskCommandParser --> AddressBookParser
+deactivate AddTaskCommandParser
+
+AddressBookParser -> AddTaskCommandParser : parse("...")
+activate AddTaskCommandParser
+
+create AddTaskCommand
+AddTaskCommandParser -> AddTaskCommand : AddTaskCommand(...)
+activate AddTaskCommand
+
+AddTaskCommand --> AddTaskCommandParser
+deactivate AddTaskCommand
+
+AddTaskCommandParser --> AddressBookParser
+deactivate AddTaskCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddTaskCommandParser -[hidden]-> AddressBookParser
+destroy AddTaskCommandParser
+
+AddressBookParser --> LogicManager
+deactivate AddressBookParser
+
+LogicManager -> AddTaskCommand : execute()
+activate AddTaskCommand
+
+AddTaskCommand -> Model : addTask(...)
+activate Model
+
+Model --> AddTaskCommand
+deactivate Model
+
+create CommandResult
+AddTaskCommand -> CommandResult : CommandResult(...)
+activate CommandResult
+
+CommandResult --> AddTaskCommand
+deactivate CommandResult
+
+AddTaskCommand --> LogicManager : result
+deactivate AddTaskCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..bfef152a898 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -7,10 +7,10 @@ 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 : "deletec 1"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("deletec 1")
activate logic LOGIC_COLOR
logic -[LOGIC_COLOR]> model : deletePerson(p)
diff --git a/docs/diagrams/DeleteModelSequenceDiagram.puml b/docs/diagrams/DeleteModelSequenceDiagram.puml
new file mode 100644
index 00000000000..2e898a943ff
--- /dev/null
+++ b/docs/diagrams/DeleteModelSequenceDiagram.puml
@@ -0,0 +1,32 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":AddressBook" as AddressBook MODEL_COLOR
+participant ":TaskList" as TaskList MODEL_COLOR
+end box
+
+
+[-> DeleteCommand : execute()
+activate DeleteCommand
+
+DeleteCommand -> Model : deletePerson(target)
+activate Model
+
+Model -> AddressBook : removePerson(target)
+activate AddressBook
+
+AddressBook --> Model
+deactivate AddressBook
+
+Model -> TaskList : removePerson(target)
+activate TaskList
+
+TaskList --> Model
+deactivate TaskList
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 1dc2311b245..4bce125f07e 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -13,10 +13,10 @@ box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
end box
-[-> LogicManager : execute("delete 1")
+[-> LogicManager : execute("deletec 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
+LogicManager -> AddressBookParser : parseCommand("deletec 1")
activate AddressBookParser
create DeleteCommandParser
diff --git a/docs/diagrams/DeleteTaskSequenceDiagram.puml b/docs/diagrams/DeleteTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..29baa2f8cc5
--- /dev/null
+++ b/docs/diagrams/DeleteTaskSequenceDiagram.puml
@@ -0,0 +1,69 @@
+@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 ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("deletet 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("deletet 1")
+activate AddressBookParser
+
+create DeleteTaskCommandParser
+AddressBookParser -> DeleteTaskCommandParser : DeleteTaskCommandParser()
+activate DeleteTaskCommandParser
+
+DeleteTaskCommandParser --> AddressBookParser
+deactivate DeleteTaskCommandParser
+
+AddressBookParser -> DeleteTaskCommandParser : parse("1")
+activate DeleteTaskCommandParser
+
+create DeleteTaskCommand
+DeleteTaskCommandParser -> DeleteTaskCommand : DeleteTaskCommand(1)
+activate DeleteTaskCommand
+
+DeleteTaskCommand --> DeleteTaskCommandParser
+deactivate DeleteTaskCommand
+
+DeleteTaskCommandParser --> AddressBookParser
+deactivate DeleteTaskCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+DeleteTaskCommandParser -[hidden]-> AddressBookParser
+destroy DeleteTaskCommandParser
+
+AddressBookParser --> LogicManager
+deactivate AddressBookParser
+
+LogicManager -> DeleteTaskCommand : execute()
+activate DeleteTaskCommand
+
+DeleteTaskCommand -> Model : deleteTask(1)
+activate Model
+
+Model --> DeleteTaskCommand
+deactivate Model
+
+create CommandResult
+DeleteTaskCommand -> CommandResult : CommandResult(...)
+activate CommandResult
+
+CommandResult --> DeleteTaskCommand
+deactivate CommandResult
+
+DeleteTaskCommand --> LogicManager : result
+deactivate DeleteTaskCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/EditTaskSequenceDiagram.puml b/docs/diagrams/EditTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..d76423c917e
--- /dev/null
+++ b/docs/diagrams/EditTaskSequenceDiagram.puml
@@ -0,0 +1,122 @@
+@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 "ParserUtil" as ParserUtil LOGIC_COLOR
+participant "editTaskDescriptor:EditTaskDescriptor" as EditTaskDescriptor LOGIC_COLOR
+participant ":EditTaskCommand" as EditTaskCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+ -> LogicManager : parseCommand("editt ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("editt ...")
+activate AddressBookParser
+
+create EditTaskCommandParser
+AddressBookParser -> EditTaskCommandParser : EditTaskCommandParser()
+activate EditTaskCommandParser
+
+EditTaskCommandParser --> AddressBookParser
+deactivate EditTaskCommandParser
+
+AddressBookParser -> EditTaskCommandParser : parse(...)
+activate EditTaskCommandParser
+
+EditTaskCommandParser -> ParserUtil : parseIndex(...)
+activate ParserUtil
+
+ParserUtil --> EditTaskCommandParser : index
+deactivate ParserUtil
+
+create EditTaskDescriptor
+EditTaskCommandParser -> EditTaskDescriptor : EditTaskDescriptor()
+activate EditTaskDescriptor
+
+EditTaskDescriptor --> EditTaskCommandParser
+deactivate EditTaskDescriptor
+
+opt Task name present
+ EditTaskCommandParser -> EditTaskDescriptor : setName(...)
+ activate EditTaskDescriptor
+ EditTaskDescriptor --> EditTaskCommandParser
+ deactivate EditTaskDescriptor
+end
+
+opt DateTime present
+ EditTaskCommandParser -> EditTaskDescriptor : setDate(...)
+ activate EditTaskDescriptor
+ EditTaskDescriptor --> EditTaskCommandParser
+ deactivate EditTaskDescriptor
+end
+
+opt Tags present
+EditTaskCommandParser -> ParserUtil : parseTags(...)
+activate ParserUtil
+ParserUtil --> EditTaskCommandParser : Tags
+deactivate ParserUtil
+EditTaskCommandParser -> EditTaskDescriptor : setTags(...)
+activate EditTaskDescriptor
+EditTaskDescriptor --> EditTaskCommandParser
+deactivate EditTaskDescriptor
+end
+
+opt Link present
+EditTaskCommandParser -> ParserUtil : parseLink(...)
+activate ParserUtil
+ParserUtil --> EditTaskCommandParser : Link
+deactivate ParserUtil
+EditTaskCommandParser -> EditTaskDescriptor : setLink(...)
+activate EditTaskDescriptor
+EditTaskDescriptor --> EditTaskCommandParser
+deactivate EditTaskDescriptor
+end
+
+create EditTaskCommand
+EditTaskCommandParser -> EditTaskCommand : EditTaskCommand(index, editTaskDescriptor)
+activate EditTaskCommand
+
+EditTaskCommand --> EditTaskCommandParser
+deactivate EditTaskCommand
+
+EditTaskCommandParser --> AddressBookParser
+deactivate EditTaskCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+EditTaskCommandParser -[hidden]-> AddressBookParser
+destroy EditTaskCommandParser
+
+AddressBookParser --> LogicManager
+deactivate AddressBookParser
+
+LogicManager -> EditTaskCommand : execute()
+activate EditTaskCommand
+
+EditTaskCommand -> Model : setTask(...)
+activate Model
+
+Model --> EditTaskCommand
+EditTaskCommand -> Model : updateFilteredTaskList(...)
+Model --> EditTaskCommand
+deactivate Model
+
+create CommandResult
+EditTaskCommand -> CommandResult : CommandResult(...)
+activate CommandResult
+
+CommandResult --> EditTaskCommand
+deactivate CommandResult
+
+EditTaskCommand --> LogicManager : result
+deactivate EditTaskCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 4439108973a..d8abdbebaca 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -7,44 +7,57 @@ skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
+Class "<>\nReadOnlyTaskList" as ReadOnlyTaskList
Class "<>\nModel" as Model
Class AddressBook
Class ModelManager
Class UserPrefs
+Class TaskList
+Class Task
Class UniquePersonList
Class Person
-Class Address
+Class Username
Class Email
Class Name
Class Phone
Class Tag
+Class Link
}
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+AddressBook .up..|> ReadOnlyAddressBook
+TaskList .up..|> ReadOnlyTaskList
ModelManager .up.|> Model
-Model .right.> ReadOnlyUserPrefs
-Model .left.> ReadOnlyAddressBook
+Model .down.> ReadOnlyUserPrefs
+Model .down.> ReadOnlyAddressBook
+Model .down.> ReadOnlyTaskList
ModelManager -left-> "1" AddressBook
-ModelManager -right-> "1" UserPrefs
+ModelManager -up-> "1" UserPrefs
+ModelManager -right-> "1" TaskList
UserPrefs .up.|> ReadOnlyUserPrefs
+TaskList --> "~* " Task
+
AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
+UniquePersonList --> "~* " Person
Person *--> Name
Person *--> Phone
Person *--> Email
-Person *--> Address
+Person *--> Username
Person *--> "*" Tag
+Task -> "*" Tag
+Task -down-> "0 .. 1" Link
+
Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+Task -[hidden]left-----> Person
+
-ModelManager -->"~* filtered" Person
+ModelManager -->"~*" Person
+ModelManager -->"~*" Task
@enduml
diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml
index 0c7424de6e0..1d0a72ba1ef 100644
--- a/docs/diagrams/ParserClasses.puml
+++ b/docs/diagrams/ParserClasses.puml
@@ -35,4 +35,5 @@ XYZCommandParser ..> ParserUtil
ParserUtil .down.> Prefix
ArgumentTokenizer .down.> Prefix
XYZCommand -up-|> Command
+ArgumentMultimap ..> Prefix
@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 760305e0e58..db3bbcb8a77 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -19,20 +19,30 @@ Class "<>\nAddressBookStorage" as AddressBookStorage
Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
-Class JsonAdaptedTag
}
+package "TaskList Storage" #F4F6F6{
+Class "<>\nTaskListStorage" as TaskListStorage
+Class JsonTaskListStorage
+Class JsonSerializableTaskList
+Class JsonAdaptedTask
+}
+
+Class JsonAdaptedTag
+
}
Class HiddenOutside #FFFFFF
HiddenOutside ..> Storage
StorageManager .up.|> Storage
-StorageManager -up-> "1" UserPrefsStorage
-StorageManager -up-> "1" AddressBookStorage
+StorageManager -down-> "1" UserPrefsStorage
+StorageManager -right-> "1" AddressBookStorage
+StorageManager -left-> "1" TaskListStorage
-Storage -left-|> UserPrefsStorage
-Storage -right-|> AddressBookStorage
+Storage -down-|> UserPrefsStorage
+Storage -down-|> AddressBookStorage
+Storage -down-|>TaskListStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
@@ -40,4 +50,9 @@ JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonTaskListStorage .up.|> TaskListStorage
+JsonTaskListStorage ..> JsonSerializableTaskList
+JsonSerializableTaskList --> "*" JsonAdaptedTask
+JsonAdaptedTask --> "*" JsonAdaptedTag
+
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..54b187942c5 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -12,7 +12,9 @@ Class MainWindow
Class HelpWindow
Class ResultDisplay
Class PersonListPanel
+Class TaskListPanel
Class PersonCard
+Class TaskCard
Class StatusBarFooter
Class CommandBox
}
@@ -33,10 +35,12 @@ UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "1" TaskListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
PersonListPanel -down-> "*" PersonCard
+TaskListPanel -down-> "*" TaskCard
MainWindow -left-|> UiPart
@@ -44,10 +48,13 @@ ResultDisplay --|> UiPart
CommandBox --|> UiPart
PersonListPanel --|> UiPart
PersonCard --|> UiPart
+TaskListPanel --|> UiPart
+TaskCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
-PersonCard ..> Model
+PersonCard ...> Model
+TaskCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
diff --git a/docs/diagrams/ViewSequenceDiagram.puml b/docs/diagrams/ViewSequenceDiagram.puml
new file mode 100644
index 00000000000..41798215a1c
--- /dev/null
+++ b/docs/diagrams/ViewSequenceDiagram.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 ":ViewCommandParser" as ViewCommandParser LOGIC_COLOR
+participant ":ViewCommand" as ViewCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Task" as Task MODEL_COLOR
+
+end box
+
+[-> LogicManager : execute("view 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("view 1")
+activate AddressBookParser
+
+create ViewCommandParser
+AddressBookParser -> ViewCommandParser : ViewCommandParser()
+activate ViewCommandParser
+
+ViewCommandParser --> AddressBookParser
+deactivate ViewCommandParser
+
+AddressBookParser -> ViewCommandParser : parse("1")
+activate ViewCommandParser
+
+create ViewCommand
+ViewCommandParser -> ViewCommand : ViewCommand(1)
+activate ViewCommand
+
+ViewCommand --> ViewCommandParser
+deactivate ViewCommand
+
+ViewCommandParser --> AddressBookParser
+deactivate ViewCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ViewCommandParser -[hidden]-> AddressBookParser
+destroy ViewCommandParser
+
+AddressBookParser --> LogicManager
+deactivate AddressBookParser
+
+LogicManager -> ViewCommand : execute()
+activate ViewCommand
+
+ViewCommand -> Model : getFilteredTaskList()
+activate Model
+
+Model --> ViewCommand
+deactivate Model
+
+ViewCommand -> Task : getPeople()
+activate Task
+
+Task --> ViewCommand : x
+deactivate Task
+
+ViewCommand -> Model : updateFilteredPersonList(x)
+
+create CommandResult
+ViewCommand -> CommandResult : CommandResult(...)
+activate CommandResult
+
+CommandResult --> ViewCommand
+deactivate CommandResult
+
+ViewCommand --> LogicManager : result
+deactivate ViewCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/images/Activity Diagram/DeleteTaskOutcome.png b/docs/images/Activity Diagram/DeleteTaskOutcome.png
new file mode 100644
index 00000000000..189f6c4a9ad
Binary files /dev/null and b/docs/images/Activity Diagram/DeleteTaskOutcome.png differ
diff --git a/docs/images/Activity Diagram/EditTaskOutcome.png b/docs/images/Activity Diagram/EditTaskOutcome.png
new file mode 100644
index 00000000000..b3698af2fdf
Binary files /dev/null and b/docs/images/Activity Diagram/EditTaskOutcome.png differ
diff --git a/docs/images/AddTaskSequenceDiagram.png b/docs/images/AddTaskSequenceDiagram.png
new file mode 100644
index 00000000000..46eaeaa0c30
Binary files /dev/null and b/docs/images/AddTaskSequenceDiagram.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..162f7b22fd9 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/DeleteModelSequenceDiagram.png b/docs/images/DeleteModelSequenceDiagram.png
new file mode 100644
index 00000000000..179c2a77709
Binary files /dev/null and b/docs/images/DeleteModelSequenceDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index fa327b39618..7850b7ff384 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/DeleteTaskSequenceDiagram.png b/docs/images/DeleteTaskSequenceDiagram.png
new file mode 100644
index 00000000000..1fa917be5a5
Binary files /dev/null and b/docs/images/DeleteTaskSequenceDiagram.png differ
diff --git a/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState0.png b/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState0.png
new file mode 100644
index 00000000000..f9f6fdb1cbf
Binary files /dev/null and b/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState0.png differ
diff --git a/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState1.png b/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState1.png
new file mode 100644
index 00000000000..b906c2cbefc
Binary files /dev/null and b/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState1.png differ
diff --git a/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState2.png b/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState2.png
new file mode 100644
index 00000000000..2e722263432
Binary files /dev/null and b/docs/images/EditTaskDiagram/EditTaskSequenceDiagramState2.png differ
diff --git a/docs/images/EditTaskSequenceDiagram.png b/docs/images/EditTaskSequenceDiagram.png
new file mode 100644
index 00000000000..8d4857cca34
Binary files /dev/null and b/docs/images/EditTaskSequenceDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 04070af60d8..f4ad1760965 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png
index e7b4c8880cd..2977bae6ce8 100644
Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 2533a5c1af0..c48301d7563 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..74c67d1c4ba 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..6ae64810b7c 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/ViewSequenceDiagram.png b/docs/images/ViewSequenceDiagram.png
new file mode 100644
index 00000000000..5dfedd83fd3
Binary files /dev/null and b/docs/images/ViewSequenceDiagram.png differ
diff --git a/docs/images/adrianongjj.png b/docs/images/adrianongjj.png
new file mode 100644
index 00000000000..3e280cb22a3
Binary files /dev/null and b/docs/images/adrianongjj.png differ
diff --git a/docs/images/assignCommandShowcase.png b/docs/images/assignCommandShowcase.png
new file mode 100644
index 00000000000..c76ea0bfcc5
Binary files /dev/null and b/docs/images/assignCommandShowcase.png differ
diff --git a/docs/images/brian16600.png b/docs/images/brian16600.png
new file mode 100644
index 00000000000..73c69d2dcf4
Binary files /dev/null and b/docs/images/brian16600.png differ
diff --git a/docs/images/deletecCommandShowcase1.png b/docs/images/deletecCommandShowcase1.png
new file mode 100644
index 00000000000..18eb1b52234
Binary files /dev/null and b/docs/images/deletecCommandShowcase1.png differ
diff --git a/docs/images/deletecCommandShowcase2.png b/docs/images/deletecCommandShowcase2.png
new file mode 100644
index 00000000000..4b9596bec9d
Binary files /dev/null and b/docs/images/deletecCommandShowcase2.png differ
diff --git a/docs/images/deletetCommandShowcase2.png b/docs/images/deletetCommandShowcase2.png
new file mode 100644
index 00000000000..af1068b7806
Binary files /dev/null and b/docs/images/deletetCommandShowcase2.png differ
diff --git a/docs/images/deletettCommandShowcase1.png b/docs/images/deletettCommandShowcase1.png
new file mode 100644
index 00000000000..9c08e93cb29
Binary files /dev/null and b/docs/images/deletettCommandShowcase1.png differ
diff --git a/docs/images/editcCommandShowcase.png b/docs/images/editcCommandShowcase.png
new file mode 100644
index 00000000000..da207b17759
Binary files /dev/null and b/docs/images/editcCommandShowcase.png differ
diff --git a/docs/images/edittCommandShowcase1.png b/docs/images/edittCommandShowcase1.png
new file mode 100644
index 00000000000..31760208177
Binary files /dev/null and b/docs/images/edittCommandShowcase1.png differ
diff --git a/docs/images/edittCommandShowcase2.png b/docs/images/edittCommandShowcase2.png
new file mode 100644
index 00000000000..ea37467bee0
Binary files /dev/null and b/docs/images/edittCommandShowcase2.png differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
deleted file mode 100644
index 235da1c273e..00000000000
Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ
diff --git a/docs/images/findcCommandShowcase.png b/docs/images/findcCommandShowcase.png
new file mode 100644
index 00000000000..371538ee9e7
Binary files /dev/null and b/docs/images/findcCommandShowcase.png differ
diff --git a/docs/images/findtCommandShowcase.png b/docs/images/findtCommandShowcase.png
new file mode 100644
index 00000000000..3b0b1f8f742
Binary files /dev/null and b/docs/images/findtCommandShowcase.png differ
diff --git a/docs/images/genCommandShowcase.png b/docs/images/genCommandShowcase.png
new file mode 100644
index 00000000000..bc6ebcacd43
Binary files /dev/null and b/docs/images/genCommandShowcase.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/helpmessage2.png b/docs/images/helpmessage2.png
new file mode 100644
index 00000000000..dc09c773086
Binary files /dev/null and b/docs/images/helpmessage2.png differ
diff --git a/docs/images/importCommandShowcase1.png b/docs/images/importCommandShowcase1.png
new file mode 100644
index 00000000000..80bdfe487ba
Binary files /dev/null and b/docs/images/importCommandShowcase1.png differ
diff --git a/docs/images/importCommandShowcase2.png b/docs/images/importCommandShowcase2.png
new file mode 100644
index 00000000000..d4a0271fa49
Binary files /dev/null and b/docs/images/importCommandShowcase2.png differ
diff --git a/docs/images/junjunjieong.png b/docs/images/junjunjieong.png
new file mode 100644
index 00000000000..5b6eac25fe3
Binary files /dev/null and b/docs/images/junjunjieong.png differ
diff --git a/docs/images/junrong98.png b/docs/images/junrong98.png
new file mode 100644
index 00000000000..dd9ae6a5ac4
Binary files /dev/null and b/docs/images/junrong98.png differ
diff --git a/docs/images/marktCommandShowcase.png b/docs/images/marktCommandShowcase.png
new file mode 100644
index 00000000000..b7c7a955bdf
Binary files /dev/null and b/docs/images/marktCommandShowcase.png differ
diff --git a/docs/images/snss231.png b/docs/images/snss231.png
new file mode 100644
index 00000000000..edd9c201644
Binary files /dev/null and b/docs/images/snss231.png differ
diff --git a/docs/images/unassignCommandShowcase.png b/docs/images/unassignCommandShowcase.png
new file mode 100644
index 00000000000..7b6d9923b50
Binary files /dev/null and b/docs/images/unassignCommandShowcase.png differ
diff --git a/docs/images/unmarkCommandShowcase.png b/docs/images/unmarkCommandShowcase.png
new file mode 100644
index 00000000000..cd96b6c3383
Binary files /dev/null and b/docs/images/unmarkCommandShowcase.png differ
diff --git a/docs/images/viewtCommandShowcase.png b/docs/images/viewtCommandShowcase.png
new file mode 100644
index 00000000000..ead2ff01891
Binary files /dev/null and b/docs/images/viewtCommandShowcase.png differ
diff --git a/docs/img.png b/docs/img.png
new file mode 100644
index 00000000000..64590a54b57
Binary files /dev/null and b/docs/img.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..c28b2a2200d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,19 @@
---
layout: page
-title: AddressBook Level-3
+title: NUS Classes
---
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
+[](https://github.com/ay2122s2-cs2103t-t12-4/tp/actions)
+[](https://codecov.io/gh/ay2122s2-cs2103t-t12-4/tp)

-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+**NUS Classes is a desktop application for managing your contacts and tasks, with features such as marking tasks, assigning tasks to contacts and searching for tasks or contacts.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+* If you are interested in using NUS Classes, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing NUS Classes, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+a
**Acknowledgements**
* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
diff --git a/docs/team/adrianongjj.md b/docs/team/adrianongjj.md
new file mode 100644
index 00000000000..ef3e6aa797f
--- /dev/null
+++ b/docs/team/adrianongjj.md
@@ -0,0 +1,52 @@
+---
+layout: page
+
+title: Ong John Jun, Adrian Project Portfolio Page
+---
+
+### Project: NUSClasses
+
+NUS Classes is an app that enables the professors to better manage contacts from large numbers of students and staff, and allow the professor to document the task that he/she needs to do.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Finding tasks by date and/or time
+ * What it does: Find all tasks that are in between the given date time range
+ * Justification: Users can check their availability within a certain time frame
+ * Highlights:
+ * Uses same command as `findt` so the user can have 1 less command to remember
+ * Input order for date time search is not required, i.e. `findt dt/19-03-2022 1000, 19-03-2022 1200` is the same as `findt dt/19-03-2022 1200, 19-03-2022 1000`
+ * **Credits:** *Reused some functionalities from the existing AB3 to suit Task*
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=AdrianOngJJ&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18)
+
+
+* **Project management**:
+ * Set up team's repo
+ * Assigning tasks to myself
+
+* **Enhancements to existing feature**:
+ * What it does: Find all tasks based on their tags
+ * Justification: Users can filter tasks based on tags
+ * **Credits:** *Help from [brain16600](https://github.com/brian16600)*
+
+* **Enhancements to existing feature**:
+ * What it does: Find all contacts based on their tags
+ * Justification: Users can filter contacts based on tags
+ * **Credits:** *Help from [brain16600](https://github.com/brian16600)*
+
+* **Documentation**:
+ * User Guide:
+ * Updated `findt` based on tags [\#190](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/190)
+ * Changed `filter` to `findt` for standardisation [\#186](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/186)
+ * Added `filter` command [\#64](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/64)
+ * Developer Guide:
+ * Added `findt` logic [\#303](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/303)
+ * Updated Glossary [\#22](../DeveloperGuide.md)
+
+* **Community**:
+ * Clarify my coding intention for one of my PR [#135](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/135)
+
+* **Tools**:
+ * IntelliJ: IDE
diff --git a/docs/team/brian16600.md b/docs/team/brian16600.md
new file mode 100644
index 00000000000..becc99a0841
--- /dev/null
+++ b/docs/team/brian16600.md
@@ -0,0 +1,122 @@
+---
+layout: page
+title: Brian's Project Portfolio Page
+---
+
+### Project: NUS Classes
+
+NUS Classes is a desktop app for NUS Computing professors to manage their tasks and contacts. It includes task management features such as creating tasks, tagging tasks, assigning contacts to tasks, and marking tasks as complete or incomplete. It also includes contact management features such as finding contacts, assigning contacts to specific tasks and tagging contacts.
+
+Given below are my contributions to the project:
+
+* **New Feature:** Built the foundations for the Task functionality.
+ * **What it does:** Set up the framework for our NUS Classes product, which is a contact and task management app. By adding the task feature onto AB3's existing contact functionality,
+this feature is the foundation for all Task-related commands in NUS Classes. Fields such as Task Name, Tag, DateTime set up the basic information contained within a Task, enabling NUS Computing professors to track their tasks
+ * **Highlights:** This addition affects existing commands and commands to be added in the future. It required an in-depth analysis of what information should be encapsulated in a Task. The implementation too was challenging as it had to be correctly implemented to ensure all future commands built on this foundation work.
+ * **Credits:** *Reused some functionalities from the existing AB3 to suit Task*
+
+* **New Feature:** Added AddTaskCommand. (Pull Request [\#55](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/55), [\#78](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/78))
+ * **What it does:** Adds a Task to NUS Classes with the relevant information. Users can add Tasks with the relevant information such as `TASKNAME`, `DATETIME` and `TAGS`.
+ * **Justification:** This allows NUS Computing Professors to keep track of their Task duties and stay organised.
+
+* **New Feature:** Added AddTaskCommandParser. (Pull Request [\#78](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/78))
+ * **What it does:** Parses information from the user with the `addt` parameter and extracts relevant information to create a Task. Relevant information such as `TASKNAME`, `DATETIME`, `TAGS` are all parsed accurately to ensure the user's input is relevant. Invalid inputs such as invalid datetime, missing parameters, or even invalid user inputs are reflected to the user.
+This allows the user to easily create tasks with relevant information.
+ * **Highlights:** Custom error messages based on what the user has input incorrectly. For example for missing parameters, the error message will specify what parameters are missing. For invalid indexes, the error message will specify it as well, and invalid datetime is also covered.
+ * **Justification:** A parser specific to AddTaskCommand ensures relevant input is parsed and custom messages highlight to the user what is needed to be fixed.
+
+* **New Feature:** Person includes their `GITHUB_USERNAME`. Updated UI accordingly. (Pull Request [\#98](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/98), [\#126](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/126))
+ * **What it does:** Introduce Github Username functionality for contacts for NUS Computing professors to better manage their contacts. This also allows profs to easily review students' and TAs' work on Github.
+ * **Justification:** Github username is a relevant field for NUS Computing professors for many project modules, and it will make contact management easier.
+ * **Highlights:** Valid username inputs are in line with Github username standards, meaning that usernames can only contain alphanumeric characters or singular hyphens, and not starting with or ending with a hyphen.
+ * **Credits:** [Stack Overflow](https://stackoverflow.com/questions/58726546/github-username-convention-using-regex) for the regex for Github usernames.
+
+* **New Feature:** Specific customised error messages for `addc`, `addt`, `assign`, `unassign`, `deletec`, `deletet`, `editc`, `editt`, `findc`, `gen`, `mark`, `unmark`, `view`.
+ * **What it does:** Give the user specific error messages informing them of what parameters are missing/what parameters are invalid.
+ * **Justification:** The user can more easily fix their command rather than combing through the User Guide.
+
+* **New Feature:** Wrote tests for `AddTaskCommand`, `AddTaskCommandParser` and `GitUsername`.
+ * **Justification:** Proper testing to ensure these functions work.
+
+
+
+**Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=cs2103t-t12-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18)
+
+**Project management**:
+ * Did project incrementally, with code contributions and commits to team repo in every week.
+ * Finished all tasks by version deadline.
+ * Regularly did PR reviews, issue tracker and assigning of issues based on team meetings.
+ * Managed releases `v1.2` - `v1.4` (3 releases) on GitHub.
+ * Did necessary team tasks.
+
+
+**Enhancements to existing features**:
+
+**Update:** AddContact and EditCommand to take in the relevant input. (Pull Request ([\#88](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/88), [\#98](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/98), [\#183](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/183)))
+ * **What it does:** Update these functionalities to be more appropriate for NUS Computing professor: Removed `ADDRESS` field and added `GITHUB_USERNAME` field for relevancy.
+ * **Highlights:** Customised error messages for duplicated fields of `PHONENUMBER`, `EMAIL` and `USERNAME` as no two people should have these same two fields.
+
+**Update:** AddContactParser and EditCommandParser command to parse relevant details and throw custom error messages. (Pull Request ([\#88](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/88), [\#183](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/183)))
+ * **What it does:** Update these parsers parse `USERNAME` rather than `ADDRESS` and to throw custom error messages based on what parameters are missing.
+ * **Justification:** Custom error messages let the user know what needs to be changed.
+
+**Update:** Added new logo for NUS Classes.
+
+**Update:** Zoom Link display color. (Pull Request [\#153](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/153))
+
+**Update:** SampleDataUtil for more relevant tasks for NUS Classes as default tasks/contacts. (Pull Request [\#153](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/153))
+
+**Update:** Update tests for `AddCommand`, `AddCommandParser` and `EditCommand`.
+
+**Update:** Added tests for `TaskBetweenDatesPredicate`
+
+**Update:** Update test cases based on new error messages for all classes.
+
+**Update:** Update UniquePersonList from AB3's implementation. This allows the functionality of contacts to allow same name among contacts.
+ * **What it does:** Differentiate contacts via their email, phone and Github Username (which are required to be unique)
+ * **Justification:** Professors may have many contacts with the same name but are different students, especially for larger modules.
+
+
+**Documentation**:
+* User Guide:
+ * Added images for examples of each command. (Pull Request [\#158](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/158))
+ * Added images of UI. (Pull Request [\#158](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/158))
+ * Added write-up for description of NUS Classes.
+ * Updated Table of Contents.
+ * Added documentation for the features `add contact` and `add task`
+ * Rewrote examples for existing features to be more appropriate to NUS Classes, e.g. `Lecture` as example of Tasks.
+ * Updated Command Summary in UG
+ * Fixed PE-D documentation issues (Pull Requests ([\#249](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/249)))
+
+
+* README:
+ * Updated README.md
+
+
+* SettingUp:
+ * Updated SettingUp.md
+
+
+* Developer Guide:
+ * Added description of NUS Classes
+ * Fixed naming in DG
+ * Update Design Considerations in DG
+ * Update Use cases in DG
+ * Added Adding Task implementation in DG.
+
+
+**Community**:
+ * PRs reviewed (with non-trivial review comments): [\#258](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/258), [\#253](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/253),
+[\#175](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/175), [\#170](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/170), [\#166](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/166),
+[\#162](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/162), [\#95](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/95)
+ * Maintained Issue Tracker and commented on Issues for others.
+ * Regularly reviewed PRs
+
+
+
+**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/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/junjunjieong.md b/docs/team/junjunjieong.md
new file mode 100644
index 00000000000..b5a4e1eeb90
--- /dev/null
+++ b/docs/team/junjunjieong.md
@@ -0,0 +1,69 @@
+---
+layout: page
+
+title: Ong Jun Jie Project Portfolio Page
+---
+
+### Project: NUSClasses
+
+NUS Classes is a desktop app for NUS Computing professors to manage their tasks and contacts. It includes task management features such as
+creating tasks, tagging tasks, assigning contacts to tasks, and marking tasks as complete or incomplete. It also includes contact management features such as finding contacts, assigning contacts to specific tasks and tagging contacts.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Delete Task feature
+ * What it does: Delete the task from the task list based on index
+ * Justification: Important for user to remove the task that is not needed
+ * Credits: *{code ideas from AB3 delete contacts feature}*
+
+* **New Feature**: Edit Task feature
+ * What it does: Edit the selected task based on the field(s) user want to edit
+ * Justification: Important for user to edit the task to change or update the information
+ * Highlights:
+ * This enhancement requires in-depth analysis on the class `EditPersonDescriptor` and require to modify it to suit for `Task`.
+ * Able to construct `EditTaskDescriptor` and provide flexibility to modify it if there is any changes to the fields in class`Task`
+ * Credits: *{code ideas from AB3 edit contacts feature}*
+
+* **New Feature**: Mark and Unmark Task features
+ * What it does:
+ * Mark feature allows the user to set the task as done
+ * Unmark feature allows the user to set the task as not done
+ * Justification: User may want to know which task has been completed
+ * Highlights: This enhancement requires to understand the structure of `UI` and require to modify the `taskcard` UI and FMXL to hold image that represent the checkbox.
+
+* **New Feature**: List Task feature
+ * What it does: List the task based on the prefix(all/, c/, nc/) input by user.
+ * Justification: User may prefer to see all the available task only, or see list of completed/uncompleted task only.
+ * Credits: *{code ideas from AB3 list feature with modification}*
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=junjunjieOng&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18)
+
+* **Project management**:
+ * Managed releases `v1.2` - `v1.4rc` (3 releases) on GitHub
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the feature `deletet` [\#6](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/6)
+ * Added documentation for the features `editt` [\#86](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/86)
+ * Added documentation for the features `mark` and `unmark` [\#150](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/150)
+ * Added documentation for the features `listt` [\#154](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/154)
+ * Annotated the document images with [brain16600](https://github.com/brian16600) for clearer explanation [\#276](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/276)
+ * Developer Guide:
+ * Updated `user profile` and `value proposition` [\#7](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/7/files)
+ * Updated `User Stories` and `Non-functional requirement` [\#7](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/7/files)
+ * Added implementation detail of `deletet` and `editt` feature [\#107](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/107)
+ * Created activity diagram and Sequence diagram for `editt` and `deletet` feature [\#107](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/107)
+
+* **Community**:
+ * Total PRs reviewed (with non-trivial review comments): **14**
+ * [\#106](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/106), [\#114](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/114),
+ [\#145](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/145), [\#149](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/149),
+ [\#158](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/158), [\#175](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/175),
+ [\#183](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/183), [\#251](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/251),
+ [\#255](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/255), [\#260](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/260),
+ [\#263](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/263), [\#276](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/276),
+ [\#278](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/278), [\#281](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/281)
+ * Reported bugs and suggestions for other team in [PE-D](https://github.com/junjunjieOng/ped/issues)
+
+
diff --git a/docs/team/junrong98.md b/docs/team/junrong98.md
new file mode 100644
index 00000000000..cb398862517
--- /dev/null
+++ b/docs/team/junrong98.md
@@ -0,0 +1,69 @@
+---
+layout: page
+title: Jun Rong's Project Portfolio Page
+---
+
+### Project: Nus Classes
+
+NUS Classes is an app that enables the professors to better manage contacts from large numbers of students and staff, and allow the professor to document the task that he/she needs to do.
+Given below are my contributions to the project.
+
+* **New Feature**: View Task (Pull Request [\#68](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/68))
+ * What it does: Allows user to view who were assigned to each task.
+ * Justification: This allows NUS Computing Professors to easily remember who they assigned to the task.
+ * Highlights: Understanding how `ObservableList` works and the effect it has on the GUI.
+ * Credits: *Reused code ideas from AB3*
+
+* **New Feature**: Storing link in tasks (Pull Request [\#95](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/95))
+ * What it does: Stores link in tasks.
+ * Justification: This allows NUS Computing Professors to store link to task for easier management.
+
+
+* **New Feature**: Create recurring tasks (Pull Request [\#114](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/114))
+ * What it does: Allows user to create a task repeatedly with a fixed interval using a single command.
+ * Justification: This allows NUS Computing Professors to create tasks that occur regularly conveniently.
+ * Credits: [baeldung](https://www.baeldung.com/java-initialize-hashmap) *for the mapping of common date intervals. Reused with slight modifications*
+
+* **New Feature**: List tasks (Pull Request [\#146](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/146))
+ * What it does: List all tasks.
+ * Justification: This allows NUS Computing Professors to see all the tasks they created, after they have manipulated the list.
+ * Credits: *Reused some code ideas from AB3 to suit Task*
+
+* **New Feature**: Highlighting of tasks (Pull Request [\#149](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/149))
+ * What it does: Highlight tasks with different colour depending on their state.
+ * Justification: This allows NUS Computing Professors to easily see which tasks are coming up or are already dued.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `Viewing contacts assigned to a task` [\#68](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/68)
+ * Update documentation for the features `Adding a task` [\#114](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/114/files)
+ * Update documentation for NUS Classes [\#123](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/123/files)
+ * Developer Guide:
+ * Added documentation for the features `View contacts assigned to a task` [\#118](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/118)
+ * ReadMe:
+ * Update ReadMe.
+
+
+* **Community**:
+ * Total PRs reviewed (with non-trivial review comments): **25**
+ * [\#45](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/45), [\#66](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/66),
+ [\#77](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/77), [\#100](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/100),
+ [\#107](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/107), [\#109](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/109),
+ [\#112](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/112), [\#117](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/117),
+ [\#132](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/132), [\#135](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/135),
+ [\#132](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/132), [\#135](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/135),
+ [\#144](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/144), [\#150](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/150),
+ [\#154](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/154), [\#157](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/157),
+ [\#158](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/158), [\#164](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/178),
+ [\#170](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/170), [\#178](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/178),
+ [\#180](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/180), [\#183](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/183),
+ [\#185](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/185), [\#186](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/186),
+ [\#249](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/249)
+
+ * Reported bugs and suggestions for other team in [PE-D](https://github.com/junrong98/ped/issues)
diff --git a/docs/team/snss231.md b/docs/team/snss231.md
new file mode 100644
index 00000000000..147af6d04cc
--- /dev/null
+++ b/docs/team/snss231.md
@@ -0,0 +1,79 @@
+---
+layout: page
+title: Sean's Project Portfolio Page
+---
+
+### Project: NUS Classes
+
+NUS Classes is an all-in-one task and contacts organizer for NUS Computing professors. It is written in Java.
+
+Given below are my contributions to the project.
+
+* **New feature**: Implement the model and storage parts to facilitate a `Task` entity
+ * What it does: Allow users to add their tasks to NUS Classes.
+ * Justification: Users can use the feature to be well organised with their tasks
+
+
+* **New Feature**: Assign, unassign command
+ * What it does: Allow users to assign and unassign contacts from tasks
+ * Justification: Users can use the feature for bookkeeping purposes as well as to facilitate other contact-related commands.
+ * Highlights: Users can use the `gen` command to easily generate and copy all emails related to a task for ease of communication.
+
+
+* **New Feature**: Generate emails command
+ * What it does: Users can use the `gen` command to easily generate and copy all emails related to a task, then paste into their preferred email client.
+ * Justification: Users have an easy way to contact all contacts related to a task in case urgent dissemination of information is required (e.g. in the event of unforeseen circumstances)
+
+
+* **New Feature**: Date and time range for events
+ * What it does: Users can specify a stand and end date and time for tasks (as opposed to just one single deadline) to represent events that occur during a specific interval.
+ * Justification: Users can easily keep track of start and end timings for events.
+
+
+* **New Feature**: User-friendly date and time display
+ * What it does: Instead of displaying all dates in full e.g.`13 May 2022, 3.00pm - 13 May 2022, 5.00pm`, dates are displayed relative to the current date
+ * Examples:
+ * `Today` for tasks that occur on the current calendar day,
+ * `Friday` for tasks that occur on the same week
+ * `13 Apr` (omitting the year for tasks that occur on the same calendar year)
+ * `Today, 3.00 pm - 5.00 pm` (not repeating the end date when it is the same as the start date)
+ * Justification: Dates are far more intuitive and easy to read for the user.
+
+
+* **New Feature**: Import contacts from .csv
+ * What it does: Import contacts from a .csv file to create contacts in NUS Classes.
+ * Justification: It can be incredibly time-consuming and tedious to add all contacts manually.
+ * Highlights: Users can easily export contacts from a spreadsheet containing contacts and relevant headers to import the contacts into NUS Classes.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=snss231&tabRepo=AY2122S2-CS2103T-T12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+
+* **Project management**:
+ * Managed all releases `v1.1` - `v1.4` (4 releases) on GitHub
+
+
+* **Enhancements to existing features**:
+ * Updates and deletions to contacts are propagated to all tasks to ensure consistency in the model side of things.
+ * Limit tag length to 50 characters to avoid UI bugging out (part of the tag getting obscured)
+ * Polish GUI text feedback (e.g. message usages, fix typos and phrase messages better)
+ * Adjust name constraints to allow symbols that are commonly present in names (e.g. `Martin Luther King, Jr.`, `Joseph Lewitt-Hewman`, `Raj s/o Rajesh`)
+ * Change `clear` command to delete task data as well as contact data
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `clear`, `import` and `gen`
+ * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
+ * Developer Guide:
+ * Added implementation details of the `deletec` feature.
+ * Added design considerations for how tasks should be updated when contacts are deleted or updated.
+ * Updated text to reflect that the storage now handles both tasks and contacts
+ * Updated all UML class architecture diagrams to reflect the current status
+ * Readme.md, index.md
+ * Update links to CI and Codecov that was previously linking to AB-3
+
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#18](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/18), [\#59](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/59), [\#68](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/68), [\#83](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/83), [\#95](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/95), [\#98](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/98), [\#120](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/120), [\#121](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/121), [\#135](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/135), [\#170](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/170), [\#183](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/183), [\#249](https://github.com/AY2122S2-CS2103T-T12-4/tp/pull/249)
+ * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2122S2/forum/issues/241))
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..809d8fb22bd 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -191,7 +191,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
...
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ if (!personToEdit.isSamePerson(editedPerson) && model.hasName(editedPerson)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
model.setPerson(personToEdit, editedPerson);
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..bc3a4fef5d5 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -19,11 +19,14 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.TaskList;
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.JsonTaskListStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
@@ -57,7 +60,8 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTaskListStorage tasksStorage = new JsonTaskListStorage(userPrefs.getTaskListFilePath());
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, tasksStorage);
initLogging(config);
@@ -75,22 +79,40 @@ public void init() throws Exception {
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ ReadOnlyAddressBook initialAddressBookData;
+ Optional taskListOptional;
+ ReadOnlyTaskList initialTaskListData;
try {
addressBookOptional = storage.readAddressBook();
if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ logger.info("AddressBook data file not found. Will be starting with a sample AddressBook");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialAddressBookData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+
+
} catch (DataConversionException e) {
logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ initialAddressBookData = new AddressBook();
} catch (IOException e) {
logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ initialAddressBookData = new AddressBook();
}
- return new ModelManager(initialData, userPrefs);
+ try {
+ taskListOptional = storage.readTaskList();
+ if (!taskListOptional.isPresent()) {
+ logger.info("TaskList data file not found. Will be starting with a sample TaskList");
+ }
+ initialTaskListData = taskListOptional.orElseGet(SampleDataUtil::getSampleTaskList);
+
+ } catch (DataConversionException e) {
+ logger.warning("Data file not in the correct format. Will be starting with an empty TaskList");
+ initialTaskListData = new TaskList();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the file. Will be starting with an empty TaskList");
+ initialTaskListData = new TaskList();
+ }
+ return new ModelManager(initialAddressBookData, userPrefs, initialTaskListData);
}
private void initLogging(Config config) {
@@ -167,13 +189,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting NUS Classes " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping NUS Classes ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..3c43381bbf9 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -5,9 +5,42 @@
*/
public class Messages {
- public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
+ public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command! Use 'help' for a list of commands";
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_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
+ public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The contact index provided is invalid!"
+ + " Only a single non-zero positive integer is valid. Index must also not be out of bounds!\n"
+ + "Examples of invalid indexes: -1, 1 abc, 1 i/(invalid parameter)";
+ public static final String MESSAGE_INVALID_RECURRENCE = "The recurrence parameter is invalid! \n%1$s";
+ public static final String MESSAGE_INVALID_INTERVAL = "The interval parameter is invalid! \n%1$s";
+ public static final String MESSAGE_INVALID_RECURRENCE_INDEX = "The interval/recurrence indexes are invalid!"
+ + " Only a single non-zero positive integer is valid. \n%1$s";
+ public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d contacts listed!";
+ public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!";
+ public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid!"
+ + " Only non-zero positive values are valid. Index must also not be out of bounds!\n"
+ + "Examples of invalid indexes: -1, 1 abc, 1 i/(invalid parameter)";
+ public static final String MESSAGE_INVALID_DATE_RANGE = "The end date must be after the start date!";
+ public static final String MESSAGE_NO_KEYWORDS = "You need to specify a keyword! \n%1$s";
+ public static final String MESSAGE_INVALID_DATETIME = "The date and time provided is invalid!\n%1$s";
+ public static final String MESSAGE_NEED_AT_LEAST_ONE_VALID_PARAMETER =
+ "You need at least one valid parameter!\n%1$s";
+ public static final String MESSAGE_INVALID_PARAMETERS = "Missing/Invalid parameters: ";
+ public static final String MESSAGE_TAG_TOO_LONG = "Error: Tags can be at most 50 characters in length.\n"
+ + "The following tag(s) are too long:\n%s";
+
+ public static final String MESSAGE_DUPLICATE_GIT_USERNAME = "This Github username already exists in NUS Classes!\n"
+ + "Check again?";
+ public static final String MESSAGE_DUPLICATE_EMAIL = "This email already exists in NUS Classes!\nCheck again?";
+ public static final String MESSAGE_DUPLICATE_PHONE = "This phone number already exists in NUS Classes!\n"
+ + "Check again?";
+ public static final String ERROR_MESSAGE_INVALID_FORMAT =
+ "Invalid date format. It should be \"dd-MM-yyyy HHmm\" or \"dd-MM-yyyy\"\n"
+ + "dd: Day (from 01 to 31); MM: Month (from 01 to 12); "
+ + "yyyy: Year (from 1 to 99999999); HH: Hour (from 00 to 23); "
+ + "mm: Minute (from 00 to 59)";
+
+ public static final String ERROR_MESSAGE_INVALID_PARAMETER =
+ "Invalid parameter format. It should be either \"dt/dd-MM-yyyy HHmm, dd-MM-yyyy HHmm\""
+ + " or \"dt/dd-MM-yyyy, dd-MM-yyyy\"";
}
diff --git a/src/main/java/seedu/address/commons/util/TagUtil.java b/src/main/java/seedu/address/commons/util/TagUtil.java
new file mode 100644
index 00000000000..f400b5de855
--- /dev/null
+++ b/src/main/java/seedu/address/commons/util/TagUtil.java
@@ -0,0 +1,28 @@
+package seedu.address.commons.util;
+
+import static seedu.address.commons.core.Messages.MESSAGE_TAG_TOO_LONG;
+
+import java.util.Set;
+
+import seedu.address.model.tag.Tag;
+
+/**
+ * Static library of methods related to Tags
+ */
+public class TagUtil {
+
+ /**
+ * Checks if any of the tags is too long (more than 50 characters).
+ *
+ * @return null if none of the tags are too long, error message otherwise.
+ */
+ public static String checkTagLength(Set tags) {
+ if (tags.stream().anyMatch(tag -> tag.tagName.length() > 50)) {
+ return String.format(MESSAGE_TAG_TOO_LONG,
+ tags.stream()
+ .filter(tag -> tag.tagName.length() > 50)
+ .reduce("", (str, tag) -> tag + "\n" + str, (s1, s2) -> s1 + s2));
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/seedu/address/commons/util/TranslatorUtil.java b/src/main/java/seedu/address/commons/util/TranslatorUtil.java
new file mode 100644
index 00000000000..6ac5b4d3d85
--- /dev/null
+++ b/src/main/java/seedu/address/commons/util/TranslatorUtil.java
@@ -0,0 +1,28 @@
+package seedu.address.commons.util;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+public class TranslatorUtil {
+
+ //@@author
+ //Reused from https://www.baeldung.com/java-initialize-hashmap
+ // with minor modifications
+ private static final Map periodMap = Map.ofEntries(
+ new AbstractMap.SimpleEntry("annually", 365),
+ new AbstractMap.SimpleEntry("quarterly", 120),
+ new AbstractMap.SimpleEntry("monthly", 30),
+ new AbstractMap.SimpleEntry("weekly", 7),
+ new AbstractMap.SimpleEntry("daily", 1)
+ );
+ //@@author
+
+ /**
+ * Get the mapping of period to no. of days.
+ *
+ * @return Return the mapping of period to no. of days.
+ */
+ public static Map getPeriodMapping() {
+ return periodMap;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..51426faf770 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.ReadOnlyTaskList;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
/**
* API of the Logic component
@@ -33,6 +35,9 @@ public interface Logic {
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered list of tasks */
+ ObservableList getFilteredTaskList();
+
/**
* Returns the user prefs' address book file path.
*/
@@ -44,7 +49,14 @@ public interface Logic {
GuiSettings getGuiSettings();
/**
- * Set the user prefs' GUI settings.
+ * Sets the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the Task List.
+ *
+ * @see seedu.address.model.Model#getTaskList()
+ */
+ ReadOnlyTaskList getTaskList();
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..9be09d23332 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.ReadOnlyTaskList;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
import seedu.address.storage.Storage;
/**
@@ -47,6 +49,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
try {
storage.saveAddressBook(model.getAddressBook());
+ storage.saveTaskList(model.getTaskList());
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
@@ -64,6 +67,11 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getFilteredTaskList() {
+ return model.getFilteredTaskList();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
@@ -78,4 +86,9 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ @Override
+ public ReadOnlyTaskList getTaskList() {
+ return model.getTaskList();
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 71656d7c5c8..9b302296bb4 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -1,12 +1,16 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EMAIL;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_GIT_USERNAME;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GIT_USERNAME;
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.commons.util.TagUtil;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
@@ -16,30 +20,31 @@
*/
public class AddCommand extends Command {
- public static final String COMMAND_WORD = "add";
+ public static final String COMMAND_WORD = "addc";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
- + "Parameters: "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to NUS Classes.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
+ + PREFIX_GIT_USERNAME + "GITHUB_USERNAME "
+ "[" + 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_GIT_USERNAME + "john123 "
+ 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}
+ * Constructs an AddCommand to add the specified {@code Person}
*/
public AddCommand(Person person) {
requireNonNull(person);
@@ -50,8 +55,23 @@ public AddCommand(Person person) {
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- if (model.hasPerson(toAdd)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ if (model.hasPhone(toAdd.getPhone())) {
+ throw new CommandException(MESSAGE_DUPLICATE_PHONE);
+ }
+
+ if (model.hasEmail(toAdd.getEmail())) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ if (model.hasUsername(toAdd.getUsername())) {
+ throw new CommandException(MESSAGE_DUPLICATE_GIT_USERNAME);
+ }
+
+ String checkTagLength = TagUtil.checkTagLength(toAdd.getTags());
+
+ //null value represents no tags are too long.
+ if (checkTagLength != null) {
+ throw new CommandException(checkTagLength);
}
model.addPerson(toAdd);
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..58ba3e4de02
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java
@@ -0,0 +1,150 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.isNull;
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_RANGE;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LINK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_RECURRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASKNAME;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import seedu.address.commons.util.TagUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.Task;
+
+/**
+ * Represents an AddTaskCommand.
+ */
+public class AddTaskCommand extends Command {
+
+ /* Message printed if wrong usage */
+ public static final String COMMAND_WORD = "addt";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to NUS Classes.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + PREFIX_TASKNAME + "TASKNAME "
+ + PREFIX_DATETIME + "DATETIME [, ENDDATETIME]"
+ + " [" + PREFIX_TAG + "TAG] "
+ + " [" + PREFIX_LINK + "LINK] "
+ + " [" + PREFIX_RECURRING + "PERIOD RECURRENCE]\n"
+ + "Example: " + "addt" + " "
+ + PREFIX_TASKNAME + "Lecture "
+ + PREFIX_DATETIME + "25-12-2023 1800,"
+ + "25-12-2023 2000 "
+ + PREFIX_TAG + "CS2103T "
+ + PREFIX_LINK + "https://... "
+ + PREFIX_RECURRING + "5 5\n"
+ + "Hint: for " + PREFIX_RECURRING + " you can use predefined values [annually, quarterly, monthly,"
+ + " weekly, daily] for the period field.";
+
+ public static final String ADD_TASK_SUCCESS = "Task added!";
+
+ private final String taskName;
+ private final LocalDateTime dateTime;
+ private final LocalDateTime endDateTime;
+ private final Set tags;
+ private final Link link;
+ private final int recurrence;
+ private final int period;
+ private final boolean isTaskMarkDone;
+
+
+
+ /**
+ * Constructor for AddTaskCommand. Takes in 6 parameters, taskName, dateTime, tags,
+ * link, recurrence, and period. There can be multiple tags.
+ *
+ * @param taskName Name of Task.
+ * @param dateTime LocalDateTime object to represent date time of Task.
+ * @param tags A set of tags link to the Task.
+ * @param link Link of a task.
+ * @param recurrence The number of times the task should recur.
+ * @param period The number of days apart each task should be.
+ */
+ public AddTaskCommand(String taskName, LocalDateTime dateTime, LocalDateTime endDateTime, Set tags, Link link,
+ int recurrence, int period) {
+
+ requireAllNonNull(taskName, dateTime, tags);
+ this.taskName = taskName;
+ this.dateTime = dateTime;
+ this.endDateTime = endDateTime;
+ this.tags = tags;
+ this.link = link;
+ this.recurrence = recurrence;
+ this.period = period;
+ this.isTaskMarkDone = false;
+ }
+
+ /**
+ * Constructor for AddTaskCommand without period or recurrence.
+ */
+ public AddTaskCommand(String taskName, LocalDateTime dateTime, LocalDateTime endDateTime, Set tags,
+ Link link) {
+ this(taskName, dateTime, endDateTime, tags, link, 0, 0);
+ }
+
+ /**
+ * Constructor that takes in a Task and creates an AddTaskCommand.
+ *
+ * @param task Task from which information is added to AddTaskCommand.
+ */
+ public AddTaskCommand(Task task) {
+ this.taskName = task.getName();
+ this.dateTime = task.getDateTime();
+ this.endDateTime = task.getEndDateTime();
+ this.tags = task.getTags();
+ this.link = task.getLink();
+ this.recurrence = 0;
+ this.period = 0;
+ this.isTaskMarkDone = false;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ String checkTagLength = TagUtil.checkTagLength(tags);
+
+ //null value represents no tags are too long.
+ if (checkTagLength != null) {
+ throw new CommandException(checkTagLength);
+ }
+ Task taskToBeAdded = new Task(taskName, dateTime, endDateTime, tags, link, isTaskMarkDone);
+
+ if (taskToBeAdded.hasInvalidDateRange()) {
+ throw new CommandException(MESSAGE_INVALID_DATE_RANGE);
+ }
+ model.addTask(taskToBeAdded);
+ for (int i = 1; i < period; i++) {
+ LocalDateTime temp = dateTime.plusDays(i * recurrence);
+ LocalDateTime tempEnd = null;
+ if (!isNull(endDateTime)) {
+ tempEnd = endDateTime.plusDays(i * recurrence);
+ }
+ taskToBeAdded = new Task(taskName, temp, tempEnd, tags, link, isTaskMarkDone);
+ model.addTask(taskToBeAdded);
+ }
+ return new CommandResult(ADD_TASK_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof AddTaskCommand
+ && this.taskName.equals(((AddTaskCommand) other).taskName)
+ && this.dateTime.equals(((AddTaskCommand) other).dateTime)
+ && this.tags.equals(((AddTaskCommand) other).tags));
+ }
+
+ @Override
+ public String toString() {
+ return this.taskName + " " + this.dateTime + " " + this.tags + " " + this.link;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AssignCommand.java b/src/main/java/seedu/address/logic/commands/AssignCommand.java
new file mode 100644
index 00000000000..7eee7f281fd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AssignCommand.java
@@ -0,0 +1,120 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON;
+
+import java.util.ArrayList;
+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;
+import seedu.address.model.task.Task;
+
+/**
+ * Assigns a contact in the contact list to a task.
+ */
+public class AssignCommand extends Command {
+
+ public static final String COMMAND_WORD = "assign";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Add the person identified by the index number "
+ + "used in the displayed person list "
+ + "to the task identified by the index number used in the displayed task list.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "TASK_INDEX " + PREFIX_PERSON + "PERSON_INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PERSON + "2";
+
+
+ public static final String MESSAGE_ADD_PERSON_TO_TASK_SUCCESS_MULTIPLE =
+ "Added %1$s, Number: %2$s to the task: `%3$s`\nThere are currently %4$s people assigned to this task.";
+
+ public static final String MESSAGE_ADD_PERSON_TO_TASK_SUCCESS_SINGLE =
+ "Added %1$s, Number: %2$s to the task: `%3$s`\nThere is currently %4$s person assigned to this task.";
+
+ public static final String MESSAGE_DUPLICATE_PERSON =
+ "Failed: The person %1$s is already assigned to the task: `%2$s`";
+
+ private final Index taskIndex;
+ private final Index personIndex;
+
+ /**
+ * Constructs AssignCommand that takes in a task index and person index.
+ *
+ * @param taskIndex of the task in the filtered task list to be added to
+ * @param personIndex of the person in the filtered person list to add
+ */
+ public AssignCommand(Index taskIndex, Index personIndex) {
+ requireNonNull(taskIndex);
+ requireNonNull(personIndex);
+
+ this.personIndex = personIndex;
+ this.taskIndex = taskIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List filteredPersonList = model.getFilteredPersonList();
+ List filteredTaskList = model.getFilteredTaskList();
+
+ if (personIndex.getZeroBased() >= filteredPersonList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ if (taskIndex.getZeroBased() >= filteredTaskList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ Task taskToEdit = filteredTaskList.get(taskIndex.getZeroBased());
+ Person personToAdd = filteredPersonList.get(personIndex.getZeroBased());
+
+ if (taskToEdit.getPeople().contains(personToAdd)) {
+ throw new CommandException(
+ String.format(MESSAGE_DUPLICATE_PERSON, personToAdd.getName(), taskToEdit));
+ }
+ Task updatedTask = getUpdatedTask(personToAdd, taskToEdit);
+
+ model.setTask(taskToEdit, updatedTask);
+
+ int numberOfPeople = updatedTask.getNoOfPeople();
+
+ if (numberOfPeople <= 1) {
+ return new CommandResult(
+ String.format(MESSAGE_ADD_PERSON_TO_TASK_SUCCESS_SINGLE,
+ personToAdd.getName(), personToAdd.getPhone(), updatedTask, numberOfPeople));
+ }
+
+ return new CommandResult(
+ String.format(MESSAGE_ADD_PERSON_TO_TASK_SUCCESS_MULTIPLE,
+ personToAdd.getName(), personToAdd.getPhone(), updatedTask, numberOfPeople));
+
+ }
+
+
+ /**
+ * Obtains the updated task.
+ *
+ * @param personToAdd Person object to be added.
+ * @param taskToUpdate Task to be changed.
+ * @return New edited Task.
+ * @throws CommandException If command format is wrong.
+ */
+ private Task getUpdatedTask(Person personToAdd, Task taskToUpdate) {
+ List updatedList = new ArrayList<>(taskToUpdate.getPeople());
+ updatedList.add(personToAdd);
+ Task editedTask = new Task(taskToUpdate.getName(), taskToUpdate.getDateTime(), taskToUpdate.getEndDateTime(),
+ updatedList, taskToUpdate.getTags(), taskToUpdate.getLink(), taskToUpdate.isTaskMark());
+ return editedTask;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof AssignCommand
+ && personIndex.equals(((AssignCommand) other).personIndex)
+ && taskIndex.equals(((AssignCommand) other).taskIndex));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..372d82caf0f 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -4,6 +4,7 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
+import seedu.address.model.TaskList;
/**
* Clears the address book.
@@ -11,13 +12,14 @@
public class ClearCommand extends Command {
public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
+ public static final String MESSAGE_SUCCESS = "NUS Classes data has been cleared!";
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
model.setAddressBook(new AddressBook());
+ model.setTaskList(new TaskList());
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 92f900b7916..c81ca878250 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -11,6 +11,8 @@ public class CommandResult {
private final String feedbackToUser;
+ private final String emails;
+
/** Help information should be shown to the user. */
private final boolean showHelp;
@@ -24,6 +26,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.emails = null;
}
/**
@@ -34,10 +37,25 @@ public CommandResult(String feedbackToUser) {
this(feedbackToUser, false, false);
}
+ /**
+ *
+ * @return
+ */
+ public CommandResult(String feedbackToUser, String emails) {
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.showHelp = false;
+ this.exit = false;
+ this.emails = requireNonNull(emails);
+ }
+
public String getFeedbackToUser() {
return feedbackToUser;
}
+ public String getEmails() {
+ return emails;
+ }
+
public boolean isShowHelp() {
return showHelp;
}
@@ -46,6 +64,10 @@ public boolean isExit() {
return exit;
}
+ public boolean isGenerateEmails() {
+ return this.emails != null;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -60,12 +82,17 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
&& showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && exit == otherCommandResult.exit
+ && emails == otherCommandResult.emails;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, showHelp, exit, emails);
+ }
+
+ public String toString() {
+ return getFeedbackToUser();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 02fd256acba..ebc6eefe992 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -15,17 +15,24 @@
*/
public class DeleteCommand extends Command {
- public static final String COMMAND_WORD = "delete";
+ public static final String COMMAND_WORD = "deletec";
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"
+ + "Index must be a positive integer.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX\n"
+ "Example: " + COMMAND_WORD + " 1";
public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
private final Index targetIndex;
+ /**
+ * Constructs a DeleteCommand to delete the specified {@code targetIndex}
+ *
+ */
public DeleteCommand(Index targetIndex) {
this.targetIndex = targetIndex;
}
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..cf2bf148abb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java
@@ -0,0 +1,59 @@
+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.
+ */
+public class DeleteTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "deletet";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the task identified by the index number used in the displayed task list.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s";
+
+ private final Index targetIndex;
+
+ /**
+ * Constructs a DeleteTaskCommand to delete the specified {@code 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(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteTask(taskToDelete);
+ 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
index 7e36114902f..464cfa7af37 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,8 +1,11 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EMAIL;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_GIT_USERNAME;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GIT_USERNAME;
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;
@@ -17,10 +20,11 @@
import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
+import seedu.address.commons.util.TagUtil;
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.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -31,29 +35,34 @@
*/
public class EditCommand extends Command {
- public static final String COMMAND_WORD = "edit";
+ public static final String COMMAND_WORD = "editc";
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) "
+ + "\nExisting values will be overwritten by the input values. Index must be positive integers.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX "
+ "[" + PREFIX_NAME + "NAME] "
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_GIT_USERNAME + "GITHUB_USERNAME] "
+ + "[" + PREFIX_TAG + "TAG]...\n "
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
+ + PREFIX_EMAIL + "johndoe@example.com "
+ + PREFIX_GIT_USERNAME + "john123";
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.";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.\n"
+ + MESSAGE_USAGE;
+ public static final String MESSAGE_PERSON_NOT_EDITED = "At least one field of this contact must be edited! \n%1$s";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
/**
+ * Constructs EditCommand that takes in a person index and editPersonDescriptor.
* @param index of the person in the filtered person list to edit
* @param editPersonDescriptor details to edit the person with
*/
@@ -77,8 +86,31 @@ public CommandResult execute(Model model) throws CommandException {
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ String checkTagLength = TagUtil.checkTagLength(editedPerson.getTags());
+
+ //null value represents no tags are too long.
+ if (checkTagLength != null) {
+ throw new CommandException(checkTagLength);
+ }
+
+ if (model.hasPhone(editedPerson.getPhone())
+ && !editedPerson.getPhone().equals(personToEdit.getPhone())) {
+ throw new CommandException(MESSAGE_DUPLICATE_PHONE);
+ }
+
+ if (model.hasEmail(editedPerson.getEmail())
+ && !editedPerson.getEmail().equals(personToEdit.getEmail())) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ if (model.hasUsername(editedPerson.getUsername())
+ && !editedPerson.getUsername().equals(personToEdit.getUsername())) {
+ throw new CommandException(MESSAGE_DUPLICATE_GIT_USERNAME);
+ }
+
+ if (model.hasPerson(editedPerson) && editedPerson.getName().equals(personToEdit.getName())) {
+
+ throw new CommandException(String.format(MESSAGE_PERSON_NOT_EDITED, MESSAGE_USAGE));
}
model.setPerson(personToEdit, editedPerson);
@@ -96,10 +128,10 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
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());
+ GitUsername updatedGitUsername = editPersonDescriptor.getUsername().orElse(personToEdit.getUsername());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedGitUsername, updatedTags);
}
@Override
@@ -128,7 +160,7 @@ public static class EditPersonDescriptor {
private Name name;
private Phone phone;
private Email email;
- private Address address;
+ private GitUsername gitUsername;
private Set tags;
public EditPersonDescriptor() {}
@@ -141,7 +173,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setName(toCopy.name);
setPhone(toCopy.phone);
setEmail(toCopy.email);
- setAddress(toCopy.address);
+ setUsername(toCopy.gitUsername);
setTags(toCopy.tags);
}
@@ -149,7 +181,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, email, gitUsername, tags);
}
public void setName(Name name) {
@@ -160,6 +192,7 @@ public Optional getName() {
return Optional.ofNullable(name);
}
+
public void setPhone(Phone phone) {
this.phone = phone;
}
@@ -176,12 +209,12 @@ public Optional getEmail() {
return Optional.ofNullable(email);
}
- public void setAddress(Address address) {
- this.address = address;
+ public void setUsername(GitUsername gitUsername) {
+ this.gitUsername = gitUsername;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ public Optional getUsername() {
+ return Optional.ofNullable(gitUsername);
}
/**
@@ -219,7 +252,7 @@ public boolean equals(Object other) {
return getName().equals(e.getName())
&& getPhone().equals(e.getPhone())
&& getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
+ && getUsername().equals(e.getUsername())
&& getTags().equals(e.getTags());
}
}
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..ab506ba9a3e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java
@@ -0,0 +1,240 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_RANGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LINK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASKNAME;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+import java.time.LocalDateTime;
+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.commons.util.TagUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.Task;
+
+/**
+ * Edits the details of an existing task in the task list.
+ */
+public class EditTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "editt";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edit and update the details of the task identified"
+ + " by the index number used in the displayed task list. \n"
+ + "Existing values will be overwritten by the input values. Index must be a positive integer\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX "
+ + "[" + PREFIX_TASKNAME + "TASK NAME] "
+ + "[" + PREFIX_DATETIME + "DATETIME(dd-mm-yyyy hhmm)] "
+ + "[" + PREFIX_LINK + "LINK] "
+ + "[" + PREFIX_TAG + "TAG]...\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_TASKNAME + "CS2103T Lecture "
+ + PREFIX_DATETIME + "12-03-2023 1330 "
+ + PREFIX_LINK + "https://... "
+ + PREFIX_TAG + "Lecture";
+
+ public static final String MESSAGE_EDIT_TASK_SUCCESS = "Updated Task: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.\n%1$s";
+ public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in your task list.";
+
+ private final Index index;
+ private final EditTaskDescriptor editTaskDescriptor;
+
+ /**
+ * Constructs EditTaskCommand that takes in a task index and 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) {
+ requireNonNull(index);
+ requireNonNull(editTaskDescriptor);
+
+ this.index = index;
+ this.editTaskDescriptor = new EditTaskDescriptor(editTaskDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToEdit = lastShownList.get(index.getZeroBased());
+ Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor);
+
+ String checkTagLength = TagUtil.checkTagLength(editedTask.getTags());
+
+ //null value represents no tags are too long.
+ if (checkTagLength != null) {
+ throw new CommandException(checkTagLength);
+ }
+
+ if (!taskToEdit.isSameTask(editedTask) && model.hasTask(editedTask)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TASK);
+ }
+
+ if (editedTask.hasInvalidDateRange()) {
+ throw new CommandException(MESSAGE_INVALID_DATE_RANGE);
+ }
+
+ model.setTask(taskToEdit, editedTask);
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask));
+ }
+
+ /**
+ * Creates and returns a {@code Task} with the details of {@code taskToEdit}
+ * edited with {@code editTaskDescriptor}.
+ */
+ private static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) {
+ requireNonNull(taskToEdit);
+ requireNonNull(editTaskDescriptor);
+
+ String editName = editTaskDescriptor.getName().orElse(taskToEdit.getName());
+ LocalDateTime editDate = editTaskDescriptor.getDate().orElse(taskToEdit.getDateTime());
+ LocalDateTime editEndDate = editTaskDescriptor.getEndDate();
+ Set editTag = editTaskDescriptor.getTags().orElse(taskToEdit.getTags());
+ Link link = editTaskDescriptor.getLink().orElse(taskToEdit.getLink());
+ boolean isTaskMarkDone = taskToEdit.isTaskMark();
+
+ return new Task(editName, editDate, editEndDate, editTag, link, isTaskMarkDone);
+ }
+
+ @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 String name;
+ private LocalDateTime dateTime;
+ private LocalDateTime endDateTime;
+ private Set tags;
+ private Link link;
+
+ public EditTaskDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditTaskDescriptor(EditTaskDescriptor toCopy) {
+ requireNonNull(toCopy);
+ setName(toCopy.name);
+ setDate(toCopy.dateTime);
+ setEndDate(toCopy.endDateTime);
+ setTags(toCopy.tags);
+ setLink(toCopy.link);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, dateTime, tags, link);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setDate(LocalDateTime dateTime) {
+ this.dateTime = dateTime;
+ }
+
+ public void setEndDate(LocalDateTime endDateTime) {
+ this.endDateTime = endDateTime;
+ }
+
+ public void setLink(Link link) {
+ this.link = link;
+ }
+
+ public Optional getDate() {
+ return Optional.ofNullable(dateTime);
+ }
+
+ public LocalDateTime getEndDate() {
+ return endDateTime;
+ }
+
+ public Optional getLink() {
+ return Optional.ofNullable(link);
+ }
+
+ /**
+ * Sets {@code tag} to this object's {@code tag}.
+ * 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, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return Optional.ofNullable(tags);
+ }
+
+ @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 getName().equals(e.getName())
+ && getDate().equals(e.getDate())
+ && getTags().equals(e.getTags());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..eba2b6f8dcf 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -9,7 +9,7 @@ public class ExitCommand extends Command {
public static final String COMMAND_WORD = "exit";
- public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
+ public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting NUS CLasses as requested ...";
@Override
public CommandResult execute(Model model) {
diff --git a/src/main/java/seedu/address/logic/commands/FilterByDateCommand.java b/src/main/java/seedu/address/logic/commands/FilterByDateCommand.java
new file mode 100644
index 00000000000..51338dc74c9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FilterByDateCommand.java
@@ -0,0 +1,49 @@
+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.TaskBetweenDatesPredicate;
+
+
+
+/**
+ * Finds and lists all tasks in task storage whose date falls in between two given dates.
+ * Date format: dd-MM-yyyy HHmm
+ */
+public class FilterByDateCommand extends Command {
+
+ public static final String COMMAND_WORD = "findt";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all tasks whose dates fall within "
+ + "the specified dates inputs (order insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: dt/DATETIME1, DATETIME2\n"
+ + "Example: " + COMMAND_WORD + " dt/12-01-2022 0900, 13-02-2022 0900";
+
+ private final TaskBetweenDatesPredicate predicate;
+
+ /**
+ * Constructor for FilterByDateCommand
+ *
+ * @param predicate Predicate that returns true if task's date falls on or in between the given range
+ */
+ public FilterByDateCommand(TaskBetweenDatesPredicate 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 FilterByDateCommand // instanceof handles nulls
+ && predicate.equals(((FilterByDateCommand) 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
index d6b19b0a0de..acb94709d54 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -8,15 +8,17 @@
/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
+ * Keyword matching is case-insensitive.
*/
public class FindCommand extends Command {
- public static final String COMMAND_WORD = "find";
+ public static final String COMMAND_WORD = "findc";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names/tags contain any of "
+ "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "KEYWORD [MORE_KEYWORDS]...[TAG]...\n"
+ "Example: " + COMMAND_WORD + " alice bob charlie";
private final NameContainsKeywordsPredicate predicate;
diff --git a/src/main/java/seedu/address/logic/commands/FindTaskCommand.java b/src/main/java/seedu/address/logic/commands/FindTaskCommand.java
new file mode 100644
index 00000000000..67187764338
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindTaskCommand.java
@@ -0,0 +1,49 @@
+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.TaskNameContainsKeywordsPredicate;
+
+
+
+/**
+ * Finds and lists all tasks in task storage whose name contains any of the argument keywords.
+ * Keyword matching is case-insensitive.
+ */
+public class FindTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "findt";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all tasks whose names/tags contain any of "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "KEYWORD [MORE_KEYWORDS]...[TAG]...\n"
+ + "Example: " + COMMAND_WORD + " lecture consultation\n"
+ + "Note: Include \"dt/\" tag to search based on date";
+
+
+ private final TaskNameContainsKeywordsPredicate predicate;
+
+ public FindTaskCommand(TaskNameContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.updateFilteredTaskList(this.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 FindTaskCommand // instanceof handles nulls
+ && predicate.equals(((FindTaskCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/GenerateEmailsCommand.java b/src/main/java/seedu/address/logic/commands/GenerateEmailsCommand.java
new file mode 100644
index 00000000000..372af7982bd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/GenerateEmailsCommand.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+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.task.Task;
+
+public class GenerateEmailsCommand extends Command {
+
+ public static final String COMMAND_WORD = "gen";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Generates all the emails of the people related to the task "
+ + "identified by the index number in the task list.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ static final String MESSAGE_NO_CONTACTS_ASSIGNED =
+ "Failed: There are no contacts assigned to the task %1$s";
+
+ static final String MESSAGE_GENERATED_EMAILS = "Here are the emails related to the task %1$s:\n"
+ + "%2$s";
+
+ private final Index targetIndex;
+
+ /**
+ * Constructs GenerateEmailsCommand that takes in a {@code targetIndex}.
+ *
+ */
+ public GenerateEmailsCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List shownTaskList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= shownTaskList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task task = shownTaskList.get(targetIndex.getZeroBased());
+
+ if (task.getNoOfPeople() == 0) {
+ return new CommandResult(String.format(MESSAGE_NO_CONTACTS_ASSIGNED, task));
+ }
+
+ String emails = task.getEmails();
+
+ return new CommandResult(String.format(MESSAGE_GENERATED_EMAILS, task, emails), emails);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GenerateEmailsCommand)) {
+ return false;
+ }
+ GenerateEmailsCommand other = (GenerateEmailsCommand) o;
+ return Objects.equals(this.targetIndex, other.targetIndex);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..65a17a2af89 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -3,7 +3,7 @@
import seedu.address.model.Model;
/**
- * Format full help instructions for every command for display.
+ * Formats full help instructions for every command for display.
*/
public class HelpCommand extends Command {
diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java
new file mode 100644
index 00000000000..5859ff31119
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java
@@ -0,0 +1,131 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FILEPATH;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.util.TagUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Imports a list of contacts from a .csv file.
+ */
+public class ImportCommand extends Command {
+
+ public static final String COMMAND_WORD = "import";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports contacts into "
+ + "NUS Classes from a .csv data file. "
+ + "Parameters: "
+ + PREFIX_FILEPATH + "FILEPATH\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_FILEPATH + "data.csv";
+ public static final String MESSAGE_SUCCESS =
+ "Import completed. The following %d contact(s) were successfully added to NUS Classes from %s:\n";
+ public static final String MESSAGE_DUPLICATES_NOT_ADDED =
+ "\nNote: The following %d contact(s) containing duplicate fields were not added:\n";
+ public static final String MESSAGE_FOUND_TAGS_TOO_LONG =
+ "\nNote: The following %d contact(s) containing tags longer "
+ + "than the maximum length of 50 characters were not added:\n";
+ public static final String MESSAGE_NO_CONTACTS_ADDED =
+ "Import completed. No contacts were added to NUS Classes from %s.\n";
+ public static final String MESSAGE_INVALID_FIELDS =
+ "\nNote: %d contact(s) containing invalid fields were not added due to these issues:\n";
+
+ private final String filename;
+ private final List contactsToAdd;
+ private final List invalidFields;
+
+ /**
+ * Creates an ImportCommand with the following fields
+ * @param contactsToAdd The contacts to be added to the model (before duplicate checks)
+ * @param filename The name of the data file
+ * @param invalidFields A list of strings, each giving info on an invalid entry that was provided in the data file.
+ */
+ public ImportCommand(List contactsToAdd, String filename, List invalidFields) {
+ this.contactsToAdd = contactsToAdd;
+ this.filename = filename;
+ this.invalidFields = invalidFields;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List personAddedContacts = new ArrayList<>();
+ int addedCount = 0;
+ List duplicateContacts = new ArrayList<>();
+ int duplicateCount = 0;
+ List invalidTagContacts = new ArrayList<>();
+ int invalidTagCount = 0;
+
+ for (Person p : contactsToAdd) {
+ boolean hasEmail = model.hasEmail(p.getEmail());
+ boolean hasPhone = model.hasPhone(p.getPhone());
+ boolean hasUsername = model.hasUsername(p.getUsername());
+
+ if (hasEmail || hasPhone || hasUsername) {
+ duplicateCount++;
+ duplicateContacts.add(p);
+ continue;
+ }
+
+ //null value represents no tags are too long.
+ if (TagUtil.checkTagLength(p.getTags()) != null) {
+ invalidTagCount++;
+ invalidTagContacts.add(p);
+ continue;
+ }
+ addedCount++;
+ personAddedContacts.add(p);
+ model.addPerson(p);
+ }
+
+ String infoAdded = addedCount == 0
+ ? String.format(MESSAGE_NO_CONTACTS_ADDED, filename)
+ : String.format(MESSAGE_SUCCESS, addedCount, filename) + personListToString(personAddedContacts);
+
+ String infoDuplicates = duplicateCount == 0
+ ? ""
+ : String.format(MESSAGE_DUPLICATES_NOT_ADDED, duplicateCount) + personListToString(duplicateContacts);
+
+ String infoInvalidTags = invalidTagCount == 0
+ ? ""
+ : String.format(MESSAGE_FOUND_TAGS_TOO_LONG, invalidTagCount) + personListToString(invalidTagContacts);
+
+ int invalidCount = invalidFields.size();
+
+ String infoInvalidFields = invalidCount == 0
+ ? ""
+ : String.format(MESSAGE_INVALID_FIELDS, invalidCount) + String.join("\n", invalidFields);
+
+ return new CommandResult(infoAdded + infoDuplicates + infoInvalidTags + infoInvalidFields);
+ }
+
+ /**
+ * Converts a list of people to a string separated by a \n character.
+ *
+ * @param people The people to be converted.
+ * @return The result string.
+ */
+ public static String personListToString(List people) {
+ return String.join("\n", () -> people.stream().map(Person::toString).iterator()) + "\n";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ImportCommand)) {
+ return false;
+ }
+ ImportCommand other = (ImportCommand) o;
+ return contactsToAdd.equals(other.contactsToAdd)
+ && filename.equals(other.filename)
+ && invalidFields.equals(other.invalidFields);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..8890f670631 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -10,7 +10,7 @@
*/
public class ListCommand extends Command {
- public static final String COMMAND_WORD = "list";
+ public static final String COMMAND_WORD = "listc";
public static final String MESSAGE_SUCCESS = "Listed all persons";
diff --git a/src/main/java/seedu/address/logic/commands/ListTaskCommand.java b/src/main/java/seedu/address/logic/commands/ListTaskCommand.java
new file mode 100644
index 00000000000..7a0b7363bf8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListTaskCommand.java
@@ -0,0 +1,65 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LIST_ALL_TASK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LIST_COMPLETE_TASK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LIST_INCOMPLETE_TASK;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MARK_TASKS;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_UNMARK_TASKS;
+
+import seedu.address.model.Model;
+
+/**
+ * Lists all tasks in the task list to the user.
+ */
+public class ListTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "listt";
+
+ public static final String MESSAGE_SUCCESS_ALL = "Listed all tasks";
+
+ public static final String MESSAGE_SUCCESS_COMPLETED = "Listed all completed task(s)";
+
+ public static final String MESSAGE_SUCCESS_NOT_COMPLETED = "Listed all incomplete task(s)";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": List tasks\n"
+ + "Parameters: [" + PREFIX_LIST_ALL_TASK + "] for all (complete + incomplete) tasks or\n "
+ + "[" + PREFIX_LIST_INCOMPLETE_TASK + "] for incomplete tasks or\n "
+ + "[" + PREFIX_LIST_COMPLETE_TASK + "] for complete tasks\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_LIST_ALL_TASK;
+
+ private String prefix;
+
+ /**
+ * Constructs ListTaskCommand that takes in prefix.
+ *
+ * @param prefix prefix based on user input.
+ */
+ public ListTaskCommand(String prefix) {
+ requireNonNull(prefix);
+ this.prefix = prefix;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ if (prefix.equals("all")) {
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ return new CommandResult(MESSAGE_SUCCESS_ALL);
+ } else if (prefix.equals("c")) {
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_MARK_TASKS);
+ return new CommandResult(MESSAGE_SUCCESS_COMPLETED);
+ } else {
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_UNMARK_TASKS);
+ return new CommandResult(MESSAGE_SUCCESS_NOT_COMPLETED);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ListTaskCommand // instanceof handles nulls
+ && prefix.equals(((ListTaskCommand) other).prefix)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/MarkTaskCommand.java b/src/main/java/seedu/address/logic/commands/MarkTaskCommand.java
new file mode 100644
index 00000000000..9237a68c0e6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/MarkTaskCommand.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;
+
+/**
+ * Marks a task identified using it's displayed index as completed.
+ */
+public class MarkTaskCommand extends Command {
+ public static final String COMMAND_WORD = "mark";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Marks the task identified by the index number used in the displayed task list.\n"
+ + "Index must be a positive integer."
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_MARK_TASK_SUCCESS = "Marked Task: %1$s";
+
+ private final Index targetIndex;
+
+ /**
+ * Constructs MarkTaskCommand that takes in a task index.
+ *
+ * @param targetIndex the index of the task.
+ */
+ public MarkTaskCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List latestShownList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= latestShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToMark = latestShownList.get(targetIndex.getZeroBased());
+ model.markTask(taskToMark);
+ 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 MarkTaskCommand // instanceof handles nulls
+ && targetIndex.equals(((MarkTaskCommand) other).targetIndex)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnassignCommand.java b/src/main/java/seedu/address/logic/commands/UnassignCommand.java
new file mode 100644
index 00000000000..e41bdb5e8a5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnassignCommand.java
@@ -0,0 +1,125 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON;
+
+import java.util.ArrayList;
+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;
+import seedu.address.model.task.Task;
+
+/**
+ * Unassigns a contact in the contact list from a task.
+ */
+public class UnassignCommand extends Command {
+ public static final String COMMAND_WORD = "unassign";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Remove the person identified by the index number"
+ + "used in the displayed person list "
+ + "to the task identified by the index number used in the displayed task list.\n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "TASK_INDEX + " + PREFIX_PERSON + "PERSON_INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PERSON + "2";
+
+ public static final String MESSAGE_PREFIX = "Removed %1$s, Number: %2$s from the task `%3$s`\n";
+
+ public static final String NO_PERSON_ASSIGN = MESSAGE_PREFIX
+ + "There are currently no people assigned to this task.";
+
+ public static final String MESSAGE_REMOVE_PERSON_FROM_TASK_SUCCESS_MULTIPLE =
+ MESSAGE_PREFIX + "There are currently %4$s people assigned to this task.";
+
+ public static final String MESSAGE_REMOVE_PERSON_FROM_TASK_SUCCESS_SINGLE =
+ MESSAGE_PREFIX + "There is currently %4$s person assigned to this task.";
+
+ public static final String MESSAGE_PERSON_NOT_IN_TASK =
+ "Failed: The person selected is not associated with the task";
+
+ private final Index taskIndex;
+ private final Index personIndex;
+
+ /**
+ * Constructs UnassignCommand that takes in a task index and person index.
+ *
+ * @param taskIndex of the task in the filtered task list to be added to
+ * @param personIndex of the person in the filtered person list to add
+ */
+ public UnassignCommand(Index taskIndex, Index personIndex) {
+ requireNonNull(taskIndex);
+ requireNonNull(personIndex);
+
+ this.personIndex = personIndex;
+ this.taskIndex = taskIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List filteredPersonList = model.getFilteredPersonList();
+ List filteredTaskList = model.getFilteredTaskList();
+
+ if (personIndex.getZeroBased() >= filteredPersonList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ if (taskIndex.getZeroBased() >= filteredTaskList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ Task taskToEdit = filteredTaskList.get(taskIndex.getZeroBased());
+ Person personToRemove = filteredPersonList.get(personIndex.getZeroBased());
+
+ Task updatedTask = getUpdatedTask(personToRemove, taskToEdit);
+
+ model.setTask(taskToEdit, updatedTask);
+
+ int numberOfPeople = updatedTask.getNoOfPeople();
+
+ if (numberOfPeople == 0) {
+ return new CommandResult(
+ String.format(NO_PERSON_ASSIGN,
+ personToRemove.getName(), personToRemove.getPhone(), updatedTask));
+ }
+
+ if (numberOfPeople == 1) {
+ return new CommandResult(
+ String.format(MESSAGE_REMOVE_PERSON_FROM_TASK_SUCCESS_SINGLE,
+ personToRemove.getName(), personToRemove.getPhone(), updatedTask, numberOfPeople));
+ }
+
+ return new CommandResult(
+ String.format(MESSAGE_REMOVE_PERSON_FROM_TASK_SUCCESS_MULTIPLE,
+ personToRemove.getName(), personToRemove.getPhone(), updatedTask, numberOfPeople));
+ }
+
+ /**
+ * Obtains the updated task.
+ *
+ * @param personToRemove Person object to be removed.
+ * @param taskToUpdate Task to be changed.
+ * @return New edited Task.
+ * @throws CommandException If command format is wrong.
+ */
+ private Task getUpdatedTask(Person personToRemove, Task taskToUpdate) throws CommandException {
+ List updatedList = new ArrayList<>(taskToUpdate.getPeople());
+ if (!updatedList.remove(personToRemove)) {
+ throw new CommandException(MESSAGE_PERSON_NOT_IN_TASK);
+ }
+ Task editedTask = new Task(taskToUpdate.getName(), taskToUpdate.getDateTime(), taskToUpdate.getEndDateTime(),
+ updatedList, taskToUpdate.getTags(), taskToUpdate.getLink(), taskToUpdate.isTaskMark());
+ return editedTask;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof UnassignCommand
+ && personIndex.equals(((UnassignCommand) other).personIndex)
+ && taskIndex.equals(((UnassignCommand) other).taskIndex));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnmarkTaskCommand.java b/src/main/java/seedu/address/logic/commands/UnmarkTaskCommand.java
new file mode 100644
index 00000000000..a910013438f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnmarkTaskCommand.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;
+
+/**
+ * Unmarks a task identified using it's displayed index as not complete.
+ */
+public class UnmarkTaskCommand extends Command {
+ public static final String COMMAND_WORD = "unmark";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Unmarks the task identified by the index number used in the displayed task list.\n"
+ + "Index must be a positive integer."
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_UNMARK_TASK_SUCCESS = "Unmarked Task: %1$s";
+
+ private final Index targetIndex;
+
+
+ /**
+ * Constructs UnmarkTaskCommand that takes in a task index.
+ *
+ * @param targetIndex the index of the task.
+ */
+ public UnmarkTaskCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List latestShownList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= latestShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToMark = latestShownList.get(targetIndex.getZeroBased());
+ model.unmarkTask(taskToMark);
+ return new CommandResult(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToMark));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UnmarkTaskCommand // instanceof handles nulls
+ && targetIndex.equals(((UnmarkTaskCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java
new file mode 100644
index 00000000000..de361484bfc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java
@@ -0,0 +1,81 @@
+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;
+import seedu.address.model.person.PersonContainInTask;
+import seedu.address.model.task.Task;
+
+/**
+ * Finds and lists all persons in address book who were assigned to a given task.
+ */
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Display all contacts assigned to the task identified "
+ + "by the index number used in the displayed task list. \n"
+ + "Usage: "
+ + COMMAND_WORD + " "
+ + "INDEX (must be a positive integer) \n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String NO_CONTACT_ASSIGN = "Failed: There are no contacts assigned to task %d.";
+ public static final String DISPLAY_TASK_CONTACT_SUCCESS = "Found %1$d contact(s) assigned to this task";
+ private final Index targetIndex;
+
+ /**
+ * Constructs ViewTaskCommand that takes in targetIndex.
+ *
+ * @param targetIndex The index of the task to be targeted.
+ */
+ public ViewCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List latestShownTaskList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= latestShownTaskList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToDisplay = latestShownTaskList.get(targetIndex.getZeroBased());
+
+ List listOfPeople = taskToDisplay.getPeople();
+
+ int listSize = listOfPeople.size();
+
+ if (listSize < 1) {
+ return new CommandResult(String.format(NO_CONTACT_ASSIGN, targetIndex.getOneBased()));
+ }
+
+ PersonContainInTask predicate = new PersonContainInTask(listOfPeople);
+
+ model.updateFilteredPersonList(predicate);
+
+ listSize = model.getFilteredPersonList().size();
+
+ return new CommandResult(
+ String.format(DISPLAY_TASK_CONTACT_SUCCESS, listSize));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ViewCommand // instanceof handles nulls
+ && targetIndex.equals(((ViewCommand) 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
index 3b8bfa035e8..adfd3ec6cd7 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,19 +1,19 @@
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_GIT_USERNAME;
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.ParserUtil.arePrefixesPresent;
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.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -27,34 +27,57 @@ 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
+ *
+ * @param args String object of user input to be parsed.
+ * @return AddCommand object
+ * @throws ParseException If the input does not conform to 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);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_GIT_USERNAME, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_GIT_USERNAME)
|| !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ StringBuffer sb = displayInvalidParameters(argMultimap);
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, sb + "\n"
+ + 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());
+ GitUsername gitUsername = ParserUtil.parseGitUsername(argMultimap.getValue(PREFIX_GIT_USERNAME).get());
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, gitUsername, tagList);
return new AddCommand(person);
}
/**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
+ * Checks what parameters are missing in user's input. Returns the tags that are missing.
+ * Example: If n/ and p/ are missing, return "Missing/Invalid parameters: n/, p/".
+ *
+ * @param argMultimap Argument Multimap of user input that is read.
+ * @return StringBuffer format of missing parameters.
*/
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ public StringBuffer displayInvalidParameters(ArgumentMultimap argMultimap) {
+ String errorString = "Missing/Invalid parameters: ";
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME)) {
+ errorString += PREFIX_NAME + ", ";
+ }
+ if (!arePrefixesPresent(argMultimap, PREFIX_PHONE)) {
+ errorString += PREFIX_PHONE + ", ";
+ }
+ if (!arePrefixesPresent(argMultimap, PREFIX_EMAIL)) {
+ errorString += PREFIX_EMAIL + ", ";
+ }
+ if (!arePrefixesPresent(argMultimap, PREFIX_GIT_USERNAME)) {
+ errorString += PREFIX_GIT_USERNAME + ", ";
+ }
+ StringBuffer sb = new StringBuffer(errorString);
+ sb.delete(sb.length() - 2, sb.length() - 1); //Deleting last comma
+ return sb;
}
-
}
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..2dd540fd85b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java
@@ -0,0 +1,152 @@
+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_DATETIME;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_INTERVAL;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_RECURRENCE;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_RECURRENCE_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LINK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_RECURRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASKNAME;
+import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+
+import seedu.address.commons.util.TranslatorUtil;
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+
+/**
+ * Parses input arguments and creates a new AddTaskCommand object
+ */
+public class AddTaskCommandParser implements Parser {
+
+ private final String dateTimePattern = "dd-MM-yyyy HHmm";
+ private final SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(dateTimePattern);
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddTaskCommand for TASKNAME, DATETIME, TAG
+ * and returns an AddTaskCommand object for execution.
+ *
+ * @param args String object of user input to be parsed.
+ * @return AddTaskCommand object
+ * @throws ParseException If the input does not conform to the expected format.
+ */
+ public AddTaskCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TASKNAME, PREFIX_DATETIME,
+ PREFIX_TAG, PREFIX_LINK, PREFIX_RECURRING);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_TASKNAME, PREFIX_DATETIME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ StringBuffer sb = displayInvalidParameters(argMultimap);
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, sb + "\n"
+ + AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ String taskName = ParserUtil.parseTaskName(argMultimap.getValue(PREFIX_TASKNAME));
+ String dateTimeString = argMultimap.getValue(PREFIX_DATETIME).get();
+ LocalDateTime dateTime;
+ LocalDateTime endDateTime;
+ Set tags = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Link link = ParserUtil.parseLink(argMultimap.getValue(PREFIX_LINK));
+
+ try {
+ dateTimeFormatter.setLenient(false);
+
+ if (dateTimeString.contains(",")) {
+ String[] splits = dateTimeString.split(",");
+ dateTime = convertToLocalDateTime(dateTimeFormatter.parse(splits[0]));
+ endDateTime = convertToLocalDateTime(dateTimeFormatter.parse(splits[1]));
+ } else {
+ dateTime = convertToLocalDateTime(dateTimeFormatter.parse(dateTimeString));
+ endDateTime = null;
+ }
+ } catch (java.text.ParseException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_DATETIME, AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ // If recurring tag is present in argument
+ if (arePrefixesPresent(argMultimap, PREFIX_RECURRING)) {
+ int periodInt = 0;
+ int recurrenceInt = 0;
+
+ String[] periodMultipleArr = ParserUtil.parseRecurring(argMultimap.getValue(PREFIX_RECURRING));
+
+ String periodStr = periodMultipleArr[0].toLowerCase();
+ String recurrenceStr = periodMultipleArr[1];
+
+ Map periodMapping = TranslatorUtil.getPeriodMapping();
+
+ if (periodMapping.containsKey(periodStr)) {
+ periodInt = periodMapping.get(periodStr);
+ } else {
+ periodInt = parsePeriod(periodStr);
+ }
+
+ try {
+ recurrenceInt = Integer.parseInt(recurrenceStr);
+ } catch (NumberFormatException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_RECURRENCE,
+ AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ if (periodInt <= 0 || recurrenceInt <= 0) {
+ throw new ParseException(String.format(MESSAGE_INVALID_RECURRENCE_INDEX,
+ AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ return new AddTaskCommand(taskName, dateTime, endDateTime, tags, link, periodInt, recurrenceInt);
+ }
+
+ return new AddTaskCommand(taskName, dateTime, endDateTime, tags, link);
+ }
+
+ /**
+ * Converts a date object to LocalDateTime object.
+ *
+ * @param dateToConvert Date object to be converted.
+ * @return LocalDateTime object.
+ */
+ LocalDateTime convertToLocalDateTime(Date dateToConvert) {
+ return new java.sql.Timestamp(
+ dateToConvert.getTime()).toLocalDateTime();
+ }
+
+ /**
+ * Checks what parameters are missing in user's input. Returns the tags that are missing.
+ * Example: If tn/ and dt/ are missing, return "Missing/Invalid parameters: tn/, dt/".
+ *
+ * @param argMultimap Argument Multimap of user input that is read.
+ * @return StringBuffer format of missing parameters.
+ */
+ public StringBuffer displayInvalidParameters(ArgumentMultimap argMultimap) {
+ String errorString = "Missing/Invalid parameters: ";
+ if (!arePrefixesPresent(argMultimap, PREFIX_TASKNAME)) {
+ errorString += PREFIX_TASKNAME + ", ";
+ }
+ if (!arePrefixesPresent(argMultimap, PREFIX_DATETIME)) {
+ errorString += PREFIX_DATETIME + ", ";
+ }
+ StringBuffer sb = new StringBuffer(errorString);
+ sb.delete(sb.length() - 2, sb.length() - 1); //Deleting last comma
+ return sb;
+ }
+
+ private int parsePeriod(String periodStr) throws ParseException {
+ try {
+ return Integer.parseInt(periodStr);
+ } catch (NumberFormatException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_INTERVAL,
+ AddTaskCommand.MESSAGE_USAGE));
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..f2c70b55551 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -2,19 +2,32 @@
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.commands.AssignCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteTaskCommand;
import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.EditTaskCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindTaskCommand;
+import seedu.address.logic.commands.GenerateEmailsCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ImportCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListTaskCommand;
+import seedu.address.logic.commands.MarkTaskCommand;
+import seedu.address.logic.commands.UnassignCommand;
+import seedu.address.logic.commands.UnmarkTaskCommand;
+import seedu.address.logic.commands.ViewCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -59,15 +72,55 @@ public Command parseCommand(String userInput) throws ParseException {
case FindCommand.COMMAND_WORD:
return new FindCommandParser().parse(arguments);
+ case FindTaskCommand.COMMAND_WORD:
+ if (arguments.contains(PREFIX_DATETIME.toString())) {
+ return new FilterByDateTimeParser().parse(arguments);
+ } else {
+ return new FilterCommandParser().parse(arguments);
+ }
+
case ListCommand.COMMAND_WORD:
return new ListCommand();
+ case ListTaskCommand.COMMAND_WORD:
+ return new ListTaskCommandParser().parse(arguments);
+
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case AddTaskCommand.COMMAND_WORD:
+ return new AddTaskCommandParser().parse(arguments);
+
+ case DeleteTaskCommand.COMMAND_WORD:
+ return new DeleteTaskCommandParser().parse(arguments);
+
+ case AssignCommand.COMMAND_WORD:
+ return new AssignCommandParser().parse(arguments);
+
+ case UnassignCommand.COMMAND_WORD:
+ return new UnassignCommandParser().parse(arguments);
+
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+
+ case EditTaskCommand.COMMAND_WORD:
+ return new EditTaskCommandParser().parse(arguments);
+
+ case GenerateEmailsCommand.COMMAND_WORD:
+ return new GenerateEmailsCommandParser().parse(arguments);
+
+ case MarkTaskCommand.COMMAND_WORD:
+ return new MarkTaskCommandParser().parse(arguments);
+
+ case UnmarkTaskCommand.COMMAND_WORD:
+ return new UnmarkTaskCommandParser().parse(arguments);
+
+ case ImportCommand.COMMAND_WORD:
+ return new ImportCommandParser().parse(arguments);
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/AssignCommandParser.java b/src/main/java/seedu/address/logic/parser/AssignCommandParser.java
new file mode 100644
index 00000000000..7375e9c0eee
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AssignCommandParser.java
@@ -0,0 +1,52 @@
+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_PERSON;
+import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AssignCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new AssignCommand object
+ */
+public class AssignCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the AssignCommand
+ * and returns an AssignCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AssignCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_PERSON);
+
+ Index taskIndex;
+ Index personIndex;
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_PERSON)) {
+ String missingParameterMessage = displayInvalidParameters(argMultimap);
+ String errorMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingParameterMessage
+ + "\n" + AssignCommand.MESSAGE_USAGE);
+ throw new ParseException(errorMessage);
+ }
+
+ taskIndex = ParserUtil.parseIndex(argMultimap.getPreamble());
+ personIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PERSON).get());
+
+ return new AssignCommand(taskIndex, personIndex);
+ }
+
+ /**
+ * Checks what parameters are missing in user's input. Returns the tags that are missing.
+ * Example: If p/ are missing, return "Missing/Invalid parameters: p/".
+ *
+ * @param argMultimap Argument Multimap of user input that is read.
+ * @return String format of missing parameters.
+ */
+ public String displayInvalidParameters(ArgumentMultimap argMultimap) {
+ return "Missing/Invalid parameters: " + PREFIX_PERSON;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..cc590b079cb 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -12,4 +12,15 @@ public class CliSyntax {
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ /* Added prefix definitions */
+ public static final Prefix PREFIX_TASKNAME = new Prefix("tn/");
+ public static final Prefix PREFIX_DATETIME = new Prefix("dt/");
+ public static final Prefix PREFIX_PERSON = new Prefix("p/");
+ public static final Prefix PREFIX_GIT_USERNAME = new Prefix("u/");
+ public static final Prefix PREFIX_LINK = new Prefix("z/");
+ public static final Prefix PREFIX_RECURRING = new Prefix("r/");
+ public static final Prefix PREFIX_LIST_ALL_TASK = new Prefix("all/");
+ public static final Prefix PREFIX_LIST_INCOMPLETE_TASK = new Prefix("nc/");
+ public static final Prefix PREFIX_LIST_COMPLETE_TASK = new Prefix("c/");
+ public static final Prefix PREFIX_FILEPATH = new Prefix("fp/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 522b93081cc..c3879e8a378 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -1,6 +1,6 @@
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_PERSON_DISPLAYED_INDEX;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.DeleteCommand;
@@ -22,7 +22,7 @@ public DeleteCommand parse(String args) throws ParseException {
return new DeleteCommand(index);
} catch (ParseException pe) {
throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ String.format(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, DeleteCommand.MESSAGE_USAGE), pe);
}
}
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..0b7f44932b7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+
+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 {
+
+ /**
+ * 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 {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteTaskCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 845644b7dea..bd4d36b92a9 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -1,9 +1,9 @@
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.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GIT_USERNAME;
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;
@@ -32,14 +32,14 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_GIT_USERNAME, 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);
+ throw new ParseException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
@@ -52,9 +52,12 @@ public EditCommand parse(String args) throws ParseException {
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()));
+
+ if (argMultimap.getValue(PREFIX_GIT_USERNAME).isPresent()) {
+ editPersonDescriptor.setUsername(ParserUtil
+ .parseGitUsername(argMultimap.getValue(PREFIX_GIT_USERNAME).get()));
}
+
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
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..9bf967de79c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java
@@ -0,0 +1,114 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATETIME;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+import static seedu.address.commons.core.Messages.MESSAGE_NEED_AT_LEAST_ONE_VALID_PARAMETER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LINK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASKNAME;
+import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTaskCommand;
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new EditTaskCommand object
+ */
+public class EditTaskCommandParser implements Parser {
+ private final String dateTimePattern = "dd-MM-yyyy HHmm";
+ private final SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(dateTimePattern);
+
+ /**
+ * 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_TASKNAME, PREFIX_DATETIME, PREFIX_TAG, PREFIX_LINK);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_TASKNAME) && !arePrefixesPresent(argMultimap, PREFIX_DATETIME)
+ && !arePrefixesPresent(argMultimap, PREFIX_TAG) && !arePrefixesPresent(argMultimap, PREFIX_LINK)) {
+ String errorMessage = MESSAGE_NEED_AT_LEAST_ONE_VALID_PARAMETER;
+ throw new ParseException(String.format(errorMessage, EditTaskCommand.MESSAGE_USAGE));
+ }
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ EditTaskDescriptor editTaskDescriptor = new EditTaskDescriptor();
+ if (argMultimap.getValue(PREFIX_TASKNAME).isPresent()) {
+ String taskName = ParserUtil.parseTaskName(argMultimap.getValue(PREFIX_TASKNAME));
+ editTaskDescriptor.setName(taskName);
+ }
+ if (argMultimap.getValue(PREFIX_DATETIME).isPresent()) {
+ try {
+ dateTimeFormatter.setLenient(false);
+ String dateTimeString = argMultimap.getValue(PREFIX_DATETIME).get();
+ if (dateTimeString.contains(",")) {
+ String[] splits = dateTimeString.split(",");
+ editTaskDescriptor.setDate(convertToLocalDateTime(dateTimeFormatter.parse(splits[0])));
+ editTaskDescriptor.setEndDate(convertToLocalDateTime(dateTimeFormatter.parse(splits[1])));
+ } else {
+ editTaskDescriptor.setDate(convertToLocalDateTime(dateTimeFormatter.parse(dateTimeString)));
+ editTaskDescriptor.setEndDate(null);
+ }
+ } catch (java.text.ParseException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_DATETIME, EditTaskCommand.MESSAGE_USAGE));
+ }
+ }
+
+ if (argMultimap.getValue(PREFIX_LINK).isPresent()) {
+ editTaskDescriptor.setLink(ParserUtil.parseLink(argMultimap.getValue(PREFIX_LINK)));
+ }
+
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editTaskDescriptor::setTags);
+
+ if (!editTaskDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(String.format(EditTaskCommand.MESSAGE_NOT_EDITED, EditTaskCommand.MESSAGE_USAGE));
+ }
+
+ return new EditTaskCommand(index, editTaskDescriptor);
+ }
+
+ private LocalDateTime convertToLocalDateTime(Date dateToConvert) {
+ return new java.sql.Timestamp(
+ dateToConvert.getTime()).toLocalDateTime();
+ }
+
+ /**
+ * 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/FilterByDateTimeParser.java b/src/main/java/seedu/address/logic/parser/FilterByDateTimeParser.java
new file mode 100644
index 00000000000..caaa27cc95d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FilterByDateTimeParser.java
@@ -0,0 +1,180 @@
+package seedu.address.logic.parser;
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.ERROR_MESSAGE_INVALID_FORMAT;
+import static seedu.address.commons.core.Messages.ERROR_MESSAGE_INVALID_PARAMETER;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import seedu.address.logic.commands.FilterByDateCommand;
+import seedu.address.logic.commands.FindTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.task.TaskBetweenDatesPredicate;
+/**
+ * Parses input arguments and creates a new FilterByDate Command
+ */
+public class FilterByDateTimeParser implements Parser {
+
+ private static final String DATE_TIME_PATTERN = "dd-MM-yyyy HHmm";
+ private static final String DATE_ONLY_PATTERN = "dd-MM-yyyy";
+ private SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(DATE_TIME_PATTERN);
+ private SimpleDateFormat dateOnlyFormatter = new SimpleDateFormat(DATE_ONLY_PATTERN);
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FilterByDateTimeCommand
+ * and returns a FilterByDateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FilterByDateCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTaskCommand.MESSAGE_USAGE));
+ }
+
+ return new FilterByDateCommand(new TaskBetweenDatesPredicate(inBetweenDates(trimmedArgs)));
+ }
+
+ /**
+ * Gets the before and after date from a String of dates, e.g. "filter dt/22-08-2022 0800,23-08-2023 0800"
+ *
+ * @param dates dates to be seperated
+ * @return A list of before and after dates
+ */
+ public List inBetweenDates(String dates) throws DateTimeParseException, ParseException {
+ // from "dt/22-08-2022 0800,23-08-2022 0800" to ["dt", "22-08-2022 0800", "23-08-2022 0800"]
+ String[] splitDates = dates.split("[/,]");
+ if (splitDates.length != 3) {
+ throw new ParseException(ERROR_MESSAGE_INVALID_PARAMETER);
+ }
+
+ // check if time is provided
+ if (checkTime(splitDates[1]) && checkTime(splitDates[2])) {
+ return localDateTimeChecker(dayMonthYearTime(splitDates[1]), dayMonthYearTime(splitDates[2]));
+ } else if (!checkTime(splitDates[1]) && !checkTime(splitDates[2])) {
+ return timeAdder(localDateTimeChecker(dayMonthYear(splitDates[1]), dayMonthYear(splitDates[2])));
+ } else if (checkTime(splitDates[1]) && !checkTime(splitDates[2])) {
+ return localDateTimeTargetedAdder(dayMonthYearTime(splitDates[1]),
+ dayMonthYear(splitDates[2]), true);
+ } else if (!checkTime(splitDates[1]) && checkTime(splitDates[2])) {
+ return localDateTimeTargetedAdder(dayMonthYear(splitDates[1]),
+ dayMonthYearTime(splitDates[2]), false);
+ } else {
+ throw new ParseException(ERROR_MESSAGE_INVALID_FORMAT);
+ }
+ }
+
+ /**
+ * Check if input contains time
+ *
+ * @param datetime String datetime input form user
+ * @return true if date time contains time, else return false
+ */
+ private boolean checkTime(String datetime) {
+ return datetime.trim().split("[- ]").length == 4;
+ }
+
+ /**
+ * Converts user date only input into a list containing 2 date time elements
+ *
+ * @param datetime String of date without time in the format dd-MM-yyyy
+ * @return List of date sorted, first date at 0000 hrs (lower bound) and second date at 2359 hrs (upper bound)
+ * @throws ParseException Invalid date format
+ */
+ private LocalDateTime dayMonthYear(String datetime) throws ParseException {
+ try {
+ dateOnlyFormatter.setLenient(false);
+ return convertToLocalDateTime(dateOnlyFormatter.parse(datetime));
+ } catch (java.text.ParseException e) {
+ throw new ParseException(ERROR_MESSAGE_INVALID_FORMAT);
+ }
+ }
+ /**
+ * Converts user date time input into a list containing 2 date time elements
+ *
+ * @param datetime String of date time in the format dd-MM-yyyy HHmm
+ * @return List of date sorted, first dt being lower bound, second dt being upper bound
+ * @throws ParseException Invalid date format
+ */
+ private LocalDateTime dayMonthYearTime(String datetime) throws ParseException {
+ try {
+ dateTimeFormatter.setLenient(false);
+ return convertToLocalDateTime(dateTimeFormatter.parse(datetime));
+ } catch (java.text.ParseException e) {
+ throw new ParseException(ERROR_MESSAGE_INVALID_FORMAT);
+ }
+ }
+
+ /**
+ * Sort 2 given date time
+ *
+ * @param firstDateTime first date time to be sorted
+ * @param secondDateTime second date time to be sorted
+ * @return A list of date time, first dt being the earlier one, second dt being the later one
+ */
+ private List localDateTimeChecker(LocalDateTime firstDateTime, LocalDateTime secondDateTime) {
+ if (firstDateTime.isBefore(secondDateTime)) {
+ return Arrays.asList(firstDateTime, secondDateTime);
+ } else {
+ return Arrays.asList(secondDateTime, firstDateTime);
+ }
+ }
+
+ /**
+ * Sort 2 given date time, where only 1 of them have time
+ *
+ * @param firstDateTime first date time to be sorted
+ * @param secondDateTime second date time to be sorted
+ * @param firstDateContainsTime is true if first date contains time
+ * @return A list of date time, first dt being the earlier one, second dt being the later one
+ * @throws ParseException Invalid date time format
+ */
+ private List localDateTimeTargetedAdder(LocalDateTime firstDateTime,
+ LocalDateTime secondDateTime,
+ boolean firstDateContainsTime) throws ParseException {
+ if (firstDateTime.isBefore(secondDateTime) && firstDateContainsTime) {
+ return Arrays.asList(firstDateTime, secondDateTime.plusHours(23).plusMinutes(59));
+ } else if (firstDateTime.isBefore(secondDateTime) && !firstDateContainsTime) {
+ return Arrays.asList(firstDateTime, secondDateTime);
+ } else if (!firstDateTime.isBefore(secondDateTime) && firstDateContainsTime) {
+ return Arrays.asList(secondDateTime, firstDateTime);
+ } else if (!firstDateTime.isBefore(secondDateTime) && !firstDateContainsTime) {
+ return Arrays.asList(secondDateTime, firstDateTime.plusHours(23).plusMinutes(59));
+ } else {
+ throw new ParseException(MESSAGE_INVALID_COMMAND_FORMAT);
+ }
+ }
+
+ /**
+ * Adds upperbound time 23 hours and 59 min, in order to give it the property of full day search
+ * e.g. dt/21-02-2022, 22-02-2022 -> 21 Feb 2022 12mn, 22 Feb 2022 11:59pm
+ *
+ * @param listOfDates Sorted date time list
+ * @return Sorted date time list with proper time
+ */
+ private List timeAdder(List listOfDates) {
+ LocalDateTime setUpperBoundTiming = listOfDates.get(1).plusHours(23).plusMinutes(59);
+ listOfDates.set(1, setUpperBoundTiming);
+ return listOfDates;
+ }
+
+ /**
+ * Inspired from .\src\main\java\seedu\address\logic\parser\EditTaskCommandParser.java
+ *
+ * @param dateToConvert date to be converted
+ * @return Local Date Time
+ */
+ private LocalDateTime convertToLocalDateTime(Date dateToConvert) {
+ return new java.sql.Timestamp(
+ dateToConvert.getTime()).toLocalDateTime();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
new file mode 100644
index 00000000000..6efd97027cc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
@@ -0,0 +1,35 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_NO_KEYWORDS;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.FindTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.task.TaskNameContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindCommand object
+ */
+public class FilterCommandParser 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 FindTaskCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_NO_KEYWORDS, FindTaskCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ return new FindTaskCommand(new TaskNameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ }
+
+
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/GenerateEmailsCommandParser.java b/src/main/java/seedu/address/logic/parser/GenerateEmailsCommandParser.java
new file mode 100644
index 00000000000..25b9bb0c91a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/GenerateEmailsCommandParser.java
@@ -0,0 +1,23 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.GenerateEmailsCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new GenerateEmailsCommand object
+ */
+public class GenerateEmailsCommandParser implements Parser {
+
+ @Override
+ public GenerateEmailsCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new GenerateEmailsCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ImportCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java
new file mode 100644
index 00000000000..9f9b8baee7f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java
@@ -0,0 +1,142 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FILEPATH;
+import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+
+import seedu.address.logic.commands.ImportCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.GitUsername;
+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 ImportCommand object
+ */
+public class ImportCommandParser implements Parser {
+
+ public static final String MESSAGE_CSV_MISSING_HEADERS = "Missing headers in the file \"%s\"";
+
+ public static final String MESSAGE_FOLDER_SPECIFIED = "Error: \"%s\" is a directory";
+
+ public static final String MESSAGE_FILE_DOES_NOT_EXIST = "Error: could not find the file \"%s\".";
+
+ public static final String ERROR_INVALID_NAME = "Error: the name \"%s\" is invalid: " + Name.MESSAGE_CONSTRAINTS;
+
+ public static final String ERROR_INVALID_PHONE = "Error: the phone \"%s\" is invalid: " + Phone.MESSAGE_CONSTRAINTS;
+
+ public static final String ERROR_INVALID_EMAIL = "Error: the email \"%s\" is invalid: "
+ + Email.MESSAGE_CONSTRAINTS;
+
+ public static final String ERROR_INVALID_GITHUB = "Error: the github username\"%s\" is invalid: "
+ + GitUsername.MESSAGE_CONSTRAINTS;
+
+ public static final String ERROR_INVALID_TAG = "Error: the tag\"%s\" is invalid: "
+ + Tag.MESSAGE_CONSTRAINTS;
+
+
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ImportCommand
+ * and returns a ImportCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ImportCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_FILEPATH);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_FILEPATH)) {
+ throw new ParseException(ImportCommand.MESSAGE_USAGE);
+ }
+
+ File file = ParserUtil.parsePath(argMultimap.getValue(PREFIX_FILEPATH)).toFile();
+
+ if (!file.exists()) {
+ throw new ParseException(String.format(MESSAGE_FILE_DOES_NOT_EXIST, file.getPath()));
+ }
+
+ if (file.isDirectory()) {
+ throw new ParseException(String.format(MESSAGE_FOLDER_SPECIFIED, file.getPath()));
+ }
+
+ List personToAddList = new ArrayList<>();
+
+ List invalidFields = new ArrayList<>();
+
+ try {
+ Scanner sc = new Scanner(file);
+ List columns = Arrays.asList(sc.nextLine().split(","));
+ int nameIndex = columns.indexOf("Name");
+ int phoneIndex = columns.indexOf("Phone");
+ int emailIndex = columns.indexOf("Email");
+ int githubIndex = columns.indexOf("Github");
+ int tagsIndex = columns.indexOf("Tags");
+
+ if (List.of(nameIndex, phoneIndex, emailIndex, githubIndex, tagsIndex).contains(-1)) {
+ throw new ParseException(String.format(MESSAGE_CSV_MISSING_HEADERS, file.getPath()));
+ }
+
+ loop: while (sc.hasNextLine()) {
+ String[] values = sc.nextLine().split(",");
+ Name name;
+ Phone phone;
+ Email email;
+ GitUsername gitUsername;
+ Set tags = new HashSet<>();
+
+ try {
+ name = ParserUtil.parseName(values[nameIndex]);
+ } catch (ParseException e) {
+ invalidFields.add(String.format(ERROR_INVALID_NAME, values[nameIndex]));
+ continue;
+ }
+ try {
+ phone = ParserUtil.parsePhone(values[phoneIndex]);
+ } catch (ParseException e) {
+ invalidFields.add(String.format(ERROR_INVALID_PHONE, values[phoneIndex]));
+ continue;
+ }
+ try {
+ email = ParserUtil.parseEmail(values[emailIndex]);
+ } catch (ParseException e) {
+ invalidFields.add(String.format(ERROR_INVALID_EMAIL, values[emailIndex]));
+ continue;
+ }
+
+ try {
+ gitUsername = ParserUtil.parseGitUsername(values[githubIndex]);
+ } catch (ParseException e) {
+ invalidFields.add(String.format(ERROR_INVALID_GITHUB, values[githubIndex]));
+ continue;
+ }
+
+ for (String tag : values[tagsIndex].split("/")) {
+ try {
+ tags.add(ParserUtil.parseTag(tag));
+ } catch (ParseException e) {
+ invalidFields.add(String.format(ERROR_INVALID_TAG, tag));
+ continue loop;
+ }
+ }
+
+ personToAddList.add(new Person(name, phone, email, gitUsername, tags));
+ }
+
+ } catch (FileNotFoundException e) {
+ throw new ParseException(String.format(MESSAGE_CSV_MISSING_HEADERS, file.getPath()));
+ }
+
+ return new ImportCommand(personToAddList, argMultimap.getValue(PREFIX_FILEPATH).get(), invalidFields);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ListTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/ListTaskCommandParser.java
new file mode 100644
index 00000000000..fb36fba6e8e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListTaskCommandParser.java
@@ -0,0 +1,39 @@
+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_LIST_ALL_TASK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LIST_COMPLETE_TASK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LIST_INCOMPLETE_TASK;
+
+import seedu.address.logic.commands.ListTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Lists the task from the task list based on prefix.
+ */
+public class ListTaskCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListTaskCommand
+ * and returns an ListTaskCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ListTaskCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_LIST_ALL_TASK,
+ PREFIX_LIST_INCOMPLETE_TASK, PREFIX_LIST_COMPLETE_TASK);
+
+ if (argMultimap.getValue(PREFIX_LIST_ALL_TASK).isPresent()) {
+ return new ListTaskCommand("all");
+ } else if (argMultimap.getValue(PREFIX_LIST_INCOMPLETE_TASK).isPresent()) {
+ return new ListTaskCommand("nc");
+ } else if (argMultimap.getValue(PREFIX_LIST_COMPLETE_TASK).isPresent()) {
+ return new ListTaskCommand("c");
+ } else {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ListTaskCommand.MESSAGE_USAGE));
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/MarkTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkTaskCommandParser.java
new file mode 100644
index 00000000000..396720066a0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/MarkTaskCommandParser.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.MarkTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new MarkTaskCommand object
+ */
+public class MarkTaskCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the MarkTaskCommand
+ * and returns a MarkTaskCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public MarkTaskCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new MarkTaskCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_DISPLAYED_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..fa931d2fff4 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -1,26 +1,34 @@
package seedu.address.logic.parser;
import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.stream.Stream;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.commands.ImportCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.Task;
/**
* 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 is not a non-zero unsigned integer!";
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
@@ -65,20 +73,6 @@ public static Phone parsePhone(String phone) throws ParseException {
return new Phone(trimmedPhone);
}
- /**
- * Parses a {@code String address} into an {@code Address}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code address} is invalid.
- */
- public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
- String trimmedAddress = address.trim();
- if (!Address.isValidAddress(trimmedAddress)) {
- throw new ParseException(Address.MESSAGE_CONSTRAINTS);
- }
- return new Address(trimmedAddress);
- }
/**
* Parses a {@code String email} into an {@code Email}.
@@ -92,9 +86,48 @@ public static Email parseEmail(String email) throws ParseException {
if (!Email.isValidEmail(trimmedEmail)) {
throw new ParseException(Email.MESSAGE_CONSTRAINTS);
}
+
+ if (!Email.isValidLength(trimmedEmail)) {
+ throw new ParseException(Email.MESSAGE_CONSTRAINTS);
+ }
+
return new Email(trimmedEmail);
}
+ /**
+ * Parses task name
+ *
+ * @param option String input for Git username
+ * @return The Task name.
+ */
+ public static String parseTaskName(Optional option) throws ParseException {
+ requireNonNull(option);
+
+ String trimmedUsername = option.get().trim();
+ if (!Task.isValidLength(trimmedUsername)) {
+ throw new ParseException(Task.NAME_LENGTH_ERROR);
+ }
+
+ return trimmedUsername;
+ }
+
+ /**
+ * Parses Git username. Only allows AlphaNumeric and hyphens, as per GitHub's username formats.
+ * Spaces are not allowed.
+ *
+ * @param gitUsername String input for Git username
+ * @return GitUsername object created using user input
+ * @throws ParseException If gitUsername is not in alphanumeric format or has symbols other than hyphens.
+ */
+ public static GitUsername parseGitUsername(String gitUsername) throws ParseException {
+ requireNonNull(gitUsername);
+ String trimmedUsername = gitUsername.trim();
+ if (!GitUsername.isValidId(trimmedUsername)) {
+ throw new ParseException(GitUsername.MESSAGE_CONSTRAINTS);
+ }
+ return new GitUsername(trimmedUsername);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -121,4 +154,62 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses {@Code Optional option} into a {@code Link}.
+ */
+ public static Link parseLink(Optional option) throws ParseException {
+ requireNonNull(option);
+ if (option.isEmpty()) {
+ return new Link();
+ } else {
+ if (!Link.isValidLink(option.get())) {
+ throw new ParseException(Link.MESSAGE_CONSTRAINTS);
+ }
+ return new Link(option.get());
+ }
+ }
+
+ /**
+ * Parses {@Code Optional option} into a {@code String[]}.
+ */
+ public static String[] parseRecurring(Optional option) throws ParseException {
+ requireNonNull(option);
+ if (option.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ String arg = option.get();
+ String[] commands = arg.split(" ");
+
+ if (commands.length != 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ return commands;
+ }
+
+ /**
+ * Checks if the given prefixes are provided in the argMultimap
+ */
+ public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Creates and returns the Path of the import file.
+ *
+ * @param option Optional containing the filepath
+ * @return The path of the file
+ * @throws ParseException if no filepath is provide
+ */
+ public static Path parsePath(Optional option) throws ParseException {
+ requireNonNull(option);
+
+ if (option.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE));
+ }
+
+ return Path.of(option.get());
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/UnassignCommandParser.java b/src/main/java/seedu/address/logic/parser/UnassignCommandParser.java
new file mode 100644
index 00000000000..400b59407bb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnassignCommandParser.java
@@ -0,0 +1,53 @@
+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_PERSON;
+import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnassignCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new unassignCommand object
+ */
+public class UnassignCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the UnassignCommand
+ * and returns an UnassignCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnassignCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_PERSON);
+
+ Index taskIndex;
+ Index personIndex;
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_PERSON)) {
+ String missingParameterMessage = displayInvalidParameters(argMultimap);
+ String errorMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingParameterMessage
+ + "\n" + UnassignCommand.MESSAGE_USAGE);
+ throw new ParseException(errorMessage);
+ }
+
+ taskIndex = ParserUtil.parseIndex(argMultimap.getPreamble());
+ personIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PERSON).get());
+
+ return new UnassignCommand(taskIndex, personIndex);
+ }
+
+ /**
+ * Checks what parameters are missing in user's input. Returns the tags that are missing.
+ * Example: If p/ are missing, return "Missing/Invalid parameters: p/".
+ *
+ * @param argMultimap Argument Multimap of user input that is read.
+ * @return String format of missing parameters.
+ */
+ public String displayInvalidParameters(ArgumentMultimap argMultimap) {
+ return "Missing/Invalid parameters: " + PREFIX_PERSON;
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/UnmarkTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/UnmarkTaskCommandParser.java
new file mode 100644
index 00000000000..751bdef4388
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnmarkTaskCommandParser.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnmarkTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new UnmarkTaskCommand object
+ */
+public class UnmarkTaskCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the UnmarkTaskCommand
+ * and returns a UnmarkTaskCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnmarkTaskCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new UnmarkTaskCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
new file mode 100644
index 00000000000..710c34dafc6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ViewCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ViewTaskCommand object
+ */
+public class ViewCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ViewTaskCommand
+ * and returns a ViewTaskCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 1a943a0781a..45b2c26cf91 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -5,7 +5,10 @@
import java.util.List;
import javafx.collections.ObservableList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.GitUsername;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
import seedu.address.model.person.UniquePersonList;
/**
@@ -59,13 +62,57 @@ public void resetData(ReadOnlyAddressBook newData) {
//// person-level operations
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Returns true if a person with the same attributes as {@code person} exists in the address book.
*/
public boolean hasPerson(Person person) {
requireNonNull(person);
return persons.contains(person);
}
+ /**
+ * Returns true if gitUsername already exists in the address book.
+ *
+ * @param gitUsername Github gitUsername to be checked
+ * @return true/false whether gitUsername already exists.
+ */
+ public boolean hasUsername(GitUsername gitUsername) {
+ requireNonNull(gitUsername);
+ return persons.containsUsername(gitUsername);
+ }
+
+ /**
+ * Checks if AddressBook has an existing contact with this email.
+ *
+ * @param email Email to be checked.
+ * @return Whether email exists.
+ */
+ public boolean hasEmail(Email email) {
+ requireNonNull(email);
+ return persons.containsEmail(email);
+ }
+
+ /**
+ * Checks if AddressBook has an existing contact with this phone number.
+ *
+ * @param phone Phone number to be checked.
+ * @return Whether phone number exists.
+ */
+ public boolean hasPhone(Phone phone) {
+ requireNonNull(phone);
+ return persons.containsPhone(phone);
+ }
+
+ /**
+ * Checks if AddressBook has an existing contact with this GitUsername.
+ *
+ * @param gitUsername Git username to be checked.
+ * @return Whether git username exists.
+ */
+ public boolean hasGitUsername(GitUsername gitUsername) {
+ requireNonNull(gitUsername);
+ return persons.containsUsername(gitUsername);
+ }
+
/**
* Adds a person to the address book.
* The person must not already exist in the address book.
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..f5d9b139d15 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,7 +5,11 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.GitUsername;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.task.Task;
/**
* The API of the Model component.
@@ -14,6 +18,15 @@ public interface Model {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true;
+
+ /** {@code Predicate} that evaluate to false if task is unmark */
+ Predicate PREDICATE_SHOW_ALL_UNMARK_TASKS = task -> !task.isTaskMark();
+
+ /** {@code Predicate} that evaluate to true if task is mark */
+ Predicate PREDICATE_SHOW_ALL_MARK_TASKS = task -> task.isTaskMark();
+
/**
* Replaces user prefs data with the data in {@code userPrefs}.
*/
@@ -53,13 +66,38 @@ public interface Model {
ReadOnlyAddressBook getAddressBook();
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Returns true if the same person as {@code Person} exists in NUS Classes
*/
boolean hasPerson(Person person);
/**
- * Deletes the given person.
- * The person must exist in the address book.
+ * Returns true if a person with the same gitUsername as {@code gitUsername} exists in NUS Classes
+ *
+ * @param gitUsername
+ * @return Whether username exists
+ */
+ boolean hasUsername(GitUsername gitUsername);
+
+ /**
+ * Checks if Model has Email already existing.
+ *
+ * @param email Email to be checked.
+ * @return Whether email exists
+ */
+ boolean hasEmail(Email email);
+
+ /**
+ * Checks if Model has Phone already existing.
+ *
+ * @param phone Phone to be checked.
+ * @return Whether phone exists.
+ */
+ boolean hasPhone(Phone phone);
+
+ /**
+ * Deletes person.
+ *
+ * @param target Person to be deleted.
*/
void deletePerson(Person target);
@@ -84,4 +122,55 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ ReadOnlyTaskList getTaskList();
+
+ /** Returns an unmodifiable view of the filtered task list */
+ ObservableList getFilteredTaskList();
+
+ /**
+ * Updates the filter of the filtered task list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredTaskList(Predicate predicate);
+
+ /**
+ * Adds the task to the taskList.
+ *
+ * @param task the task to be added
+ */
+ void addTask(Task task);
+
+ /**
+ * Deletes the given task.
+ * The task must exist in the task list.
+ */
+ void deleteTask(Task target);
+
+ void setTask(Task taskToEdit, Task editedTask);
+
+ /**
+ * Returns true if a task with the same description as {@code task} exists in the task list.
+ */
+ boolean hasTask(Task task);
+
+ /**
+ * Marks the given task as completed.
+ *
+ * @param task the task to be marked.
+ */
+ void markTask(Task task);
+
+ /**
+ * Unmarks the given task as not complete.
+ *
+ * @param task the task to be unmarked.
+ */
+ void unmarkTask(Task task);
+
+ /**
+ * Replaces Task list data with the data in {@code tasklist}.
+ *
+ */
+ void setTaskList(ReadOnlyTaskList taskList);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 86c1df298d7..b6527dc8102 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.Email;
+import seedu.address.model.person.GitUsername;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.task.Task;
/**
* Represents the in-memory model of the address book data.
@@ -22,53 +26,75 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final FilteredList filteredTasks;
+ private final TaskList taskList;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, ReadOnlyTaskList taskList) {
+ requireAllNonNull(addressBook, userPrefs, taskList);
logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
+ this.taskList = new TaskList(taskList);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredTasks = new FilteredList<>(this.taskList.getTaskList());
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new UserPrefs(), new TaskList());
}
//=========== UserPrefs ==================================================================================
+ /**
+ * {@inheritDoc}
+ */
@Override
public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
requireNonNull(userPrefs);
this.userPrefs.resetData(userPrefs);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public ReadOnlyUserPrefs getUserPrefs() {
return userPrefs;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public GuiSettings getGuiSettings() {
return userPrefs.getGuiSettings();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void setGuiSettings(GuiSettings guiSettings) {
requireNonNull(guiSettings);
userPrefs.setGuiSettings(guiSettings);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public Path getAddressBookFilePath() {
return userPrefs.getAddressBookFilePath();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void setAddressBookFilePath(Path addressBookFilePath) {
requireNonNull(addressBookFilePath);
@@ -77,38 +103,85 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
//=========== AddressBook ================================================================================
+ /**
+ * {@inheritDoc}
+ */
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
this.addressBook.resetData(addressBook);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
return addressBook.hasPerson(person);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasUsername(GitUsername gitUsername) {
+ requireNonNull(gitUsername);
+ return addressBook.hasUsername(gitUsername);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasEmail(Email email) {
+ requireNonNull(email);
+ return addressBook.hasEmail(email);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasPhone(Phone phone) {
+ requireNonNull(phone);
+ return addressBook.hasPhone(phone);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public void deletePerson(Person target) {
addressBook.removePerson(target);
+ taskList.removePerson(target);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void addPerson(Person person) {
addressBook.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
addressBook.setPerson(target, editedPerson);
+ taskList.setPerson(target, editedPerson);
}
//=========== Filtered Person List Accessors =============================================================
@@ -122,6 +195,9 @@ public ObservableList getFilteredPersonList() {
return filteredPersons;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void updateFilteredPersonList(Predicate predicate) {
requireNonNull(predicate);
@@ -147,4 +223,87 @@ public boolean equals(Object obj) {
&& filteredPersons.equals(other.filteredPersons);
}
+ //=========== TaskList ================================================================================
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setTaskList(ReadOnlyTaskList taskList) {
+ this.taskList.resetData(taskList);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ReadOnlyTaskList getTaskList() {
+ return taskList;
+ }
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Task} backed by the internal list of
+ * {@code versionedAddressBook}
+ */
+ @Override
+ public ObservableList getFilteredTaskList() {
+ return filteredTasks;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void updateFilteredTaskList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredTasks.setPredicate(predicate);
+ }
+
+ @Override
+ public void addTask(Task task) {
+ taskList.addTask(task);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void deleteTask(Task target) {
+ taskList.deleteCurrTask(target);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setTask(Task target, Task editedTask) {
+ requireAllNonNull(target, editedTask);
+
+ taskList.setTask(target, editedTask);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasTask(Task task) {
+ requireNonNull(task);
+ return taskList.hasTask(task);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void markTask(Task task) {
+ taskList.markTask(task);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void unmarkTask(Task task) {
+ taskList.unmarkTask(task);
+ }
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyTaskList.java b/src/main/java/seedu/address/model/ReadOnlyTaskList.java
new file mode 100644
index 00000000000..a5aa0de5a5d
--- /dev/null
+++ b/src/main/java/seedu/address/model/ReadOnlyTaskList.java
@@ -0,0 +1,14 @@
+package seedu.address.model;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.task.Task;
+
+public interface ReadOnlyTaskList {
+
+ /**
+ * Returns ObservableList object containing Tasks.
+ *
+ * @return ObservableList of Task objects representing a TaskList.
+ */
+ ObservableList getTaskList();
+}
diff --git a/src/main/java/seedu/address/model/TaskList.java b/src/main/java/seedu/address/model/TaskList.java
new file mode 100644
index 00000000000..a91c7594cf1
--- /dev/null
+++ b/src/main/java/seedu/address/model/TaskList.java
@@ -0,0 +1,182 @@
+package seedu.address.model;
+
+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.Person;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.exceptions.TaskNotFoundException;
+
+/**
+ * Contains a list of Tasks
+ */
+public class TaskList implements Iterable, ReadOnlyTaskList {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ public TaskList() {}
+
+ /**
+ * Creates a TaskList using the Tasks in the {@code toBeCopied}
+ */
+ public TaskList(ReadOnlyTaskList toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ /**
+ * Replaces the contents of the task list with {@code tasks}.
+ * {@code tasks} must not contain duplicate tasks. //todo: really cannot contain duplicates?
+ */
+ public void setTasks(List tasks) {
+ requireAllNonNull(tasks);
+ internalList.setAll(tasks);
+ }
+
+
+ /**
+ * Resets the existing data of this {@code TaskList} with {@code newData}.
+ */
+ public void resetData(ReadOnlyTaskList newData) {
+ requireNonNull(newData);
+
+ setTasks(newData.getTaskList());
+ }
+
+
+ /**
+ * Adds a Task to the list.
+ *
+ * @param taskToAdd Task to be added.
+ */
+ public void addTask(Task taskToAdd) {
+ requireNonNull(taskToAdd);
+ this.internalList.add(taskToAdd);
+ }
+
+
+ @Override
+ public String toString() {
+ String output = "";
+ for (int i = 0; i < this.internalList.size(); i++) {
+ output += this.internalList.get(i) + "\n";
+ }
+ return output;
+ }
+
+ @Override
+ public ObservableList getTaskList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ /**
+ * Deletes a Task to the list.
+ *
+ * @param taskToDelete Task to be deleted.
+ */
+ public void deleteCurrTask(Task taskToDelete) {
+ requireNonNull(taskToDelete);
+ this.internalList.remove(taskToDelete);
+ }
+
+ /**
+ * Replaces Task at index target in TaskList with editedTask.
+ *
+ * @param target index of task to be changed.
+ * @param editedTask New task to replace the previous one.
+ */
+ public void setTask(Task target, Task editedTask) {
+ requireAllNonNull(target, editedTask);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new TaskNotFoundException();
+ }
+
+ internalList.set(index, editedTask);
+ }
+
+ /**
+ * Returns true if a task with the same description as {@code task} exists in the task list.
+ */
+ public boolean hasTask(Task task) {
+ requireNonNull(task);
+ return internalList.contains(task);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskList // instanceof handles nulls
+ && internalList.equals(((TaskList) other).internalList));
+
+ }
+
+ /**
+ * Removes the person from each task if the task contains the person.
+ * @param target the person to be removed.
+ */
+ public void removePerson(Person target) {
+ internalList.forEach(task -> task.removePerson(target));
+ }
+
+
+ /**
+ * Updates the person in each task if the task contains the person.
+ *
+ * @param target the person to be updated.
+ * @param editedPerson the updated person.
+ */
+ public void setPerson(Person target, Person editedPerson) {
+ internalList.forEach(task-> task.updatePerson(target, editedPerson));
+ }
+
+ /**
+ * Marks the task as completed and update the task list.
+ *
+ * @param task the task to be marked.
+ */
+ public void markTask(Task task) {
+ requireAllNonNull(task);
+
+ int index = internalList.indexOf(task);
+ if (index == -1) {
+ throw new TaskNotFoundException();
+ }
+
+ Task newTask = internalList.get(index);
+ newTask.markTask();
+ setTask(task, newTask);
+ }
+
+ /**
+ * Unmarks the task as not complete and update the task list.
+ *
+ * @param task the task to be unmarked.
+ */
+ public void unmarkTask(Task task) {
+ requireAllNonNull(task);
+
+ int index = internalList.indexOf(task);
+ if (index == -1) {
+ throw new TaskNotFoundException();
+ }
+
+ Task newTask = internalList.get(index);
+ newTask.unmarkTask();
+ setTask(task, newTask);
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..82b5b46c164 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path taskListFilePath = Paths.get("data", "tasklist.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -38,19 +39,31 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) {
setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
}
+ /**
+ * Returns the GUI settings.
+ */
public GuiSettings getGuiSettings() {
return guiSettings;
}
+ /**
+ * Sets the GUI setting with {@code guiSettings}.
+ */
public void setGuiSettings(GuiSettings guiSettings) {
requireNonNull(guiSettings);
this.guiSettings = guiSettings;
}
+ /**
+ * Returns the path of address book.
+ */
public Path getAddressBookFilePath() {
return addressBookFilePath;
}
+ /**
+ * Sets the path of address book to {@code addressBookFilePath}.
+ */
public void setAddressBookFilePath(Path addressBookFilePath) {
requireNonNull(addressBookFilePath);
this.addressBookFilePath = addressBookFilePath;
@@ -84,4 +97,10 @@ public String toString() {
return sb.toString();
}
+ /**
+ * Returns the path of task list.
+ */
+ public Path getTaskListFilePath() {
+ return taskListFilePath;
+ }
}
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
index f866e7133de..762f531f311 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -9,6 +9,8 @@
*/
public class Email {
+ private static final int MAX_LENGTH = 54;
+ private static final int MIN_LENGTH = 3;
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"
@@ -20,7 +22,9 @@ public class Email {
+ "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.";
+ + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."
+ + " 3. The total length of the E-mail must be at least " + MIN_LENGTH
+ + " and at most " + MAX_LENGTH + "characters long";
// 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 + "]"
@@ -30,7 +34,6 @@ public class Email {
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;
/**
@@ -51,6 +54,13 @@ public static boolean isValidEmail(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Returns if a given string is a valid length.
+ */
+ public static boolean isValidLength(String test) {
+ return (test.length() >= MIN_LENGTH && test.length() <= MAX_LENGTH);
+ }
+
@Override
public String toString() {
return value;
diff --git a/src/main/java/seedu/address/model/person/GitUsername.java b/src/main/java/seedu/address/model/person/GitUsername.java
new file mode 100644
index 00000000000..4063f8c58ac
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/GitUsername.java
@@ -0,0 +1,61 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * GitUsername represents the Github userID of a Person.
+ */
+public class GitUsername {
+
+ //Credits to Stack Overflow. Accreditation done in PPP
+ public static final String GIT_USERNAME_REGEX = "^[a-zA-Z0-9]+(-[a-zA-Z0-9]+){0,2}$";
+
+ public static final String MESSAGE_CONSTRAINTS = "Github usernames should only contain alphanumeric"
+ + " characters or single hyphens, and cannot begin or end with a hyphen.";
+
+ private String userid;
+
+ /**
+ * Constructs a Github GitUsername for Person.
+ *
+ * @param userid String username
+ */
+ public GitUsername(String userid) {
+ requireNonNull(userid);
+ this.userid = userid;
+ }
+
+ /**
+ * Returns username
+ *
+ * @return Github username of Person
+ */
+ public String getUsername() {
+ return this.userid;
+ }
+
+ /**
+ * Checks if Id is a valid Id. Id must be alphanumeric without a space in front.
+ *
+ * @param test Id being checked
+ * @return True for valid id, false for invalid id.
+ */
+ public static boolean isValidId(String test) {
+ return test.matches(GIT_USERNAME_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return this.userid;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof GitUsername)) {
+ return false;
+ }
+ GitUsername otherGitUsername = (GitUsername) other;
+ return this.userid.equals(otherGitUsername.userid);
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 79244d71cf7..ba4a8506508 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -10,13 +10,15 @@
public class Name {
public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
+ "A name should only contain alphabets, valid symbols "
+ + "(commas, full stops, slashes, apostrophes and hyphens)"
+ + " 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 static final String VALIDATION_REGEX = "[a-zA-z,./'-][a-zA-z ,./'-]*";
public final String fullName;
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
index c9b5868427c..4786d89535d 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
@@ -4,6 +4,7 @@
import java.util.function.Predicate;
import seedu.address.commons.util.StringUtil;
+import seedu.address.model.tag.Tag;
/**
* Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
@@ -17,6 +18,21 @@ public NameContainsKeywordsPredicate(List keywords) {
@Override
public boolean test(Person person) {
+ boolean isEqual = false;
+ for (String str: keywords) {
+ Tag[] tags = new Tag[person.getTags().size()];
+ person.getTags().toArray(tags);
+ for (int i = 0; i < tags.length; i++) {
+ if (StringUtil.containsWordIgnoreCase(tags[i].toString(), str.trim())) {
+ isEqual = true;
+ break;
+ }
+ }
+ if (isEqual) {
+ return isEqual;
+ }
+ }
+
return keywords.stream()
.anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index 8ff1d83fe89..17f1bcb57f7 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -6,6 +6,7 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import seedu.address.model.tag.Tag;
@@ -19,23 +20,31 @@ public class Person {
private final Name name;
private final Phone phone;
private final Email email;
+ private final GitUsername gitUsername;
// Data fields
- private final Address address;
private final Set tags = new HashSet<>();
+
/**
- * Every field must be present and not null.
+ * Constructs a Person object using 5 fields: name, Phone, Email, Address, and any number of tags.
+ *
+ * @param name Name of Person.
+ * @param phone Phone Number of Person.
+ * @param email Email address of Person.
+ * @param gitUsername Github GitUsername of Person.
+ * @param tags Tags for Person.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(Name name, Phone phone, Email email, GitUsername gitUsername, Set tags) {
+ requireAllNonNull(name, phone, email, gitUsername, tags);
this.name = name;
this.phone = phone;
this.email = email;
- this.address = address;
+ this.gitUsername = gitUsername;
this.tags.addAll(tags);
}
+
public Name getName() {
return name;
}
@@ -48,8 +57,8 @@ public Email getEmail() {
return email;
}
- public Address getAddress() {
- return address;
+ public GitUsername getUsername() {
+ return this.gitUsername;
}
/**
@@ -61,7 +70,7 @@ public Set getTags() {
}
/**
- * Returns true if both persons have the same name.
+ * Returns true if both persons are the same object.
* This defines a weaker notion of equality between two persons.
*/
public boolean isSamePerson(Person otherPerson) {
@@ -70,7 +79,7 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.equals(this);
}
/**
@@ -88,17 +97,16 @@ public boolean equals(Object other) {
}
Person otherPerson = (Person) other;
- return otherPerson.getName().equals(getName())
- && otherPerson.getPhone().equals(getPhone())
+ return otherPerson.getPhone().equals(getPhone())
&& otherPerson.getEmail().equals(getEmail())
- && otherPerson.getAddress().equals(getAddress())
+ && otherPerson.getUsername().equals(getUsername())
&& 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);
+ return Objects.hash(name, phone, email, gitUsername, tags);
}
@Override
@@ -109,13 +117,13 @@ public String toString() {
.append(getPhone())
.append("; Email: ")
.append(getEmail())
- .append("; Address: ")
- .append(getAddress());
+ .append("; Github: ")
+ .append(getUsername());
Set tags = getTags();
if (!tags.isEmpty()) {
- builder.append("; Tags: ");
- tags.forEach(builder::append);
+ builder.append("; Tags: ")
+ .append(tags.stream().map(Tag::toString).collect(Collectors.joining(", ")));
}
return builder.toString();
}
diff --git a/src/main/java/seedu/address/model/person/PersonContainInTask.java b/src/main/java/seedu/address/model/person/PersonContainInTask.java
new file mode 100644
index 00000000000..679b182990f
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PersonContainInTask.java
@@ -0,0 +1,33 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Tests that a {@code Person} matches any of the person object provided.
+ */
+public class PersonContainInTask implements Predicate {
+ private final List targetPersonList;
+
+ public PersonContainInTask(List targetPersonList) {
+ this.targetPersonList = targetPersonList;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean test(Person person) {
+ return targetPersonList.stream()
+ .anyMatch(targetPerson -> targetPerson.isSamePerson(person));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object other) {
+ return other == this;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index 872c76b382f..6fa0beda8f6 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -11,8 +11,9 @@ 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,}";
+ "Phone numbers should only contain numbers, and it should be at least "
+ + "3 digits long and at most 15 digits long.";
+ public static final String VALIDATION_REGEX = "\\d{3,15}";
public final String value;
/**
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index 0fee4fe57e6..0a5ef26fec2 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -36,6 +36,54 @@ public boolean contains(Person toCheck) {
return internalList.stream().anyMatch(toCheck::isSamePerson);
}
+ /**
+ * Returns true if the list contains a Person with an identical GitUsername as the given argument.
+ *
+ * @param toCheck Github username to be checked.
+ * @return true/false whether username already exists.
+ */
+ public boolean containsUsername(GitUsername toCheck) {
+ requireNonNull(toCheck);
+ for (int i = 0; i < internalList.size(); i++) {
+ if (internalList.get(i).getUsername().equals(toCheck)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the list contains a person with an identical Email.
+ *
+ * @param toCheck Email to be checked.
+ * @return Whether email already exists.
+ */
+ public boolean containsEmail(Email toCheck) {
+ requireNonNull(toCheck);
+ for (int i = 0; i < internalList.size(); i++) {
+ if (internalList.get(i).getEmail().equals(toCheck)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the list contains a person with an identical phone.
+ *
+ * @param toCheck Phone to be checked.
+ * @return Whether Phone already exists.
+ */
+ public boolean containsPhone(Phone toCheck) {
+ requireNonNull(toCheck);
+ for (int i = 0; i < internalList.size(); i++) {
+ if (internalList.get(i).getPhone().equals(toCheck)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Adds a person to the list.
* The person must not already exist in the list.
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index b0ea7e7dad7..89a2437825d 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -9,8 +9,11 @@
*/
public class Tag {
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
+ public static final String MESSAGE_CONSTRAINTS =
+ "Tag names should contain ASCII characters and should not be empty";
+
+ //Format for valid tag name
+ public static final String VALIDATION_REGEX = "\\p{ASCII}+";
public final String tagName;
@@ -26,10 +29,13 @@ public Tag(String tagName) {
}
/**
- * Returns true if a given string is a valid tag name.
+ * Returns true if a String is a valid tag name. Tag must be alphanumeric.
+ *
+ * @param testString String to be tested.
+ * @return boolean true/false on whether tag name is valid.
*/
- public static boolean isValidTagName(String test) {
- return test.matches(VALIDATION_REGEX);
+ public static boolean isValidTagName(String testString) {
+ return testString.matches(VALIDATION_REGEX);
}
@Override
@@ -48,7 +54,7 @@ public int hashCode() {
* Format state as text for viewing.
*/
public String toString() {
- return '[' + tagName + ']';
+ return tagName;
}
}
diff --git a/src/main/java/seedu/address/model/task/Link.java b/src/main/java/seedu/address/model/task/Link.java
new file mode 100644
index 00000000000..1c890ef7f41
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Link.java
@@ -0,0 +1,61 @@
+package seedu.address.model.task;
+
+import static java.util.Objects.requireNonNull;
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * Represents a link associated with the task.
+ */
+public class Link {
+ public static final String MESSAGE_CONSTRAINTS = "The link provided should follows the proper URL format "
+ + "(must have reference to 'https://' or 'http://'. E.g. www.google.com will not be considered a valid URL"
+ + " whereas https://www.google.com will be considered a valid URL.";
+ private String link;
+
+ /**
+ * Constructs a {@code Link}.
+ *
+ * @param link A link
+ */
+ public Link(String link) {
+ requireNonNull(link);
+
+ this.link = link;
+ }
+
+ public Link() {
+ this.link = null;
+ }
+
+ /**
+ * Checks if link is valid.
+ * @param link Link of users.
+ * @throws IllegalArgumentException If link is not a valid URL.
+ */
+ public static boolean isValidLink(String link) {
+ try {
+ new URL(link).toURI();
+ } catch (URISyntaxException | MalformedURLException e) {
+ // Unconventional, but currently only way to check whether a link is valid.
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || this.link.equals(((Link) other).link);
+ }
+
+ @Override
+ public String toString() {
+ return link;
+ }
+
+ public boolean isEmpty() {
+ return link == null;
+ }
+}
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..c7aac94e391
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Task.java
@@ -0,0 +1,371 @@
+package seedu.address.model.task;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Represents a Task in NUSClasses.
+ * Task consists of a String object representing a name and a LocalDateTime object representing the date and time.
+ */
+public class Task {
+ public static final DateTimeFormatter FORMAT_TIME = DateTimeFormatter.ofPattern("h.mm a");
+ public static final DateTimeFormatter FORMAT_DAY_OF_WEEK = DateTimeFormatter.ofPattern("EEE, h.mm a");
+ public static final DateTimeFormatter FORMAT_MONTH = DateTimeFormatter.ofPattern("dd MMM, h.mm a");
+ public static final DateTimeFormatter FORMAT_YEAR = DateTimeFormatter.ofPattern("dd MMM yyyy, h.mm a");
+ private static final int MAX_LENGTH = 100;
+ private static final int MIN_LENGTH = 3;
+ public static final String NAME_LENGTH_ERROR = "The name of the tasks must be at least "
+ + MIN_LENGTH + " characters long and at most " + MAX_LENGTH + " characters long";
+ private String name;
+ private LocalDateTime dateTime;
+ private LocalDateTime endDateTime;
+ private List people;
+ private Set tags;
+ private Link link;
+ private boolean isMarkDone;
+
+ /**
+ * Constructs Task that takes in the following parameters.
+ *
+ * @param name Name of task.
+ * @param people People to be added to the list.
+ * @param dateTime LocalDateTime object representing Date and Time for Task.
+ * @param tags Tags for the tasks.
+ * @param link Link to be added to the task.
+ * @param isMarkDone true if task is done, else false.
+ */
+ public Task(String name, LocalDateTime dateTime, LocalDateTime endDateTime, List people, Set tags,
+ Link link, boolean isMarkDone) {
+ this.name = name;
+ this.dateTime = dateTime;
+ this.endDateTime = endDateTime;
+ this.people = new ArrayList<>(people);
+ this.tags = tags;
+ this.link = link;
+ this.isMarkDone = isMarkDone;
+ }
+
+ /**
+ * Constructor for Task with people but no endDateTime.
+ */
+ public Task(String name, LocalDateTime dateTime, List people, Set tags, Link link,
+ boolean isMarkDone) {
+ this(name, dateTime, null, people, tags, link, isMarkDone);
+ }
+
+ /**
+ * Constructs Task with endDateTime but no people.
+ */
+ public Task(String name, LocalDateTime dateTime, LocalDateTime endDateTime, Set tags, Link link,
+ boolean isMarkDone) {
+ this(name, dateTime, endDateTime, new ArrayList<>(), tags, link, isMarkDone);
+ }
+
+ /**
+ * Constructs Task without people or endDateTime.
+ */
+ public Task(String name, LocalDateTime dateTime, Set tags, Link link, boolean isMarkDone) {
+ this(name, dateTime, null, new ArrayList<>(), tags, link, isMarkDone);
+ }
+
+
+
+ /**
+ * Changes name of Task.
+ *
+ * @param name new Name to be changed.
+ */
+ public void changeName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Adds a person to the list of people associated with the task.
+ *
+ * @param person Person to add.
+ */
+ public void addPerson(Person person) {
+ people.add(person);
+ }
+
+ /**
+ * Removes a person from the list of people associated with the task.
+ *
+ * @param person Person to remove.
+ */
+ public void removePerson(Person person) {
+ people.remove(person);
+ }
+
+ /**
+ * Updates a person in the list of people associated with the task.
+ *
+ * @param person The person to update.
+ * @param editedPerson The edited person.
+ */
+ public void updatePerson(Person person, Person editedPerson) {
+ int index = people.indexOf(person);
+ if (index != -1) {
+ people.set(index, editedPerson);
+ }
+ }
+
+ /**
+ * Changes DateTime of Task.
+ *
+ * @param newDateTime LocalDateTime of new DateTime.
+ */
+ public void changeDateTime(LocalDateTime newDateTime) {
+ this.dateTime = newDateTime;
+ }
+
+ /**
+ * Changes EndDateTime of Task.
+ *
+ * @param endDateTime LocalDateTime of new endDateTime.
+ */
+ public void changeEndDateTime(LocalDateTime endDateTime) {
+ this.endDateTime = endDateTime;
+ }
+
+ @Override
+ public String toString() {
+ return this.name + " " + dateTime.format(FORMAT_YEAR);
+ }
+
+ /**
+ * Checks if this task has an end date time or not.
+ * @return true if this task has an end date time, false otherwise.
+ */
+ public boolean hasEndDateTime() {
+ return this.endDateTime != null;
+ }
+
+ /**
+ * Returns a user-friendly representation of the dateTime and endDateTime.
+ */
+ public String getDateTimeString() {
+ if (endDateTime == null) {
+ return getDeadline();
+ }
+ return getDateTimeRange();
+ }
+
+ private String getDeadline() {
+ return String.format("Due: %s", getUserFriendlyDateTime(dateTime));
+ }
+
+ private String getDateTimeRange() {
+ assert endDateTime != null;
+ LocalDateTime date = dateTime.toLocalDate().atStartOfDay();
+ LocalDateTime endDate = endDateTime.toLocalDate().atStartOfDay();
+
+ String result;
+ if (Duration.between(date, endDate).toDays() == 0) {
+ result = String.format("%s - %s", getUserFriendlyDateTime(dateTime), endDateTime.format(FORMAT_TIME));
+ } else {
+ result = String.format("%s - %s", getUserFriendlyDateTime(dateTime), getUserFriendlyDateTime(endDateTime));
+ }
+ return String.format("From: %s", result);
+ }
+
+ /**
+ * Methods for generating date representation relative to current calendar day.
+ *
+ * @param dateTime The dateTime to be processed.
+ * @return User-friendly dateTime string.
+ */
+ static String getUserFriendlyDateTime(LocalDateTime dateTime) {
+ LocalDateTime today = LocalDate.now().atStartOfDay();
+ LocalDateTime date = dateTime.toLocalDate().atStartOfDay();
+ long daysFrom = Duration.between(today, date).toDays();
+ String result;
+ if (daysFrom == 0) { // today
+ result = String.format("Today, %s", dateTime.format(FORMAT_TIME));
+ } else if (daysFrom == 1) { // tomorrow
+ result = String.format("Tomorrow, %s", dateTime.format(FORMAT_TIME));
+ } else if (daysFrom >= 2 && daysFrom <= 7) { // this week
+ result = dateTime.format(FORMAT_DAY_OF_WEEK);
+ } else if (date.getYear() == today.getYear()) { // this year
+ result = dateTime.format(FORMAT_MONTH);
+ } else { // next year onwards
+ result = dateTime.format(FORMAT_YEAR);
+ }
+ return result;
+ }
+
+
+ /**
+ * Returns a user-friendly representation of the endDateTime.
+ */
+ public String getEndDateTimeString() {
+ return this.endDateTime.format(FORMAT_YEAR);
+ }
+
+
+ /**
+ * Returns DateTime of Task.
+ *
+ * @return DateTime object of Task.
+ */
+ public LocalDateTime getDateTime() {
+ return this.dateTime;
+ }
+
+ /**
+ * Returns endDateTime of Task.
+ *
+ * @return endDateTime object of Task.
+ */
+ public LocalDateTime getEndDateTime() {
+ return this.endDateTime;
+ }
+
+
+ /**
+ * Returns name of Task.
+ *
+ * @return Name of Task.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns Tag of Task.
+ *
+ * @return Tag of Task.
+ */
+ public Set getTags() {
+ return Collections.unmodifiableSet(tags);
+ }
+
+ /**
+ * Sets new tags for Task object
+ *
+ * @param tags new Set of tags to replace previous ones
+ */
+ public void setTags(Set tags) {
+ this.tags = tags;
+ }
+
+ /**
+ * Returns list of People assigned to Task.
+ *
+ * @return List of People.
+ */
+ public List getPeople() {
+ return this.people;
+ }
+
+ /**
+ * Returns true if both tasks have the same name.
+ */
+ public boolean isSameTask(Task otherTask) {
+ if (otherTask == this) {
+ return true;
+ }
+ return otherTask != null
+ && otherTask.getName().equals(getName());
+ }
+
+ /**
+ * Returns the number of people assigned to Task.
+ *
+ * @return Number of people.
+ */
+ public int getNoOfPeople() {
+ return this.people.size();
+ }
+
+ /**
+ * Returns a copy-paste friendly string containing all the emails related to this task.
+ * The emails will be joined with a comma separator (e.g. "e1234578@u.nus.edu.sg, e12121212@u.nus.edu.sg").
+ *
+ * @return The generated email string
+ */
+ public String getEmails() {
+ String[] emails = this.people.stream().map(p -> p.getEmail().toString()).toArray(String[]::new);
+ return String.join(", ", emails);
+ }
+
+ /**
+ * Checks if this task contains the person.
+ * @param p the person to check for
+ * @return true if this task contains the person, false otherwise.
+ */
+ public boolean containsPerson(Person p) {
+ return this.people.contains(p);
+ }
+
+ /**
+ * Returns the zoom link assigned to Task.
+ *
+ * @return A link.
+ */
+ public Link getLink() {
+ return link;
+ }
+
+ /**
+ * Sets the isMarkDone as true to show that the task is done.
+ */
+ public void markTask() {
+ this.isMarkDone = true;
+ }
+
+ /**
+ * Sets the isMarkDone as false to show that the task is not done.
+ */
+ public void unmarkTask() {
+ this.isMarkDone = false;
+ }
+
+ /**
+ * Returns the status if the task is mark done.
+ */
+ public boolean isTaskMark() {
+ return isMarkDone;
+ }
+
+ /**
+ * Returns true if endDateTime is earlier than dateTime
+ *
+ * @return True if endDateTime is earlier, false if endDateTime is later
+ */
+ public boolean hasInvalidDateRange() {
+ return endDateTime != null && dateTime.compareTo(endDateTime) >= 0;
+ }
+
+ /**
+ * Returns if a given string is a valid length.
+ */
+ public static boolean isValidLength(String testString) {
+ return (testString.length() >= MIN_LENGTH && testString.length() <= MAX_LENGTH);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Task)) {
+ return false;
+ }
+
+ Task otherTask = (Task) other;
+ return otherTask.getName().equals(this.getName())
+ && otherTask.getDateTime().equals(this.getDateTime())
+ && otherTask.getPeople().equals(this.getPeople());
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/TaskBetweenDatesPredicate.java b/src/main/java/seedu/address/model/task/TaskBetweenDatesPredicate.java
new file mode 100644
index 00000000000..3a1a80e7e54
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskBetweenDatesPredicate.java
@@ -0,0 +1,67 @@
+package seedu.address.model.task;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Tests that a {@code Task}'s {@code Name} matches any of the keywords given.
+ */
+public class TaskBetweenDatesPredicate implements Predicate {
+ private final List beforeAfterDateList;
+
+ public TaskBetweenDatesPredicate(List beforeAfterDateList) {
+ this.beforeAfterDateList = beforeAfterDateList;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ if (task.getEndDateTime() == null) {
+ boolean isTaskDateAfterMinDateRange = task.getDateTime().isAfter(beforeAfterDateList.get(0));
+ boolean isTaskDateBeforeMaxDateRange = task.getDateTime().isBefore(beforeAfterDateList.get(1));
+ boolean isTaskDateOnMinDateRange = task.getDateTime().isEqual(beforeAfterDateList.get(0));
+ boolean isTaskDateOnMaxDateRange = task.getDateTime().isEqual(beforeAfterDateList.get(1));
+
+ boolean isTaskDateValid = isTaskDateAfterMinDateRange && isTaskDateBeforeMaxDateRange;
+ return (isTaskDateValid || isTaskDateOnMinDateRange || isTaskDateOnMaxDateRange);
+
+ } else {
+ boolean isTaskDateAfterMinDateRange = task.getDateTime().isAfter(beforeAfterDateList.get(0));
+ boolean isTaskDateBeforeMaxDateRange = task.getDateTime().isBefore(beforeAfterDateList.get(1));
+ boolean isTaskDateOnMinDateRange = task.getDateTime().isEqual(beforeAfterDateList.get(0));
+ boolean isTaskDateOnMaxDateRange = task.getDateTime().isEqual(beforeAfterDateList.get(1));
+ boolean isTaskEndDateAfterMinDateRange = task.getEndDateTime().isAfter(beforeAfterDateList.get(0));
+ boolean isTaskEndDateBeforeMaxDateRange = task.getEndDateTime().isBefore(beforeAfterDateList.get(1));
+
+ boolean isTaskDateValid = isTaskDateAfterMinDateRange && isTaskDateBeforeMaxDateRange;
+ boolean isTaskEndDateValid = isTaskEndDateAfterMinDateRange && isTaskEndDateBeforeMaxDateRange;
+
+ return (isTaskDateValid || isTaskEndDateValid || isTaskDateOnMinDateRange || isTaskDateOnMaxDateRange);
+ }
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskBetweenDatesPredicate // instanceof handles nulls
+ && areDatesEqual(this.beforeAfterDateList, ((TaskBetweenDatesPredicate) other)
+ .beforeAfterDateList)); // state check
+ }
+
+ /**
+ * Checks if 2 list of dates are the same date.
+ *
+ * @param list1 First list of dates.
+ * @param list2 Second list of dates.
+ * @return True if both list have the same dates.
+ */
+ private boolean areDatesEqual(List list1, List list2) {
+ boolean toReturn = false;
+ for (int i = 0; i < list1.size(); i++) {
+ toReturn = list1.get(i).isEqual(list2.get(i));
+ }
+ return toReturn;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/task/TaskNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskNameContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..aab111ddfb2
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskNameContainsKeywordsPredicate.java
@@ -0,0 +1,63 @@
+package seedu.address.model.task;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Tests that a {@code Task}'s {@code Name} matches any of the keywords given.
+ */
+public class TaskNameContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public TaskNameContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ boolean isEqual = false;
+ for (String str: keywords) {
+ if (StringUtil.containsWordIgnoreCase(task.getName(), str.trim())) {
+ isEqual = true;
+ break;
+ }
+ Tag[] tagsArr = new Tag[task.getTags().size()];
+ task.getTags().toArray(tagsArr);
+ for (int i = 0; i < tagsArr.length; i++) {
+ if (StringUtil.containsWordIgnoreCase(tagsArr[i].toString(), str.trim())) {
+ isEqual = true;
+ break;
+ }
+ }
+ if (isEqual) {
+ break;
+ }
+ }
+ return isEqual;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskNameContainsKeywordsPredicate // instanceof handles nulls
+ && areKeywordsEqual(this.keywords, ((TaskNameContainsKeywordsPredicate) other)
+ .keywords)); // state check
+ }
+
+ private boolean areKeywordsEqual(List list1, List list2) {
+ if (list1.size() == list2.size()) {
+ boolean toReturn = false;
+ for (int i = 0; i < list1.size(); i++) {
+ toReturn = list1.get(i).equals(list2.get(i));
+ }
+ return toReturn;
+ } else {
+ return false;
+ }
+
+ }
+
+}
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..0a4c154f4ea
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java
@@ -0,0 +1,3 @@
+package seedu.address.model.task.exceptions;
+
+public class TaskNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..e05a327ecf2 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,17 +1,22 @@
package seedu.address.model.util;
+import java.time.LocalDateTime;
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.ReadOnlyTaskList;
+import seedu.address.model.TaskList;
import seedu.address.model.person.Email;
+import seedu.address.model.person.GitUsername;
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.task.Link;
+import seedu.address.model.task.Task;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
@@ -19,27 +24,30 @@
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"))
+ new Person(new Name("Joseph"), new Phone("89993233"), new Email("Joseph@nus.edu.sg"),
+ new GitUsername("ProfJosephNUS"), getTagSet("Colleague", "Lecturer")),
+ new Person(new Name("Example TA"), new Phone("92624417"), new Email("e111111@u.nus.edu"),
+ new GitUsername("TACS2103T"),
+ getTagSet("TA", "T-12")),
+ new Person(new Name("Brian Chow"), new Phone("87438807"), new Email("e123456@u.nus.edu"),
+ new GitUsername("brian16600"),
+ getTagSet("CS2101 Group 4", "CS2103T Lab 12")),
+ new Person(new Name("Sean Ng"), new Phone("99272758"), new Email("e234567@u.nus.edu"),
+ new GitUsername("snss231"),
+ getTagSet("Teammate", "CS2101 Group 4")),
+ new Person(new Name("Adrian Ong"), new Phone("93210283"), new Email("e345678@u.nus.edu"),
+ new GitUsername("adrianongjj"),
+ getTagSet("CS2103T Lab 12")),
+ new Person(new Name("Ong Jun Jie"), new Phone("91031282"), new Email("e456789@u.nus.edu"),
+ new GitUsername("junjunjieong"),
+ getTagSet("CS2107", "CS2040")),
+ new Person(new Name("Jun Rong"), new Phone("92492021"), new Email("e567890@u.nus.edu"),
+ new GitUsername("junrong98"),
+ getTagSet("CS2030", "CS2107"))
};
}
+
public static ReadOnlyAddressBook getSampleAddressBook() {
AddressBook sampleAb = new AddressBook();
for (Person samplePerson : getSamplePersons()) {
@@ -57,4 +65,39 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ public static Task[] getSampleTasks() {
+ return new Task[] {
+ new Task("Meeting with TAs", LocalDateTime.now().minusDays(2),
+ getTagSet("Discuss tutorials"), new Link(), true),
+ new Task("Consultation with students", LocalDateTime.now().plusDays(1),
+ getTagSet("Consultation"),
+ new Link("https://nus-sg.zoom.us/j/92307270969?pwd=VVMvNWFPWFpyVHRIcXR0VkJlNkg0dz09"),
+ false),
+ new Task("CS2103T Lecture",
+ LocalDateTime.of(2050, 4, 8, 14, 0),
+ getTagSet("Week 12 Lecture"),
+ new Link("https://nus-sg.zoom.us/j/92307270969?pwd=VVMvNWFPWFpyVHRIcXR0VkJlNkg0dz09"),
+ false),
+ new Task("CS2103T Lecture",
+ LocalDateTime.of(2050, 4, 15, 14, 0),
+ getTagSet("Week 13 Lecture"),
+ new Link("https://nus-sg.zoom.us/j/92307270969?pwd=VVMvNWFPWFpyVHRIcXR0VkJlNkg0dz09"), false),
+ new Task("Meeting with exam invigilators", LocalDateTime.now().plusWeeks(2) ,
+ getTagSet("Meeting"), new Link(), false),
+ new Task("CS2103T Lecture",
+ LocalDateTime.of(2050, 4, 22, 14, 0),
+ getTagSet("Week 14 Lecture"),
+ new Link("https://nus-sg.zoom.us/j/92307270969?pwd=VVMvNWFPWFpyVHRIcXR0VkJlNkg0dz09"),
+ false)
+ };
+ }
+
+ public static ReadOnlyTaskList getSampleTaskList() {
+ TaskList sampleTl = new TaskList();
+ for (Task sampleTask : getSampleTasks()) {
+ sampleTl.addTask(sampleTask);
+ }
+ return sampleTl;
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index a6321cec2ea..1caff641cd5 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,8 +10,8 @@
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.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -27,7 +27,7 @@ class JsonAdaptedPerson {
private final String name;
private final String phone;
private final String email;
- private final String address;
+ private final String gitUsername;
private final List tagged = new ArrayList<>();
/**
@@ -35,12 +35,12 @@ class JsonAdaptedPerson {
*/
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("email") String email, @JsonProperty("gitUsername") String gitUsername,
@JsonProperty("tagged") List tagged) {
this.name = name;
this.phone = phone;
this.email = email;
- this.address = address;
+ this.gitUsername = gitUsername;
if (tagged != null) {
this.tagged.addAll(tagged);
}
@@ -53,7 +53,7 @@ public JsonAdaptedPerson(Person source) {
name = source.getName().fullName;
phone = source.getPhone().value;
email = source.getEmail().value;
- address = source.getAddress().value;
+ gitUsername = source.getUsername().getUsername();
tagged.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
@@ -94,16 +94,17 @@ public Person toModelType() throws IllegalValueException {
}
final Email modelEmail = new Email(email);
- if (address == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
+ if (gitUsername == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ GitUsername.class.getSimpleName()));
}
- if (!Address.isValidAddress(address)) {
- throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
+ if (!GitUsername.isValidId(gitUsername)) {
+ throw new IllegalValueException(GitUsername.MESSAGE_CONSTRAINTS);
}
- final Address modelAddress = new Address(address);
+ final GitUsername modelGitUsername = new GitUsername(gitUsername);
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ return new Person(modelName, modelPhone, modelEmail, modelGitUsername, modelTags);
}
}
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..01d77c88361
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java
@@ -0,0 +1,120 @@
+package seedu.address.storage;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+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.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.Task;
+
+/**
+ * Jackson-friendly version of {@link Task}.
+ */
+public class JsonAdaptedTask {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!";
+
+ private final String name;
+ private final String dateTime;
+ private final String endDateTime;
+ private final List tagged = new ArrayList<>();
+ private final List people = new ArrayList<>();
+ private final String link;
+ private final String isTaskMarkDone;
+
+ /**
+ * Constructs a {@code JsonAdaptedTask} with the given task details.
+ */
+ @JsonCreator
+ public JsonAdaptedTask(@JsonProperty("name") String name,
+ @JsonProperty("dateTime") String dateTime,
+ @JsonProperty("endDateTime") String endDateTime,
+ @JsonProperty("people") List people,
+ @JsonProperty("tagged") List tagged,
+ @JsonProperty("link") String link,
+ @JsonProperty("isTaskMarkDone") String isTaskMarkDone) {
+ this.name = name;
+ this.dateTime = dateTime;
+ this.endDateTime = endDateTime;
+ this.people.addAll(people);
+ this.link = link;
+
+ if (tagged != null) {
+ this.tagged.addAll(tagged);
+ }
+ this.isTaskMarkDone = isTaskMarkDone;
+ }
+
+ /**
+ * Converts a given {@code Task} into this class for Jackson use.
+ */
+ public JsonAdaptedTask(Task source) {
+ name = source.getName();
+ dateTime = String.valueOf(source.getDateTime());
+ endDateTime = String.valueOf(source.getEndDateTime());
+ link = source.getLink().toString();
+ people.addAll(source.getPeople().stream()
+ .map(JsonAdaptedPerson::new)
+ .collect(Collectors.toList()));
+ tagged.addAll(source.getTags().stream()
+ .map(JsonAdaptedTag::new)
+ .collect(Collectors.toList()));
+ isTaskMarkDone = String.valueOf(source.isTaskMark());
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted task object into the model's {@code Task} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted task.
+ */
+ public Task toModelType() throws IllegalValueException {
+ final List taskTags = new ArrayList<>();
+ for (JsonAdaptedTag tag : tagged) {
+ taskTags.add(tag.toModelType());
+ }
+
+ List modelPeople = new ArrayList<>();
+ for (JsonAdaptedPerson person : people) {
+ modelPeople.add(person.toModelType());
+ }
+
+
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Name"));
+ }
+
+ if (dateTime == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "dateTime"));
+ }
+ LocalDateTime modelDateTime = LocalDateTime.parse(dateTime);
+
+ LocalDateTime modelEndDateTime = Objects.equals(endDateTime, "null")
+ ? null : LocalDateTime.parse(endDateTime);
+
+ Set modelTag = new HashSet<>(taskTags);
+
+ Link modelLink = Objects.equals(link, null) ? new Link() : new Link(link);
+ if (!modelLink.isEmpty()) {
+ if (!Link.isValidLink(modelLink.toString())) {
+ throw new IllegalValueException(Link.MESSAGE_CONSTRAINTS);
+ }
+ }
+ if (isTaskMarkDone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "isTaskMarkDone"));
+ }
+
+ boolean modelIsTaskMarkDone = Boolean.parseBoolean(isTaskMarkDone);
+
+ return new Task(name, modelDateTime, modelEndDateTime, modelPeople, modelTag, modelLink, modelIsTaskMarkDone);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
index dfab9daaa0d..293d52b80af 100644
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
@@ -36,6 +36,7 @@ public Optional readAddressBook() throws DataConversionExce
return readAddressBook(filePath);
}
+
/**
* Similar to {@link #readAddressBook()}.
*
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..cc814e32b92 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -20,6 +20,9 @@
class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_PHONE = "Persons list contains duplicate phone number(s).";
+ public static final String MESSAGE_DUPLICATE_GIT_USERNAME = "Persons list contains duplicate git username(s).";
+ public static final String MESSAGE_DUPLICATE_EMAIL = "Persons list contains duplicate git E-mail(s).";
private final List persons = new ArrayList<>();
@@ -52,6 +55,19 @@ public AddressBook toModelType() throws IllegalValueException {
if (addressBook.hasPerson(person)) {
throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
}
+
+ if (addressBook.hasPhone(person.getPhone())) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_PHONE);
+ }
+
+ if (addressBook.hasGitUsername(person.getUsername())) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_GIT_USERNAME);
+ }
+
+ if (addressBook.hasEmail(person.getEmail())) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_EMAIL);
+ }
+
addressBook.addPerson(person);
}
return addressBook;
diff --git a/src/main/java/seedu/address/storage/JsonSerializableTaskList.java b/src/main/java/seedu/address/storage/JsonSerializableTaskList.java
new file mode 100644
index 00000000000..6ada0c12c87
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonSerializableTaskList.java
@@ -0,0 +1,54 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.ReadOnlyTaskList;
+import seedu.address.model.TaskList;
+import seedu.address.model.task.Task;
+
+/**
+ * An Immutable TaskList that is serializable to JSON format.
+ */
+@JsonRootName(value = "tasks")
+public class JsonSerializableTaskList {
+
+ private final List tasks = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonSerializableTasks} with the given tasks.
+ */
+ @JsonCreator
+ public JsonSerializableTaskList(@JsonProperty("tasks") List tasks) {
+ this.tasks.addAll(tasks);
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyTasks} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created {@code JsonSerializableTasks}.
+ */
+ public JsonSerializableTaskList(ReadOnlyTaskList source) {
+ tasks.addAll(source.getTaskList().stream().map(JsonAdaptedTask::new).collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this address book into the model's {@code Tasks} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public TaskList toModelType() throws IllegalValueException {
+ TaskList taskList = new TaskList();
+ for (JsonAdaptedTask jsonAdaptedTask : tasks) {
+ Task task = jsonAdaptedTask.toModelType();
+ taskList.addTask(task);
+ }
+ return taskList;
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonTaskListStorage.java b/src/main/java/seedu/address/storage/JsonTaskListStorage.java
new file mode 100644
index 00000000000..93e47fd6542
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonTaskListStorage.java
@@ -0,0 +1,104 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.model.ReadOnlyTaskList;
+import seedu.address.model.TaskList;
+
+
+/**
+ * JSON Storage for Tasks.
+ */
+public class JsonTaskListStorage implements TaskListStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonTaskListStorage.class);
+
+ /* FilePath */
+ private Path filePath;
+
+ /**
+ * Constructor for JsonTasksStorage.
+ *
+ * @param filePath FilePath destination.
+ */
+ public JsonTaskListStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ /**
+ * Returns FilePath.
+ *
+ * @return FilePath of Tasks.
+ */
+ public Path getTaskListFilePath() {
+ return filePath;
+ }
+
+ /**
+ * Reads Tasks
+ *
+ * @return Optional Object containing Tasks.
+ * @throws DataConversionException if data is in incorrect format in filepath.
+ */
+ public Optional readTaskList() throws DataConversionException {
+ return readTaskList(filePath);
+ }
+
+ /**
+ * Reads Tasks from JSON.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataConversionException if the file format is not as expected.
+ */
+ public Optional readTaskList(Path filePath) throws DataConversionException {
+ requireNonNull(filePath);
+
+ Optional jsonTaskList = JsonUtil.readJsonFile(
+ filePath, JsonSerializableTaskList.class);
+
+ if (!jsonTaskList.isPresent()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonTaskList.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataConversionException(ive);
+ }
+ }
+
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList) throws IOException {
+ saveTaskList(taskList, filePath);
+ }
+
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException {
+ requireNonNull(taskList);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableTaskList(taskList), filePath);
+ }
+
+ /**
+ * Saves Tasks into JSON.
+ *
+ * @param taskList Tasks object containing tasklist to be saved
+ * @throws IOException
+ */
+ public void saveTasks(TaskList taskList) throws IOException {
+ JsonUtil.saveJsonFile(taskList, filePath);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index beda8bd9f11..38b85397315 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -6,13 +6,14 @@
import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends AddressBookStorage, UserPrefsStorage, TaskListStorage {
@Override
Optional readUserPrefs() throws DataConversionException, IOException;
@@ -26,7 +27,16 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage {
@Override
Optional readAddressBook() throws DataConversionException, IOException;
+ Path getTaskListFilePath();
+
+ Optional readTaskList() throws DataConversionException, IOException;
+
+ Optional readTaskList(Path filePath) throws DataConversionException, IOException;
+
@Override
void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
+ void saveTaskList(ReadOnlyTaskList taskList) throws IOException;
+
+ void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException;
}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 6cfa0162164..1c2a4f69c94 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -8,6 +8,7 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
@@ -17,15 +18,18 @@
public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
+ private TaskListStorage taskListStorage;
private AddressBookStorage addressBookStorage;
private UserPrefsStorage userPrefsStorage;
/**
* Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
+ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage,
+ TaskListStorage taskListStorage) {
this.addressBookStorage = addressBookStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.taskListStorage = taskListStorage;
}
// ================ UserPrefs methods ==============================
@@ -75,4 +79,32 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
addressBookStorage.saveAddressBook(addressBook, filePath);
}
+ // ================ TaskList methods ==============================
+
+ @Override
+ public Path getTaskListFilePath() {
+ return taskListStorage.getTaskListFilePath();
+ }
+
+ @Override
+ public Optional readTaskList() throws DataConversionException, IOException {
+ return readTaskList(taskListStorage.getTaskListFilePath());
+ }
+
+ @Override
+ public Optional readTaskList(Path filePath) throws DataConversionException, IOException {
+ logger.fine("Attempting to read data from file: " + filePath);
+ return taskListStorage.readTaskList(filePath);
+ }
+
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList) throws IOException {
+ saveTaskList(taskList, taskListStorage.getTaskListFilePath());
+ }
+
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException {
+ logger.fine("Attempting to write to data file: " + filePath);
+ taskListStorage.saveTaskList(taskList, filePath);
+ }
}
diff --git a/src/main/java/seedu/address/storage/TaskListStorage.java b/src/main/java/seedu/address/storage/TaskListStorage.java
new file mode 100644
index 00000000000..d7d393a3e87
--- /dev/null
+++ b/src/main/java/seedu/address/storage/TaskListStorage.java
@@ -0,0 +1,44 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
+
+
+/**
+ * Represents a storage for {@link seedu.address.model.TaskList}.
+ */
+public interface TaskListStorage {
+
+ Path getTaskListFilePath();
+
+ /**
+ * Returns AddressBook data as a {@link ReadOnlyAddressBook}.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ * @throws DataConversionException if the data in storage is not in the expected format.
+ * @throws IOException if there was any problem when reading from the storage.
+ */
+ Optional readTaskList() throws DataConversionException, IOException;
+
+ /**
+ * @see #getTaskListFilePath()
+ */
+ Optional readTaskList(Path filePath) throws DataConversionException, IOException;
+
+ /**
+ * Saves the given {@link ReadOnlyTaskList} to the storage.
+ * @param taskList cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveTaskList(ReadOnlyTaskList taskList) throws IOException;
+
+ /**
+ * @see #saveTaskList(ReadOnlyTaskList)
+ */
+ void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException;
+
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 9a665915949..77dd984f04c 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -1,9 +1,16 @@
package seedu.address.ui;
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.logging.Logger;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
+import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
@@ -15,17 +22,18 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
- public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
+ public static final String USERGUIDE_URL = "https://ay2122s2-cs2103t-t12-4.github.io/tp/UserGuide.html";
+ public static final String HELP_MESSAGE = "Refer to the user guide: ";
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
private static final String FXML = "HelpWindow.fxml";
@FXML
private Button copyButton;
-
@FXML
private Label helpMessage;
+ @FXML
+ private Hyperlink helpLink;
/**
* Creates a new HelpWindow.
@@ -35,6 +43,17 @@ public class HelpWindow extends UiPart {
public HelpWindow(Stage root) {
super(FXML, root);
helpMessage.setText(HELP_MESSAGE);
+ helpLink.setText(USERGUIDE_URL);
+ helpLink.setOnAction(new EventHandler() {
+ @Override
+ public void handle(ActionEvent event) {
+ try {
+ Desktop.getDesktop().browse(new URI(helpLink.getText()));
+ } catch (URISyntaxException | IOException e) {
+ helpLink.getStyleClass().add("cell_small_hyperlink_invalid");
+ }
+ }
+ });
}
/**
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..dff7cfeb9a8 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -8,6 +8,7 @@
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import seedu.address.commons.core.GuiSettings;
@@ -32,9 +33,11 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private TaskListPanel taskListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+
@FXML
private StackPane commandBoxPlaceholder;
@@ -44,12 +47,18 @@ public class MainWindow extends UiPart {
@FXML
private StackPane personListPanelPlaceholder;
+ @FXML
+ private StackPane taskListPanelPlaceholder;
+
@FXML
private StackPane resultDisplayPlaceholder;
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private HBox lists;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -113,10 +122,13 @@ void fillInnerParts() {
personListPanel = new PersonListPanel(logic.getFilteredPersonList());
personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ taskListPanel = new TaskListPanel(logic.getFilteredTaskList());
+ taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot());
+
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
- StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
+ StatusBarFooter statusBarFooter = new StatusBarFooter();
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
CommandBox commandBox = new CommandBox(this::executeCommand);
@@ -163,6 +175,11 @@ private void handleExit() {
primaryStage.hide();
}
+ private void handleEmails(String emails) {
+ resultDisplay.setEmails(emails);
+ resultDisplay.showCopy();
+ }
+
public PersonListPanel getPersonListPanel() {
return personListPanel;
}
@@ -178,6 +195,12 @@ private CommandResult executeCommand(String commandText) throws CommandException
logger.info("Result: " + commandResult.getFeedbackToUser());
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
+ if (commandResult.isGenerateEmails()) {
+ handleEmails(commandResult.getEmails());
+ } else {
+ resultDisplay.hideCopy();
+ }
+
if (commandResult.isShowHelp()) {
handleHelp();
}
@@ -190,6 +213,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
} catch (CommandException | ParseException e) {
logger.info("Invalid command: " + commandText);
resultDisplay.setFeedbackToUser(e.getMessage());
+ resultDisplay.hideCopy();
throw e;
}
}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 7fc927bc5d9..84f37575b6c 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -35,7 +35,7 @@ public class PersonCard extends UiPart {
@FXML
private Label phone;
@FXML
- private Label address;
+ private Label gitUsername;
@FXML
private Label email;
@FXML
@@ -47,11 +47,15 @@ public class PersonCard extends UiPart {
public PersonCard(Person person, int displayedIndex) {
super(FXML);
this.person = person;
+
+ gitUsername.setWrapText(true);
+ email.setWrapText(true);
+
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
- phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
- email.setText(person.getEmail().value);
+ phone.setText("Num: " + person.getPhone().value);
+ gitUsername.setText("Github: " + person.getUsername().getUsername());
+ email.setText("Email: " + person.getEmail().value);
person.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
.forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java
index 7d98e84eedf..bba5b9a219d 100644
--- a/src/main/java/seedu/address/ui/ResultDisplay.java
+++ b/src/main/java/seedu/address/ui/ResultDisplay.java
@@ -3,7 +3,10 @@
import static java.util.Objects.requireNonNull;
import javafx.fxml.FXML;
+import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.Region;
/**
@@ -13,11 +16,28 @@ public class ResultDisplay extends UiPart {
private static final String FXML = "ResultDisplay.fxml";
+ private static String emails;
+
@FXML
private TextArea resultDisplay;
+ @FXML
+ private Button copyButton;
+
+ /**
+ * Creates the region where feedback will be displayed.
+ */
public ResultDisplay() {
super(FXML);
+ this.copyButton.setVisible(false);
+ }
+
+ @FXML
+ private void copyEmails() {
+ final Clipboard clipboard = Clipboard.getSystemClipboard();
+ final ClipboardContent content = new ClipboardContent();
+ content.putString(emails);
+ clipboard.setContent(content);
}
public void setFeedbackToUser(String feedbackToUser) {
@@ -25,4 +45,15 @@ public void setFeedbackToUser(String feedbackToUser) {
resultDisplay.setText(feedbackToUser);
}
+ public void setEmails(String emails) {
+ this.emails = emails;
+ }
+
+ public void showCopy() {
+ this.copyButton.setVisible(true);
+ }
+
+ public void hideCopy() {
+ this.copyButton.setVisible(false);
+ }
}
diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java
index b577f829423..b8e7a85d669 100644
--- a/src/main/java/seedu/address/ui/StatusBarFooter.java
+++ b/src/main/java/seedu/address/ui/StatusBarFooter.java
@@ -1,8 +1,5 @@
package seedu.address.ui;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
@@ -18,11 +15,11 @@ public class StatusBarFooter extends UiPart {
private Label saveLocationStatus;
/**
- * Creates a {@code StatusBarFooter} with the given {@code Path}.
+ * Creates a {@code StatusBarFooter}.
*/
- public StatusBarFooter(Path saveLocation) {
+ public StatusBarFooter() {
super(FXML);
- saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString());
+ saveLocationStatus.setText("");
}
}
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..95a545621b1
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TaskCard.java
@@ -0,0 +1,144 @@
+package seedu.address.ui;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.LocalDateTime;
+import java.util.Comparator;
+
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.fxml.FXML;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.task.Task;
+
+/**
+ * An UI component that displays information of a {@code Task}.
+ */
+public class TaskCard extends UiPart {
+
+ private static final String FXML = "TaskListCard.fxml";
+
+ // Credit: Image icon taken from https://icons8.com.
+ private static final String TICK_ICON = "/images/tick.png";
+ private static final String UNTICK_ICON = "/images/untick.png";
+
+ /**
+ * 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 Task task;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label id;
+ @FXML
+ private Label name;
+ @FXML
+ private Label date;
+ @FXML
+ private FlowPane tags;
+ @FXML
+ private Hyperlink link;
+ @FXML
+ private ImageView markImage;
+ @FXML
+ private Label linkLabel;
+
+ /**
+ * Creates a {@code TaskCode} with the given {@code Task} and index to display.
+ */
+ public TaskCard(Task task, int displayedIndex) {
+ super(FXML);
+ this.task = task;
+ id.setText(displayedIndex + ". ");
+
+ name.setText(task.getName());
+ setTaskColor(task.getDateTime());
+
+ date.setText(task.getDateTimeString());
+
+ task.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ setLink();
+ setMarkedImage(task);
+ }
+
+ public void setTaskColor(LocalDateTime taskDateTime) {
+ LocalDateTime todayDate = LocalDateTime.now();
+ if (taskDateTime.isBefore(todayDate)) {
+ id.getStyleClass().add("cell_big_label_late");
+ name.getStyleClass().add("cell_big_label_late");
+ date.getStyleClass().add("cell_small_label_late");
+ linkLabel.getStyleClass().add("cell_small_label_late");
+ } else if (taskDateTime.isBefore(todayDate.plusDays(3))) {
+ id.getStyleClass().add("cell_big_label_soon");
+ name.getStyleClass().add("cell_big_label_soon");
+ date.getStyleClass().add("cell_small_label_soon");
+ linkLabel.getStyleClass().add("cell_small_label_soon");
+ } else {
+ id.getStyleClass().add("cell_big_label");
+ name.getStyleClass().add("cell_big_label");
+ date.getStyleClass().add("cell_small_label");
+ linkLabel.getStyleClass().add("cell_small_label");
+ }
+ }
+
+ public void setMarkedImage(Task task) {
+ if (task.isTaskMark()) {
+ markImage.setImage(new Image(TICK_ICON));
+ } else {
+ markImage.setImage(new Image(UNTICK_ICON));
+ }
+
+ }
+
+ public void setLink() {
+ if (!(task.getLink().isEmpty()) && !(task.getLink().toString().isEmpty())) {
+ linkLabel.setText("Link:");
+ link.setText(task.getLink().toString());
+ link.setOnAction(new EventHandler() {
+ @Override
+ public void handle(ActionEvent event) {
+ try {
+ Desktop.getDesktop().browse(new URI(link.getText()));
+ } catch (URISyntaxException | IOException e) {
+ link.getStyleClass().add("cell_small_hyperlink_invalid");
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TaskCard)) {
+ return false;
+ }
+
+ // state check
+ TaskCard card = (TaskCard) other;
+ return id.getText().equals(card.id.getText())
+ && task.equals(card.task);
+ }
+}
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..8cc91e33d90
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TaskListPanel.java
@@ -0,0 +1,49 @@
+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.task.Task;
+
+/**
+ * Panel containing the list of tasks.
+ */
+public class TaskListPanel extends UiPart {
+ private static final String FXML = "TaskListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(TaskListPanel.class);
+
+ @FXML
+ private ListView taskListView;
+
+ /**
+ * Creates a {@code TaskListPanel} with the given {@code ObservableList}.
+ */
+ public TaskListPanel(ObservableList taskList) {
+ super(FXML);
+ taskListView.setItems(taskList);
+ taskListView.setCellFactory(listView -> new TaskListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Task} using a {@code TaskCard}.
+ */
+ 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());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..bd21bd8b853 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/nuslogo.jpg";
private Logic logic;
private MainWindow mainWindow;
@@ -48,6 +48,7 @@ public void start(Stage primaryStage) {
logger.severe(StringUtil.getDetails(e));
showFatalErrorDialogAndShutdown("Fatal error during initializing", e);
}
+
}
private Image getImage(String imagePath) {
diff --git a/src/main/java/seedu/address/ui/exceptions/UiException.java b/src/main/java/seedu/address/ui/exceptions/UiException.java
new file mode 100644
index 00000000000..7630abc8b24
--- /dev/null
+++ b/src/main/java/seedu/address/ui/exceptions/UiException.java
@@ -0,0 +1,14 @@
+package seedu.address.ui.exceptions;
+
+public class UiException extends Exception {
+ public UiException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code UiException} with the specified detail {@code message} and {@code cause}.
+ */
+ public UiException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png
deleted file mode 100644
index 29810cf1fd9..00000000000
Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ
diff --git a/src/main/resources/images/nuslogo.jpg b/src/main/resources/images/nuslogo.jpg
new file mode 100644
index 00000000000..e486a3b899b
Binary files /dev/null and b/src/main/resources/images/nuslogo.jpg differ
diff --git a/src/main/resources/images/tick.png b/src/main/resources/images/tick.png
new file mode 100644
index 00000000000..2902d24a880
Binary files /dev/null and b/src/main/resources/images/tick.png differ
diff --git a/src/main/resources/images/untick.png b/src/main/resources/images/untick.png
new file mode 100644
index 00000000000..6ded20116ec
Binary files /dev/null and b/src/main/resources/images/untick.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 09f6d6fe9e4..60b069a1af0 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -4,6 +4,6 @@
-
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..b7fa5ce3b72 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -1,3 +1,4 @@
+
.background {
-fx-background-color: derive(#1d1d1d, 20%);
background-color: #383838; /* Used in the default.html file */
@@ -5,28 +6,28 @@
.label {
-fx-font-size: 11pt;
- -fx-font-family: "Segoe UI Semibold";
- -fx-text-fill: #555555;
+ -fx-font-family: "Roboto";
+ -fx-text-fill: white;
-fx-opacity: 0.9;
}
.label-bright {
-fx-font-size: 11pt;
- -fx-font-family: "Segoe UI Semibold";
+ -fx-font-family: "Roboto";
-fx-text-fill: white;
-fx-opacity: 1;
}
.label-header {
-fx-font-size: 32pt;
- -fx-font-family: "Segoe UI Light";
+ -fx-font-family: "Roboto";
-fx-text-fill: white;
-fx-opacity: 1;
}
.text-field {
-fx-font-size: 12pt;
- -fx-font-family: "Segoe UI Semibold";
+ -fx-font-family: "Roboto";
}
.tab-pane {
@@ -66,7 +67,7 @@
.table-view .column-header .label {
-fx-font-size: 20pt;
- -fx-font-family: "Segoe UI Light";
+ -fx-font-family: "Roboto";
-fx-text-fill: white;
-fx-alignment: center-left;
-fx-opacity: 1;
@@ -79,12 +80,14 @@
.split-pane:horizontal .split-pane-divider {
-fx-background-color: derive(#1d1d1d, 20%);
-fx-border-color: transparent transparent transparent #4d4d4d;
+ -fx-box-border: transparent;
}
.split-pane {
-fx-border-radius: 1;
-fx-border-width: 1;
-fx-background-color: derive(#1d1d1d, 20%);
+ -fx-box-border: transparent;
}
.list-view {
@@ -116,40 +119,83 @@
-fx-border-width: 1;
}
-.list-cell .label {
+.cell_big_label {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 16px;
-fx-text-fill: white;
}
-.cell_big_label {
- -fx-font-family: "Segoe UI Semibold";
+.cell_big_label_late {
+ -fx-font-family: "Roboto";
-fx-font-size: 16px;
- -fx-text-fill: #010504;
+ -fx-text-fill: #BC544B;
+}
+
+.cell_big_label_soon {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 16px;
+ -fx-text-fill: #ccad10;
}
.cell_small_label {
- -fx-font-family: "Segoe UI";
- -fx-font-size: 13px;
- -fx-text-fill: #010504;
+ -fx-font-family: "Roboto";
+ -fx-font-size: 12px;
+ -fx-text-fill: white;
+}
+
+.cell_small_label_late {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 12px;
+ -fx-text-fill: #BC544B;
+}
+
+.cell_small_label_soon {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 12px;
+ -fx-text-fill: #ccad10;
+}
+
+.cell_small_hyperlink {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 12px;
+ -fx-text-fill: #83CDE6;
+}
+
+.cell_small_hyperlink_invalid {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 12px;
+ -fx-text-fill: #FFCCCC;
}
.stack-pane {
-fx-background-color: derive(#1d1d1d, 20%);
}
+.pane-with-only-top-border {
+ -fx-border-style: solid none none none;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.pane-with-no-border {
+ -fx-border-style: none;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
.pane-with-border {
-fx-background-color: derive(#1d1d1d, 20%);
-fx-border-color: derive(#1d1d1d, 10%);
- -fx-border-top-width: 1px;
}
.status-bar {
+ -fx-font-family: "Roboto";
+ -fx-font-size: 5pt;
-fx-background-color: derive(#1d1d1d, 30%);
}
.result-display {
-fx-background-color: transparent;
- -fx-font-family: "Segoe UI Light";
- -fx-font-size: 13pt;
+ -fx-font-family: "Roboto";
+ -fx-font-size: 10pt;
-fx-text-fill: white;
}
@@ -158,9 +204,9 @@
}
.status-bar .label {
- -fx-font-family: "Segoe UI Light";
+ -fx-font-family: "Roboto";
-fx-text-fill: white;
- -fx-padding: 4px;
+ -fx-padding: 10px;
-fx-pref-height: 30px;
}
@@ -197,8 +243,8 @@
}
.menu-bar .label {
- -fx-font-size: 14pt;
- -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 12pt;
+ -fx-font-family: "Roboto";
-fx-text-fill: white;
-fx-opacity: 0.9;
}
@@ -218,7 +264,7 @@
-fx-border-width: 2;
-fx-background-radius: 0;
-fx-background-color: #1d1d1d;
- -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
+ -fx-font-family: "Roboto", Helvetica, Arial, sans-serif;
-fx-font-size: 11pt;
-fx-text-fill: #d8d8d8;
-fx-background-insets: 0 0 0 0, 0, 1, 2;
@@ -307,6 +353,14 @@
-fx-padding: 8 1 8 1;
}
+.list-view {
+ -fx-background-radius: 20;
+}
+
+.list-cell, .list-cell::focused {
+ -fx-border-radius: 20;
+}
+
#cardPane {
-fx-background-color: transparent;
-fx-border-width: 0;
@@ -319,15 +373,23 @@
#commandTextField {
-fx-background-color: transparent #383838 transparent #383838;
+ -fx-border-color: #383838 #383838 #D3D3D3 #383838;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
-fx-border-insets: 0;
-fx-border-width: 1;
- -fx-font-family: "Segoe UI Light";
- -fx-font-size: 13pt;
+ -fx-font-family: "Roboto";
+ -fx-font-size: 11pt;
-fx-text-fill: white;
}
+.custom-text-field:hover {
+ -fx-border-color: #383838 #383838 #ffffff #383838;
+}
+
+.custom-text-field:focus {
+ -fx-border-color: #383838 #383838 #ffffff #383838;
+}
+
#filterField, #personListPanel, #personWebpage {
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css
index 17e8a8722cd..396b250abea 100644
--- a/src/main/resources/view/HelpWindow.css
+++ b/src/main/resources/view/HelpWindow.css
@@ -2,18 +2,11 @@
-fx-text-fill: white;
}
-#copyButton {
- -fx-background-color: dimgray;
-}
-
-#copyButton:hover {
- -fx-background-color: gray;
-}
-
-#copyButton:armed {
- -fx-background-color: darkgray;
-}
-
#helpMessageContainer {
-fx-background-color: derive(#1d1d1d, 20%);
}
+
+.hyperlink {
+ -fx-border-color: transparent;
+ -fx-text-fill: #83CDE6;
+}
diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml
index 5dea0adef70..3a2f98a52b0 100644
--- a/src/main/resources/view/HelpWindow.fxml
+++ b/src/main/resources/view/HelpWindow.fxml
@@ -9,6 +9,7 @@
+
@@ -21,20 +22,13 @@
-
-
+
+
+
+
+
+
-
-
-
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index a431648f6c0..bd88d53f8e9 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -3,18 +3,19 @@
+
-
+
+
-
+
-
+
@@ -23,7 +24,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f08ea32ad55..1813a266874 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -27,10 +27,10 @@
-
-
+
+
diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml
index 58d5ad3dc56..862db8f19f5 100644
--- a/src/main/resources/view/ResultDisplay.fxml
+++ b/src/main/resources/view/ResultDisplay.fxml
@@ -1,9 +1,11 @@
-
+
+
-
-
-
+
+
+
+
diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml
new file mode 100644
index 00000000000..b59cf9799f1
--- /dev/null
+++ b/src/main/resources/view/TaskListCard.fxml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml
new file mode 100644
index 00000000000..a58887c89d1
--- /dev/null
+++ b/src/main/resources/view/TaskListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/test/data/ImportTestData/data.csv b/src/test/data/ImportTestData/data.csv
new file mode 100644
index 00000000000..ca9e8435da2
--- /dev/null
+++ b/src/test/data/ImportTestData/data.csv
@@ -0,0 +1,6 @@
+Name,Phone,Email,Github,Tags
+Alex Tan,97013404,e0315898@u.nus.edu,alexcoder,Student/Lab 14C
+Brad Tay,97553402,e03158448@u.nus.edu,braddytay,TA/Lab 12F/
+Charlie Yeo,97012344,e0323898@u.nus.edu,charlcodes,Graduate Tutor/Lab 14F
+Tag too long guy,97142342,e3240921@u.nus.edu,mythingtoolong,Shamahamunawefoiajowefijawefiojfawefoijwafiwjefaoawefawefaweweifjeawefawefafwfawfwaefaewf/af
+Missing Field Guy,,e3240921@u.nus.edu,missingdude,Student
diff --git a/src/test/data/ImportTestData/empty.csv b/src/test/data/ImportTestData/empty.csv
new file mode 100644
index 00000000000..a0b5f555cea
--- /dev/null
+++ b/src/test/data/ImportTestData/empty.csv
@@ -0,0 +1 @@
+Name,Phone,Email,Github,Tags
diff --git a/src/test/data/ImportTestData/extraneousHeaders.csv b/src/test/data/ImportTestData/extraneousHeaders.csv
new file mode 100644
index 00000000000..2504b388f77
--- /dev/null
+++ b/src/test/data/ImportTestData/extraneousHeaders.csv
@@ -0,0 +1 @@
+Name,Phone,Email,Github,Tags,Name,Weight
diff --git a/src/test/data/ImportTestData/importTest.csv b/src/test/data/ImportTestData/importTest.csv
new file mode 100644
index 00000000000..7040b462a64
--- /dev/null
+++ b/src/test/data/ImportTestData/importTest.csv
@@ -0,0 +1,3 @@
+Name,Phone,Email,Github,Tags
+Alex Tan,97013404,e0315898@u.nus.edu,alexcoder,Student/Lab 14C
+Brad Tay,97553402,e03158448@u.nus.edu,braddytay,TA/Lab 12F
diff --git a/src/test/data/ImportTestData/invalidFields.csv b/src/test/data/ImportTestData/invalidFields.csv
new file mode 100644
index 00000000000..290d0f6c118
--- /dev/null
+++ b/src/test/data/ImportTestData/invalidFields.csv
@@ -0,0 +1,6 @@
+Name,Phone,Email,Github,Tags
+2,,,,
+Marcus,a,,,
+Alekhine,9328512,k,,
+Bridget,9325151,e0543670@u.nus.edu,_,
+Charlie,3291580,e0543333@u.nus.edu,alexcoder,日本人
diff --git a/src/test/data/ImportTestData/missingHeaders.csv b/src/test/data/ImportTestData/missingHeaders.csv
new file mode 100644
index 00000000000..970d0db92ce
--- /dev/null
+++ b/src/test/data/ImportTestData/missingHeaders.csv
@@ -0,0 +1 @@
+Phone,Email,Github,Tags
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
index 6a4d2b7181c..ff670b137db 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
@@ -3,11 +3,11 @@
"name": "Valid Person",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "username": "validuser"
}, {
"name": "Person With Invalid Phone Field",
"phone": "948asdf2424",
"email": "hans@example.com",
- "address": "4th street"
+ "address": "invalidphoneuser"
} ]
}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
index ccd21f7d1a9..04e556aeb28 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
@@ -3,6 +3,6 @@
"name": "Person with invalid name field: Ha!ns Mu@ster",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "username": "hanss"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateEmailAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateEmailAddressBook.json
new file mode 100644
index 00000000000..782443ad07f
--- /dev/null
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicateEmailAddressBook.json
@@ -0,0 +1,15 @@
+{
+ "persons": [ {
+ "name": "Alice Pauline",
+ "phone": "94351253",
+ "email": "alice@example.com",
+ "gitUsername": "Alice123",
+ "tagged": [ "friends" ]
+ }, {
+ "name": "Bob Moore",
+ "phone": "99995555",
+ "email": "alice@example.com",
+ "gitUsername": "Bob123",
+ "tagged": [ "nofriends" ]
+ } ]
+}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateGitUsernameAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateGitUsernameAddressBook.json
new file mode 100644
index 00000000000..737b1d9de89
--- /dev/null
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicateGitUsernameAddressBook.json
@@ -0,0 +1,15 @@
+{
+ "persons": [ {
+ "name": "Alice Pauline",
+ "phone": "94351253",
+ "email": "alice@example.com",
+ "gitUsername": "Alice123",
+ "tagged": [ "friends" ]
+ }, {
+ "name": "Bob Moore",
+ "phone": "99995555",
+ "email": "bob@example.com",
+ "gitUsername": "Alice123",
+ "tagged": [ "nofriends" ]
+ } ]
+}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index 48831cc7674..719e477c192 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -3,12 +3,13 @@
"name": "Alice Pauline",
"phone": "94351253",
"email": "alice@example.com",
- "address": "123, Jurong West Ave 6, #08-111",
+ "gitUsername": "Alice123",
"tagged": [ "friends" ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
- "email": "pauline@example.com",
- "address": "4th street"
+ "email": "alice@example.com",
+ "gitUsername": "Alice123",
+ "tagged": [ "friends" ]
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePhoneAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePhoneAddressBook.json
new file mode 100644
index 00000000000..fc06aa8dac7
--- /dev/null
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePhoneAddressBook.json
@@ -0,0 +1,15 @@
+{
+ "persons": [ {
+ "name": "Alice Pauline",
+ "phone": "94351253",
+ "email": "alice@example.com",
+ "gitUsername": "Alice123",
+ "tagged": [ "friends" ]
+ }, {
+ "name": "Bob Moore",
+ "phone": "94351253",
+ "email": "bob@example.com",
+ "gitUsername": "Bob123",
+ "tagged": [ "nofriends" ]
+ } ]
+}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..225e0862c1c 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,6 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
- "address": "4th street"
+ "username": "hanss"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index f10eddee12e..553f4cdd714 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -4,43 +4,43 @@
"name" : "Alice Pauline",
"phone" : "94351253",
"email" : "alice@example.com",
- "address" : "123, Jurong West Ave 6, #08-111",
+ "gitUsername" : "Alice123",
"tagged" : [ "friends" ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
- "address" : "311, Clementi Ave 2, #02-25",
+ "gitUsername" : "benson234",
"tagged" : [ "owesMoney", "friends" ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
- "address" : "wall street",
+ "gitUsername" : "carlusername",
"tagged" : [ ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
- "address" : "10th street",
+ "gitUsername" : "danielusername",
"tagged" : [ "friends" ]
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
- "address" : "michegan ave",
+ "gitUsername" : "elleusername",
"tagged" : [ ]
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
- "address" : "little tokyo",
+ "gitUsername" : "littletokyo",
"tagged" : [ ]
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
- "address" : "4th street",
+ "gitUsername" : "georgegithub",
"tagged" : [ ]
} ]
}
diff --git a/src/test/data/JsonSerializableTaskListTest/invalidTasksTaskList.json b/src/test/data/JsonSerializableTaskListTest/invalidTasksTaskList.json
new file mode 100644
index 00000000000..1f52e667633
--- /dev/null
+++ b/src/test/data/JsonSerializableTaskListTest/invalidTasksTaskList.json
@@ -0,0 +1,11 @@
+{
+ "tasks" : [ {
+ "name" : "Consultation",
+ "dateTime" : "2022-02-25T16:00",
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ ],
+ "link" : "a",
+ "isTaskMarkDone" : "false"
+ } ]
+}
diff --git a/src/test/data/JsonSerializableTaskListTest/typicalTasksTaskList.json b/src/test/data/JsonSerializableTaskListTest/typicalTasksTaskList.json
new file mode 100644
index 00000000000..efba7e003e7
--- /dev/null
+++ b/src/test/data/JsonSerializableTaskListTest/typicalTasksTaskList.json
@@ -0,0 +1,35 @@
+{
+ "tasks" : [ {
+ "name" : "Brush my teeth",
+ "dateTime" : "2050-12-15T21:00",
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ "Chores" ],
+ "link" : null,
+ "isTaskMarkDone" : "false"
+ }, {
+ "name" : "Do the laundry",
+ "dateTime" : "2050-12-15T21:00",
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ "Chores" ],
+ "link" : null,
+ "isTaskMarkDone" : "false"
+ }, {
+ "name" : "Consultation with students",
+ "dateTime" : "2050-08-03T14:00",
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ "Consult" ],
+ "link" : "https:google.com",
+ "isTaskMarkDone" : "false"
+ }, {
+ "name" : "Meeting with exam invigilators",
+ "dateTime" : "2050-02-05T14:30",
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ "Meeting" ],
+ "link" : "https:apple.com",
+ "isTaskMarkDone" : "false"
+ } ]
+}
diff --git a/src/test/data/JsonTaskListStorageTest/invalidAndValidTaskList.json b/src/test/data/JsonTaskListStorageTest/invalidAndValidTaskList.json
new file mode 100644
index 00000000000..98c4ac23dfa
--- /dev/null
+++ b/src/test/data/JsonTaskListStorageTest/invalidAndValidTaskList.json
@@ -0,0 +1,19 @@
+{
+ "tasks" : [ {
+ "name" : "Consultation",
+ "dateTime" : null,
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ ],
+ "link" : null,
+ "isTaskMarkDone" : "false"
+ }, {
+ "name" : "Meetings",
+ "dateTime" : "2050-12-15T21:00",
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ ],
+ "link" : null,
+ "isTaskMarkDone" : "false"
+ } ]
+}
diff --git a/src/test/data/JsonTaskListStorageTest/invalidTasksTaskList.json b/src/test/data/JsonTaskListStorageTest/invalidTasksTaskList.json
new file mode 100644
index 00000000000..1e3fe8a6976
--- /dev/null
+++ b/src/test/data/JsonTaskListStorageTest/invalidTasksTaskList.json
@@ -0,0 +1,11 @@
+{
+ "tasks" : [ {
+ "name" : "Consultation",
+ "dateTime" : null,
+ "endDateTime" : "null",
+ "people" : [ ],
+ "tagged" : [ ],
+ "link" : null,
+ "isTaskMarkDone" : "false"
+ } ]
+}
diff --git a/src/test/data/JsonTaskListStorageTest/notJsonFormatTaskList.json b/src/test/data/JsonTaskListStorageTest/notJsonFormatTaskList.json
new file mode 100644
index 00000000000..a1097343b5d
--- /dev/null
+++ b/src/test/data/JsonTaskListStorageTest/notJsonFormatTaskList.json
@@ -0,0 +1 @@
+not json format!
diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/address/commons/util/AppUtilTest.java
index 594de1e6365..716319154d8 100644
--- a/src/test/java/seedu/address/commons/util/AppUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/AppUtilTest.java
@@ -9,7 +9,7 @@ public class AppUtilTest {
@Test
public void getImage_exitingImage() {
- assertNotNull(AppUtil.getImage("/images/address_book_32.png"));
+ assertNotNull(AppUtil.getImage("/images/nuslogo.jpg"));
}
@Test
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index ad923ac249a..40b0c15ee1e 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -3,13 +3,14 @@
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.logic.commands.CommandTestUtil.USERNAME_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.AMY;
+import java.awt.Point;
import java.io.IOException;
import java.nio.file.Path;
@@ -17,6 +18,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import seedu.address.commons.core.GuiSettings;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.ListCommand;
@@ -28,6 +30,7 @@
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonTaskListStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.StorageManager;
import seedu.address.testutil.PersonBuilder;
@@ -46,7 +49,9 @@ public void setUp() {
JsonAddressBookStorage addressBookStorage =
new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTaskListStorage taskListStorage =
+ new JsonTaskListStorage(temporaryFolder.resolve("taskList.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, taskListStorage);
logic = new LogicManager(model, storage);
}
@@ -58,7 +63,7 @@ public void execute_invalidCommandFormat_throwsParseException() {
@Test
public void execute_commandExecutionError_throwsCommandException() {
- String deleteCommand = "delete 9";
+ String deleteCommand = "deletec 9";
assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@@ -68,6 +73,7 @@ public void execute_validCommand_success() throws Exception {
assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model);
}
+
@Test
public void execute_storageThrowsIoException_throwsCommandException() {
// Setup LogicManager with JsonAddressBookIoExceptionThrowingStub
@@ -75,12 +81,14 @@ public void execute_storageThrowsIoException_throwsCommandException() {
new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json"));
JsonUserPrefsStorage userPrefsStorage =
new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTaskListStorage taskListStorage =
+ new JsonTaskListStorage(temporaryFolder.resolve("taskList.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, taskListStorage);
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;
+ + USERNAME_DESC_AMY;
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
@@ -88,6 +96,54 @@ public void execute_storageThrowsIoException_throwsCommandException() {
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
}
+ @Test
+ public void equals() {
+ Logic currentLogic;
+
+ JsonAddressBookStorage addressBookStorage =
+ new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
+ JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
+ JsonTaskListStorage taskListStorage =
+ new JsonTaskListStorage(temporaryFolder.resolve("taskList.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, taskListStorage);
+ currentLogic = new LogicManager(model, storage);
+
+ assertEquals(logic.getFilteredPersonList(), currentLogic.getFilteredPersonList());
+ assertEquals(logic.getAddressBook(), currentLogic.getAddressBook());
+ assertEquals(logic.getTaskList(), currentLogic.getTaskList());
+ assertEquals(logic.getFilteredTaskList(), currentLogic.getFilteredTaskList());
+ assertEquals(logic.getGuiSettings(), currentLogic.getGuiSettings());
+ assertEquals(logic.getAddressBookFilePath(), currentLogic.getAddressBookFilePath());
+ }
+
+ @Test
+ public void setGuiSetting_success() {
+ JsonAddressBookStorage addressBookStorage =
+ new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
+ JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
+ JsonTaskListStorage taskListStorage =
+ new JsonTaskListStorage(temporaryFolder.resolve("taskList.json"));
+
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, taskListStorage);
+ Logic currentLogic = new LogicManager(model, storage);
+
+ double widthToTest = 1000;
+ double windowHeightToTest = 740;
+ int xPosToTest = 0;
+ int yPosToTest = 0;
+
+ GuiSettings setGui = new GuiSettings(widthToTest, windowHeightToTest, xPosToTest, yPosToTest);
+ currentLogic.setGuiSettings(setGui);
+
+ GuiSettings currentGui = logic.getGuiSettings();
+ Point currentCoord = currentGui.getWindowCoordinates();
+
+ assertEquals(currentGui.getWindowWidth(), widthToTest);
+ assertEquals(currentGui.getWindowHeight(), windowHeightToTest);
+ assertEquals(currentCoord.getX(), xPosToTest);
+ assertEquals(currentCoord.getY(), yPosToTest);
+ }
+
@Test
public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0));
@@ -129,7 +185,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getTaskList());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index cb8714bb055..431e3fd24b5 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -1,6 +1,5 @@
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;
@@ -9,6 +8,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.TaskList;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -22,24 +22,18 @@ public class AddCommandIntegrationTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
}
@Test
public void execute_newPerson_success() {
Person validPerson = new PersonBuilder().build();
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList());
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
index 5865713d5dd..a3ddc7e96d9 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -4,11 +4,20 @@
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_EMAIL;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_GIT_USERNAME;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PHONE;
+import static seedu.address.commons.core.Messages.MESSAGE_TAG_TOO_LONG;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.DUPLICATE_ALICE_EMAIL;
+import static seedu.address.testutil.TypicalPersons.DUPLICATE_ALICE_PHONE;
+import static seedu.address.testutil.TypicalPersons.DUPLICATE_ALICE_USERNAME;
import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
@@ -18,11 +27,20 @@
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.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.GitUsername;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Task;
import seedu.address.testutil.PersonBuilder;
+
public class AddCommandTest {
@Test
@@ -32,28 +50,70 @@ public void constructor_nullPerson_throwsNullPointerException() {
@Test
public void execute_personAcceptedByModel_addSuccessful() throws Exception {
- ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded();
+ Model modelAcceptingPerson = new ModelManager();
Person validPerson = new PersonBuilder().build();
+ CommandResult commandResult = new AddCommand(validPerson).execute(modelAcceptingPerson);
- CommandResult commandResult = new AddCommand(validPerson).execute(modelStub);
+ //Creating the equivalent list
+ UniquePersonList validUniquePersonList = new UniquePersonList();
+ validUniquePersonList.add(validPerson);
+ ObservableList validList = validUniquePersonList.asUnmodifiableObservableList();
+ //Successfully add Person
assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser());
- assertEquals(Arrays.asList(validPerson), modelStub.personsAdded);
+
+ //Check that Valid Person exists in Model
+ assertEquals(validList, modelAcceptingPerson.getFilteredPersonList());
}
+ @Test
+ public void execute_invalidTagLength_throwsCommandException() {
+ Model model = new ModelManager();
+ ArrayList list = new ArrayList<>();
+ String invalidTag = "This tag is over 50 characters long..............................."
+ + "............................................................................................";
+ list.add(new Tag(invalidTag));
+ Set invalidTagSet = new HashSet(list);
+
+ Person invalidTagPerson = new PersonBuilder().withTags(invalidTag).build();
+ AddCommand invalidAddCommand = new AddCommand(invalidTagPerson);
+ assertThrows(CommandException.class,
+ String.format(MESSAGE_TAG_TOO_LONG,
+ invalidTagSet.stream()
+ .filter(tag -> tag.tagName.length() > 50)
+ .reduce("", (str, tag) -> tag + "\n" + str, (s1, s2) -> s1 + s2)), ()
+ -> invalidAddCommand.execute(model));
+ }
+
+
@Test
public void execute_duplicatePerson_throwsCommandException() {
- Person validPerson = new PersonBuilder().build();
+ Person validPerson = ALICE;
AddCommand addCommand = new AddCommand(validPerson);
- ModelStub modelStub = new ModelStubWithPerson(validPerson);
- assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub));
+ //Check Username Error
+ Model modelWithSameUsername = new ModelManager();
+ modelWithSameUsername.addPerson(DUPLICATE_ALICE_USERNAME);
+ assertThrows(CommandException.class,
+ MESSAGE_DUPLICATE_GIT_USERNAME, () -> addCommand.execute(modelWithSameUsername));
+
+ //Check Email Error
+ Model modelWithSameEmail = new ModelManager();
+ modelWithSameEmail.addPerson(DUPLICATE_ALICE_EMAIL);
+ assertThrows(CommandException.class,
+ MESSAGE_DUPLICATE_EMAIL, () -> addCommand.execute(modelWithSameEmail));
+
+ //Check Phone Error
+ Model modelWithSamePhone = new ModelManager();
+ modelWithSamePhone.addPerson(DUPLICATE_ALICE_PHONE);
+ assertThrows(CommandException.class,
+ MESSAGE_DUPLICATE_PHONE, () -> addCommand.execute(modelWithSamePhone));
}
@Test
public void equals() {
- Person alice = new PersonBuilder().withName("Alice").build();
- Person bob = new PersonBuilder().withName("Bob").build();
+ Person alice = new PersonBuilder().withName("Alice").withEmail("alice123@gmail.com").build();
+ Person bob = new PersonBuilder().withName("Bob").withEmail("bob123@gmail.com").build();
AddCommand addAliceCommand = new AddCommand(alice);
AddCommand addBobCommand = new AddCommand(bob);
@@ -83,6 +143,11 @@ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public void setTaskList(ReadOnlyTaskList taskList) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public ReadOnlyUserPrefs getUserPrefs() {
throw new AssertionError("This method should not be called.");
@@ -128,6 +193,16 @@ public boolean hasPerson(Person person) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public boolean hasEmail(Email email) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPhone(Phone phone) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public void deletePerson(Person target) {
throw new AssertionError("This method should not be called.");
@@ -147,6 +222,56 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public ObservableList getFilteredTaskList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setTask(Task taskToEdit, Task editedTask) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyTaskList getTaskList() {
+ 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 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 boolean hasTask(Task task) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasUsername (GitUsername gitUsername) {
+ throw new AssertionError("This method should not be called");
+ }
+
+ @Override
+ public void markTask(Task task) {
+ throw new AssertionError("This method should not be called");
+ }
+
+ @Override
+ public void unmarkTask(Task task) {
+ throw new AssertionError("This method should not be called");
+ }
+
}
/**
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..ce40b811944
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddTaskCommandTest.java
@@ -0,0 +1,103 @@
+package seedu.address.logic.commands;
+
+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_DATE_RANGE;
+import static seedu.address.commons.core.Messages.MESSAGE_TAG_TOO_LONG;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_DATETIME;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_NAME;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_TAG;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_ZOOMLINK;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.TaskList;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.Task;
+import seedu.address.testutil.TaskBuilder;
+
+public class AddTaskCommandTest {
+
+ @Test
+ public void constructor_nullTaskAttributes_throwsNullPointerException() {
+ ArrayList list = new ArrayList<>();
+ list.add(new Tag(DEFAULT_TAG));
+ Set defaultTagSet = new HashSet(list);
+ Link defaultLink = new Link(DEFAULT_ZOOMLINK);
+
+ //Null Taskname
+ assertThrows(NullPointerException.class, ()
+ -> new AddTaskCommand(null, DEFAULT_DATETIME, DEFAULT_DATETIME,
+ defaultTagSet, defaultLink));
+
+ //Null datetime
+ assertThrows(NullPointerException.class, ()
+ -> new AddTaskCommand(DEFAULT_NAME, null, null, defaultTagSet, defaultLink));
+
+ //Null tag set
+ assertThrows(NullPointerException.class, ()
+ -> new AddTaskCommand(DEFAULT_NAME, DEFAULT_DATETIME, DEFAULT_DATETIME, null, defaultLink));
+
+
+ }
+
+ @Test
+ public void execute_taskAcceptedByModel_addSuccessful() throws Exception {
+ Model modelAcceptingTask = new ModelManager();
+ Task validTask = new TaskBuilder().build();
+ CommandResult validResult = new AddTaskCommand(validTask).execute(modelAcceptingTask);
+
+ //Check that task is added
+ assertEquals(String.format(AddTaskCommand.ADD_TASK_SUCCESS, validTask), validResult.getFeedbackToUser());
+
+ //Check that TaskList in Model contains validTask
+ TaskList validTaskList = new TaskList();
+ validTaskList.addTask(validTask);
+ assertEquals(validTaskList, modelAcceptingTask.getTaskList());
+ }
+
+ @Test
+ public void execute_invalidTaskDateTimeThrowsCommandException() throws Exception {
+ Model model = new ModelManager();
+
+ //InvalidDateTime
+ Task invalidDateTimeTask = new TaskBuilder().build();
+ invalidDateTimeTask.changeEndDateTime(DEFAULT_DATETIME.minusDays(1));
+ AddTaskCommand invalidDateTimeCommand = new AddTaskCommand(invalidDateTimeTask);
+ assertThrows(CommandException.class,
+ MESSAGE_INVALID_DATE_RANGE, () -> invalidDateTimeCommand.execute(model));
+
+ //Invalid Tag Length
+ Task invalidTagTask = new TaskBuilder().build();
+ ArrayList list = new ArrayList<>();
+ list.add(new Tag("This tag is over 50 characters long..............................."
+ + "............................................................................................"));
+ Set invalidTagSet = new HashSet(list);
+ invalidTagTask.setTags(invalidTagSet);
+ AddTaskCommand invalidTagCommand = new AddTaskCommand(invalidTagTask);
+ assertThrows(CommandException.class,
+ String.format(MESSAGE_TAG_TOO_LONG,
+ invalidTagSet.stream()
+ .filter(tag -> tag.tagName.length() > 50)
+ .reduce("", (str, tag) -> tag + "\n" + str, (s1, s2) -> s1 + s2)), ()
+ -> invalidTagCommand.execute(model));
+
+ }
+
+ @Test
+ public void equals() {
+ Task validTask = new TaskBuilder().build();
+ AddTaskCommand command1 = new AddTaskCommand(validTask);
+ AddTaskCommand command2 = new AddTaskCommand(validTask);
+ assertTrue(command1.equals(command2));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/AssignCommandTest.java b/src/test/java/seedu/address/logic/commands/AssignCommandTest.java
new file mode 100644
index 00000000000..72095d4f70d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AssignCommandTest.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+import org.junit.jupiter.api.Test;
+
+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.TaskList;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
+
+class AssignCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ void execute_addFirstPersonToFirstTask_success() {
+ Person personToAdd = model.getFilteredPersonList().get(0);
+ Task taskToEdit = model.getFilteredTaskList().get(0);
+ Task updatedTask = new Task(taskToEdit.getName(), taskToEdit.getDateTime(),
+ taskToEdit.getPeople(), taskToEdit.getTags(), taskToEdit.getLink(), taskToEdit.isTaskMark());
+
+ updatedTask.addPerson(personToAdd);
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList(model.getTaskList()));
+ expectedModel.setTask(taskToEdit, updatedTask);
+
+ AssignCommand assignCommand =
+ new AssignCommand(Index.fromZeroBased(0), Index.fromZeroBased(0));
+ assertFalse(taskToEdit.getPeople().contains(personToAdd));
+
+ String expectedMessage = String.format(
+ AssignCommand.MESSAGE_ADD_PERSON_TO_TASK_SUCCESS_SINGLE,
+ personToAdd.getName(), personToAdd.getPhone(), updatedTask, updatedTask.getNoOfPeople());
+
+ assertCommandSuccess(assignCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ void execute_addDuplicatePersonToTask_failure() {
+ Person personToAdd = model.getFilteredPersonList().get(0);
+ Task taskToEdit = model.getFilteredTaskList().get(0);
+ Task updatedTask = new Task(taskToEdit.getName(), taskToEdit.getDateTime(),
+ taskToEdit.getPeople(), taskToEdit.getTags(), taskToEdit.getLink(), taskToEdit.isTaskMark());
+ updatedTask.addPerson(personToAdd);
+
+ AssignCommand assignCommand =
+ new AssignCommand(Index.fromZeroBased(0), Index.fromZeroBased(0));
+
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList(model.getTaskList()));
+ expectedModel.setTask(taskToEdit, updatedTask);
+
+ String expectedMessage = String.format(
+ AssignCommand.MESSAGE_DUPLICATE_PERSON, personToAdd.getName(), updatedTask);
+ assertCommandFailure(assignCommand, expectedModel, expectedMessage);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..aca39ef6e2c 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -8,6 +8,7 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.TaskList;
import seedu.address.model.UserPrefs;
public class ClearCommandTest {
@@ -22,8 +23,8 @@ public void execute_emptyAddressBook_success() {
@Test
public void execute_nonEmptyAddressBook_success() {
- Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
expectedModel.setAddressBook(new AddressBook());
assertCommandSuccess(new ClearCommand(), model, ClearCommand.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..0d124b00820 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -3,12 +3,16 @@
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_DATETIME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GIT_USERNAME;
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_TASKNAME;
import static seedu.address.testutil.Assert.assertThrows;
+import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -19,7 +23,10 @@
import seedu.address.model.Model;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.TaskNameContainsKeywordsPredicate;
import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.EditTaskDescriptorBuilder;
/**
* Contains helper methods for testing commands.
@@ -32,8 +39,8 @@ public class CommandTestUtil {
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_USERNAME_AMY = "amy123";
+ public static final String VALID_USERNAME_BOB = "bob123";
public static final String VALID_TAG_HUSBAND = "husband";
public static final String VALID_TAG_FRIEND = "friend";
@@ -43,14 +50,20 @@ public class CommandTestUtil {
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 USERNAME_DESC_AMY = " " + PREFIX_GIT_USERNAME + VALID_USERNAME_AMY;
+ public static final String USERNAME_DESC_BOB = " " + PREFIX_GIT_USERNAME + VALID_USERNAME_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_PHONE_SHORT = " " + PREFIX_PHONE + "99";
+ public static final String INVALID_PHONE_LONG = " " + PREFIX_PHONE + "99999999999999999";
+
+ public static final String INVALID_EMAIL_LONG = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@gmail.com";
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_EMAIL_DESC_LONG = " " + PREFIX_EMAIL + INVALID_EMAIL_LONG; // 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
@@ -60,13 +73,62 @@ public class CommandTestUtil {
public static final EditCommand.EditPersonDescriptor DESC_AMY;
public static final EditCommand.EditPersonDescriptor DESC_BOB;
+ //------------------------ For task--------------------------------------------------------
+ public static final String VALID_NAME_TASKA = "Homework";
+ public static final String VALID_NAME_TASKB = "Brush my teeth";
+ public static final String VALID_LINK_TASKA = "https:google.com";
+ public static final String VALID_LINK_TASKB = "https:apple.com";
+ public static final String INVALID_LINK = "a";
+ public static final LocalDateTime VALID_DATETIME_TASKA =
+ LocalDateTime.of(2050, 12, 15, 21, 0);
+ public static final String VALID_DATETIME_TASKA_STRING = "15-12-2050 2100";
+ public static final String VALID_DATETIME_TASKA_PLUS_ONE_DAY = "16-12-2050 2100";
+ public static final LocalDateTime VALID_DATETIME_TASKB =
+ LocalDateTime.of(2050, 02, 05, 13, 0);
+ public static final String VALID_DATETIME_TASKB_STRING = "05-02-2050 1300";
+ public static final String VALID_TAG_TASKA = "Schoolwork";
+ public static final String VALID_TAG_TASKB = "Toilet";
+ public static final String VALID_TAG_CHORES = "Chores";
+ public static final String INVALID_TASK_NAME_SHORT = "A";
+ public static final String INVALID_TASK_NAME_LONG = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+ public static final EditTaskCommand.EditTaskDescriptor TASK_A;
+ public static final EditTaskCommand.EditTaskDescriptor TASK_B;
+
+ public static final String NAME_DESC_TASKA = " " + PREFIX_TASKNAME + VALID_NAME_TASKA;
+ public static final String NAME_DESC_TASKB = " " + PREFIX_TASKNAME + VALID_NAME_TASKB;
+ public static final String DATETIME_DESC_TASKA = " " + PREFIX_DATETIME + "15-12-2050 2100";
+ public static final String DATETIME_DESC_TASKB = " " + PREFIX_DATETIME + "05-02-2050 1300";
+ public static final String TAG_DESC_TASKA = " " + PREFIX_TAG + VALID_TAG_TASKA;
+ public static final String TAG_DESC_TASKB = " " + PREFIX_TAG + VALID_TAG_TASKB;
+ public static final String WORDS_IN_DATETIME = " " + PREFIX_DATETIME + "";
+
+ public static final String INVALID_DATETIME_WORDS = PREFIX_DATETIME + "10-06-abcd 1500"; // Words instead of numbers
+ public static final String INVALID_DATETIME_FORMAT = PREFIX_DATETIME + "22/11/2050 1220"; // Wrong format
+ public static final String INVALID_DATETIME_DAY = PREFIX_DATETIME + "32-02-2050 1220"; // Wrong Day
+ public static final String INVALID_DATETIME_MONTH = PREFIX_DATETIME + "02-22-2050 1220"; // Wrong Month
+ public static final String INVALID_DATETIME_LEAPYEAR = PREFIX_DATETIME + "29-02-2021 1220"; // Wrong Leap year
+ public static final String INVALID_DATETIME_VALUE_NOTIME = "55-02-2021"; // Wrong Leap year
+
+ public static final String VALID_DATETIME_NOTIME_STRING = "13-12-2050";
+ public static final LocalDateTime VALID_DATETIME_NOTIME =
+ LocalDateTime.of(2050, 12, 13, 0, 0);
+ public static final String VALID_DATETIME_NOTIME_LATER_STRING = "30-12-2050";
+ public static final LocalDateTime VALID_DATETIME_NOTIME_LATER =
+ LocalDateTime.of(2050, 12, 30, 23, 59);
+
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withUsername(VALID_USERNAME_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)
+ .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withUsername(VALID_USERNAME_BOB)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+
+ TASK_A = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKA)
+ .withDateTime(VALID_DATETIME_TASKA).withTags(VALID_TAG_TASKA).build();
+ TASK_B = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKB)
+ .withDateTime(VALID_DATETIME_TASKB).withTags(VALID_TAG_TASKB).build();
}
/**
@@ -125,4 +187,17 @@ public static void showPersonAtIndex(Model model, Index targetIndex) {
assertEquals(1, model.getFilteredPersonList().size());
}
+ /**
+ * Updates {@code model}'s filtered list to show only the task at the given {@code targetIndex} in the
+ * {@code model}'s Task List.
+ */
+ public static void showTaskAtIndex(Model model, Index targetIndex) {
+ assertTrue(targetIndex.getZeroBased() < model.getFilteredTaskList().size());
+ Task task = model.getFilteredTaskList().get(targetIndex.getZeroBased());
+ final String taskName = task.getName();
+ String[] arrTaskName = taskName.split(" ");
+ model.updateFilteredTaskList(new TaskNameContainsKeywordsPredicate(Arrays.asList(arrTaskName)));
+ assertEquals(1, model.getFilteredTaskList().size());
+ }
+
}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index 45a8c910ba1..6b565757726 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -5,6 +5,7 @@
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.TypicalAssignedTasks.getTypicalAssignedTaskList;
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;
@@ -13,10 +14,15 @@
import seedu.address.commons.core.Messages;
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.ModelManager;
+import seedu.address.model.TaskList;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
+import seedu.address.testutil.TaskBuilder;
/**
* Contains integration tests (interaction with the Model) and unit tests for
@@ -24,7 +30,7 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
@Test
public void execute_validIndexUnfilteredList_success() {
@@ -33,7 +39,7 @@ public void execute_validIndexUnfilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList());
expectedModel.deletePerson(personToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -56,7 +62,7 @@ public void execute_validIndexFilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList());
expectedModel.deletePerson(personToDelete);
showNoPerson(expectedModel);
@@ -76,6 +82,45 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_tasksUpdated_success() throws CommandException {
+ Person toDelete = model.getFilteredPersonList().get(0);
+
+ Model m = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList());
+
+
+ Task affectedTask = new TaskBuilder().build();
+ affectedTask.addPerson(toDelete);
+
+ m.addTask(affectedTask);
+
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ deleteCommand.execute(m);
+
+ assertFalse(affectedTask.containsPerson(toDelete));
+ }
+
+ @Test
+ public void execute_personInTask_isRemovedFromTask() {
+ Model mod = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalAssignedTaskList());
+ Model expectedMod = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalAssignedTaskList());
+ Person personToDelete = mod.getFilteredPersonList().get(0);
+
+ DeleteCommand deleteCommand =
+ new DeleteCommand(Index.fromZeroBased((0)));
+
+ assertTrue(mod.getFilteredTaskList().stream().anyMatch(task -> task.containsPerson(personToDelete)));
+
+ String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
+
+ expectedMod.deletePerson(personToDelete);
+
+ assertCommandSuccess(deleteCommand, mod, expectedMessage, expectedMod);
+
+ assertTrue(expectedMod.getFilteredTaskList().stream().allMatch(task -> !task.containsPerson(personToDelete)));
+ }
+
@Test
public void equals() {
DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON);
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..c4fe0ddbbcd
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java
@@ -0,0 +1,116 @@
+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.INDEX_FIRST_TASK;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+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.TaskList;
+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(), getTypicalTaskList());
+
+ @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(DeleteTaskCommand.MESSAGE_DELETE_TASK_SUCCESS, taskToDelete);
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList());
+ expectedModel.deleteTask(taskToDelete);
+
+ assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1);
+ DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(outOfBoundIndex);
+
+ assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ /*
+ @Test
+ public void execute_validIndexFilteredList_success() {
+ showTaskAtIndex(model, INDEX_FIRST_TASK);
+
+ Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased());
+ DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(INDEX_FIRST_TASK);
+
+ String expectedMessage = String.format(DeleteTaskCommand.MESSAGE_DELETE_TASK_SUCCESS, taskToDelete);
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList());
+ expectedModel.deleteTask(taskToDelete);
+ showNoTask(expectedModel);
+
+ assertCommandSuccess(deleteTaskCommand, 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 task list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getTaskList().getTaskList().size());
+
+ DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(outOfBoundIndex);
+
+ assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ */
+
+ @Test
+ public void equals() {
+ DeleteTaskCommand deleteFirstTaskCommand = new DeleteTaskCommand(INDEX_FIRST_TASK);
+ DeleteTaskCommand deleteSecondTaskCommand = new DeleteTaskCommand(INDEX_SECOND_TASK);
+
+ // same object -> returns true
+ assertTrue(deleteFirstTaskCommand.equals(deleteFirstTaskCommand));
+
+ // same values -> returns true
+ DeleteTaskCommand deleteFirstTaskCommandCopy = new DeleteTaskCommand(INDEX_FIRST_TASK);
+ assertTrue(deleteFirstTaskCommand.equals(deleteFirstTaskCommandCopy));
+
+ // different types -> returns false
+ assertFalse(deleteFirstTaskCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(deleteFirstTaskCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(deleteFirstTaskCommand.equals(deleteSecondTaskCommand));
+ }
+
+ /**
+ * Updates {@code model}'s filtered list to show no one.
+ */
+ private void showNoTask(Model model) {
+ model.updateFilteredTaskList(p -> 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
index 214c6c2507b..355a766a4cc 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -2,6 +2,7 @@
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_PHONE;
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;
@@ -12,6 +13,7 @@
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.AMY;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
@@ -19,20 +21,24 @@
import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+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.TaskList;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
+import seedu.address.testutil.TaskBuilder;
/**
* Contains integration tests (interaction with the Model) and unit tests for EditCommand.
*/
public class EditCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
@Test
public void execute_allFieldsSpecifiedUnfilteredList_success() {
@@ -42,12 +48,37 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
-
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
+ @Test
+ public void execute_tasksUpdated_success() throws CommandException {
+ Person toEdit = model.getFilteredPersonList().get(0);
+
+ Model m = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList());
+
+
+ Task affectedTask = new TaskBuilder().build();
+ affectedTask.addPerson(toEdit);
+
+ m.addTask(affectedTask);
+
+ Person editedPerson = new PersonBuilder().build();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+ editCommand.execute(m);
+
+ assertTrue(affectedTask.containsPerson(editedPerson));
+
+ assertFalse(affectedTask.containsPerson(toEdit));
+ }
+
+
+
@Test
public void execute_someFieldsSpecifiedUnfilteredList_success() {
Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size());
@@ -63,48 +94,41 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList());
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());
+ Person editedPerson = AMY;
+ EditPersonDescriptor editedPersonDescriptor = new EditPersonDescriptorBuilder(AMY).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, editedPersonDescriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), new TaskList());
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);
+ assertCommandFailure(editCommand, model, MESSAGE_DUPLICATE_PHONE);
+
}
@Test
@@ -116,7 +140,7 @@ public void execute_duplicatePersonFilteredList_failure() {
EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
new EditPersonDescriptorBuilder(personInList).build());
- assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
+ assertCommandFailure(editCommand, model, MESSAGE_DUPLICATE_PHONE);
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index e0288792e72..2fa3d7a1f2e 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -4,11 +4,11 @@
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 static seedu.address.logic.commands.CommandTestUtil.VALID_USERNAME_BOB;
import org.junit.jupiter.api.Test;
@@ -48,7 +48,7 @@ public void equals() {
assertFalse(DESC_AMY.equals(editedAmy));
// different address -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build();
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withUsername(VALID_USERNAME_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
// different tags -> returns false
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..095949c9a6a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/EditTaskCommandTest.java
@@ -0,0 +1,165 @@
+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.TASK_A;
+import static seedu.address.logic.commands.CommandTestUtil.TASK_B;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_TASKB;
+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_SECOND_TASK;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
+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 EditTaskCommand.
+ */
+public class EditTaskCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ public void execute_allFieldsSpecifiedUnfilteredList_success() {
+ Task editedTask = new TaskBuilder().build();
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder(editedTask).build();
+ EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, descriptor);
+
+ String expectedMessage = String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask);
+
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), model.getTaskList());
+ expectedModel.setTask(model.getFilteredTaskList().get(0), editedTask);
+
+ assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_noFieldSpecifiedUnfilteredList_success() {
+ EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK, new EditTaskDescriptor());
+ Task editedTask = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased());
+
+ String expectedMessage = String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask);
+
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), model.getTaskList());
+
+ assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel);
+ }
+
+ /*
+ @Test
+ public void execute_filteredList_success() {
+ showTaskAtIndex(model, INDEX_FIRST_TASK);
+
+ Task taskInFilteredList = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased());
+ Task editedTask = new TaskBuilder(taskInFilteredList).withTaskName(VALID_NAME_TASKB).build();
+ EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK,
+ new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKB).build());
+
+ String expectedMessage = String.format(EditTaskCommand.MESSAGE_EDIT_TASK_SUCCESS, editedTask);
+
+ Model expectedModel = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), model.getTaskList());
+ expectedModel.setTask(model.getFilteredTaskList().get(0), editedTask);
+
+ assertCommandSuccess(editTaskCommand, model, expectedMessage, expectedModel);
+ }
+
+ */
+
+ @Test
+ public void execute_duplicateTaskUnfilteredList_failure() {
+ Task firstTask = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased());
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder(firstTask).build();
+ EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_SECOND_TASK, descriptor);
+
+ assertCommandFailure(editTaskCommand, model, EditTaskCommand.MESSAGE_DUPLICATE_TASK);
+ }
+
+ /*
+ @Test
+ public void execute_duplicateTaskFilteredList_failure() {
+ showTaskAtIndex(model, INDEX_FIRST_TASK);
+
+ // edit person in filtered list into a duplicate in address book
+ Task taskInList = model.getTaskList().getTaskList().get(INDEX_SECOND_TASK.getZeroBased());
+ EditTaskCommand editTaskCommand = new EditTaskCommand(INDEX_FIRST_TASK,
+ new EditTaskDescriptorBuilder(taskInList).build());
+
+ assertCommandFailure(editTaskCommand, model, EditTaskCommand.MESSAGE_DUPLICATE_TASK);
+ }
+
+ */
+
+ @Test
+ public void execute_invalidTaskIndexUnfilteredList_failure() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1);
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKB).build();
+ EditTaskCommand editTaskCommand = new EditTaskCommand(outOfBoundIndex, descriptor);
+
+ assertCommandFailure(editTaskCommand, model, Messages.MESSAGE_INVALID_TASK_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_invalidTaskIndexFilteredList_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.getTaskList().getTaskList().size());
+
+ EditTaskCommand editTaskCommand = new EditTaskCommand(outOfBoundIndex,
+ new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKB).build());
+
+ assertCommandFailure(editTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ */
+
+
+ @Test
+ public void equals() {
+ final EditTaskCommand standardCommand = new EditTaskCommand(INDEX_FIRST_TASK, TASK_A);
+
+ // same values -> returns true
+ EditTaskDescriptor copyDescriptor = new EditTaskDescriptor(TASK_A);
+ 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));
+
+ // different types -> returns false
+ assertFalse(standardCommand.equals(new ClearCommand()));
+
+ // different index -> returns false
+ assertFalse(standardCommand.equals(new EditTaskCommand(INDEX_SECOND_TASK, TASK_A)));
+
+ // different descriptor -> returns false
+ assertFalse(standardCommand.equals(new EditTaskCommand(INDEX_FIRST_TASK, TASK_B)));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/FilterByDateCommandTest.java b/src/test/java/seedu/address/logic/commands/FilterByDateCommandTest.java
new file mode 100644
index 00000000000..80236a8dd2d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FilterByDateCommandTest.java
@@ -0,0 +1,94 @@
+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.assertCommandSuccess;
+import static seedu.address.testutil.TypicalLocalDateTime.DATE_1;
+import static seedu.address.testutil.TypicalLocalDateTime.DATE_2;
+import static seedu.address.testutil.TypicalLocalDateTime.DATE_3;
+import static seedu.address.testutil.TypicalLocalDateTime.DATE_4;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.CONSULTATION;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+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.task.TaskBetweenDatesPredicate;
+import seedu.address.testutil.TypicalLocalDateTime;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for FilterByDateCommand.
+ */
+public class FilterByDateCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ public void equals() {
+ TaskBetweenDatesPredicate firstPredicate =
+ new TaskBetweenDatesPredicate(new ArrayList<>(Arrays.asList(DATE_1, DATE_2)));
+ TaskBetweenDatesPredicate secondPredicate =
+ new TaskBetweenDatesPredicate(new ArrayList<>(Arrays.asList(DATE_3, DATE_4)));
+
+ FilterByDateCommand firstFilterDateCommand = new FilterByDateCommand(firstPredicate);
+ FilterByDateCommand secondFilterDateCommand = new FilterByDateCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(firstFilterDateCommand.equals(firstFilterDateCommand));
+
+ // same values -> return true
+ FilterByDateCommand firstFilterDateCommandCopy = new FilterByDateCommand(firstPredicate);
+ assertTrue(firstFilterDateCommand.equals(firstFilterDateCommandCopy));
+
+ // different types -> returns false
+ assertFalse(firstFilterDateCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(firstFilterDateCommand.equals(null));
+
+ // different task -> returns false
+ assertFalse(firstFilterDateCommand.equals(secondFilterDateCommand));
+
+ }
+
+ /**
+ * Date Range: 10 Mar -> 6 Jul
+ * Task Filtered: Laundry - 2 June
+ */
+ @Test
+ public void execute_validDateTimeRange_oneTaskFound() {
+ String expectedMessage = String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, 1);
+ TaskBetweenDatesPredicate predicate = new TaskBetweenDatesPredicate(TypicalLocalDateTime.getTypicalDateTimes());
+ FilterByDateCommand command = new FilterByDateCommand(predicate);
+ expectedModel.updateFilteredTaskList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(CONSULTATION), model.getFilteredTaskList());
+ }
+
+ /**
+ * Date Range: 14 Jan-> 10 Mar
+ * Task Filtered: None
+ */
+ @Test
+ public void execute_validDateTimeRange_noTaskFound() {
+ String expectedMessage = String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, 0);
+ TaskBetweenDatesPredicate predicate = new TaskBetweenDatesPredicate(
+ new ArrayList<>(Arrays.asList(DATE_4, DATE_1)));
+ FilterByDateCommand command = new FilterByDateCommand(predicate);
+ expectedModel.updateFilteredTaskList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredTaskList());
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index 9b15db28bbb..98158912bc6 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -17,6 +17,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.TaskList;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.NameContainsKeywordsPredicate;
@@ -24,8 +25,8 @@
* 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());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
@Test
public void equals() {
diff --git a/src/test/java/seedu/address/logic/commands/FindTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/FindTaskCommandTest.java
new file mode 100644
index 00000000000..64a94ec3e6b
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FindTaskCommandTest.java
@@ -0,0 +1,108 @@
+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_TASKS_LISTED_OVERVIEW;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.BRUSH_TEETH;
+import static seedu.address.testutil.TypicalTasks.CONSULTATION;
+import static seedu.address.testutil.TypicalTasks.INVIGILATOR_MEETING;
+import static seedu.address.testutil.TypicalTasks.LAUNDRY;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+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.TaskNameContainsKeywordsPredicate;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for FindTaskCommand.
+ */
+
+public class FindTaskCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ public void equals() {
+ TaskNameContainsKeywordsPredicate firstPredicate =
+ new TaskNameContainsKeywordsPredicate(Collections.singletonList("first"));
+ TaskNameContainsKeywordsPredicate secondPredicate =
+ new TaskNameContainsKeywordsPredicate(Collections.singletonList("second"));
+
+ FindTaskCommand findFirstCommand = new FindTaskCommand(firstPredicate);
+ FindTaskCommand findSecondCommand = new FindTaskCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(findFirstCommand.equals(findFirstCommand));
+
+ // same values -> returns true
+ FindTaskCommand findFirstCommandCopy = new FindTaskCommand(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_noTaskFound() {
+ String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 0);
+ TaskNameContainsKeywordsPredicate predicate = preparePredicate(" ");
+ FindTaskCommand command = new FindTaskCommand(predicate);
+ expectedModel.updateFilteredTaskList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredTaskList());
+ }
+
+ @Test
+ public void execute_multipleKeywords_multipleTasksFound() {
+ String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 2);
+ TaskNameContainsKeywordsPredicate predicate = preparePredicate("Brush students");
+ FindTaskCommand command = new FindTaskCommand(predicate);
+ expectedModel.updateFilteredTaskList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(BRUSH_TEETH, CONSULTATION), model.getFilteredTaskList());
+ }
+
+ @Test
+ public void execute_oneTagAsKeyword_multipleTaskFound() {
+ String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 2);
+ TaskNameContainsKeywordsPredicate predicate = preparePredicate("Chores");
+ FindTaskCommand command = new FindTaskCommand(predicate);
+ expectedModel.updateFilteredTaskList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(BRUSH_TEETH, LAUNDRY), model.getFilteredTaskList());
+ }
+
+ @Test
+ public void execute_multipleTagsAsKeyword_multipleTaskFound() {
+ String expectedMessage = String.format(MESSAGE_TASKS_LISTED_OVERVIEW, 3);
+ TaskNameContainsKeywordsPredicate predicate = preparePredicate("Chores Meeting");
+ FindTaskCommand command = new FindTaskCommand(predicate);
+ expectedModel.updateFilteredTaskList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(BRUSH_TEETH, LAUNDRY, INVIGILATOR_MEETING), model.getFilteredTaskList());
+ }
+
+ /**
+ * Parses {@code userInput} into a {@code TaskNameContainsKeywordsPredicate}.
+ */
+ private TaskNameContainsKeywordsPredicate preparePredicate(String userInput) {
+ return new TaskNameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/GenerateEmailsCommandTest.java b/src/test/java/seedu/address/logic/commands/GenerateEmailsCommandTest.java
new file mode 100644
index 00000000000..90ea4c4fcc7
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/GenerateEmailsCommandTest.java
@@ -0,0 +1,80 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+import static seedu.address.logic.commands.GenerateEmailsCommand.MESSAGE_GENERATED_EMAILS;
+import static seedu.address.logic.commands.GenerateEmailsCommand.MESSAGE_NO_CONTACTS_ASSIGNED;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+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.ModelManager;
+import seedu.address.model.TaskList;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.task.Task;
+import seedu.address.testutil.TaskBuilder;
+import seedu.address.testutil.TypicalAssignedTasks;
+
+class GenerateEmailsCommandTest {
+
+ @Test
+ void execute_validIndex_success() throws CommandException {
+ TaskList sampleTasks = TypicalAssignedTasks.getTypicalAssignedTaskList();
+ Index index = Index.fromOneBased(1);
+
+ Model model = new ModelManager(new AddressBook(), new UserPrefs(), sampleTasks);
+
+ GenerateEmailsCommand command = new GenerateEmailsCommand(index);
+
+ Task source = sampleTasks.getTaskList().get(index.getZeroBased());
+
+ String expected = String.format(MESSAGE_GENERATED_EMAILS, source, source.getEmails());
+ String actual = command.execute(model).toString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ void execute_outOfBoundsIndex_failure() {
+ TaskList sampleTasks = TypicalAssignedTasks.getTypicalAssignedTaskList();
+ Index index = Index.fromOneBased(99);
+
+ Model model = new ModelManager(new AddressBook(), new UserPrefs(), sampleTasks);
+
+ GenerateEmailsCommand command = new GenerateEmailsCommand(index);
+
+ assertThrows(CommandException.class, MESSAGE_INVALID_TASK_DISPLAYED_INDEX, () -> command.execute(model));
+ }
+
+ @Test
+ void execute_noContactsAssigned_failure() throws CommandException {
+ TaskList sampleTasks = new TaskList();
+ Task noContactsAssigned = new TaskBuilder().withPeople(List.of()).build();
+ sampleTasks.addTask(noContactsAssigned);
+
+ Index index = Index.fromOneBased(1);
+
+ Model model = new ModelManager(new AddressBook(), new UserPrefs(), sampleTasks);
+
+ GenerateEmailsCommand command = new GenerateEmailsCommand(index);
+
+ String expected = String.format(MESSAGE_NO_CONTACTS_ASSIGNED, noContactsAssigned);
+ String actual = command.execute(model).toString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ void equals_sameAssignedIndex_success() {
+ GenerateEmailsCommand c1 = new GenerateEmailsCommand(Index.fromOneBased(1));
+ GenerateEmailsCommand c2 = new GenerateEmailsCommand(Index.fromOneBased(1));
+
+ assertEquals(c1, c2);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ImportCommandTest.java b/src/test/java/seedu/address/logic/commands/ImportCommandTest.java
new file mode 100644
index 00000000000..d03ec8a947c
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ImportCommandTest.java
@@ -0,0 +1,125 @@
+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.ImportCommand.MESSAGE_DUPLICATES_NOT_ADDED;
+import static seedu.address.logic.commands.ImportCommand.MESSAGE_FOUND_TAGS_TOO_LONG;
+import static seedu.address.logic.commands.ImportCommand.MESSAGE_INVALID_FIELDS;
+import static seedu.address.logic.commands.ImportCommand.MESSAGE_NO_CONTACTS_ADDED;
+import static seedu.address.logic.commands.ImportCommand.MESSAGE_SUCCESS;
+import static seedu.address.logic.commands.ImportCommand.personListToString;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+class ImportCommandTest {
+
+ @Test
+ void execute_onePersonAdded_success() throws Exception {
+ Person p = new PersonBuilder().build();
+ List toAdd = List.of(p);
+ String filename = "data.csv";
+ List invalidFields = List.of();
+
+ Model model = new ModelManager();
+
+ assertFalse(model.hasPerson(p));
+
+ CommandResult commandResult = new ImportCommand(toAdd, filename, invalidFields).execute(model);
+
+ assertTrue(model.hasPerson(p));
+
+ assertEquals(String.format(MESSAGE_SUCCESS, 1, filename)
+ + personListToString(toAdd), commandResult.toString());
+ }
+
+ @Test
+ void execute_noPeopleAdded_success() throws Exception {
+ List toAdd = List.of();
+ String filename = "data.csv";
+ List invalidFields = List.of();
+
+ Model model = new ModelManager();
+
+ CommandResult commandResult = new ImportCommand(toAdd, filename, invalidFields).execute(model);
+
+ assertEquals(String.format(MESSAGE_NO_CONTACTS_ADDED, filename), commandResult.toString());
+ }
+
+ @Test
+ void execute_duplicatesFound_success() throws Exception {
+ Person p = new PersonBuilder().build();
+ List toAdd = List.of(p, p);
+ String filename = "data.csv";
+ List invalidFields = List.of();
+
+ Model model = new ModelManager();
+
+ assertFalse(model.hasPerson(p));
+ CommandResult commandResult = new ImportCommand(toAdd, filename, invalidFields).execute(model);
+ assertTrue(model.hasPerson(p));
+
+ assertEquals(String.format(MESSAGE_SUCCESS, 1, filename)
+ + personListToString(List.of(p))
+ + String.format(MESSAGE_DUPLICATES_NOT_ADDED, 1)
+ + personListToString(List.of(p)), commandResult.toString());
+ }
+
+ @Test
+ void execute_alreadyInContacts_success() throws Exception {
+ Person p = new PersonBuilder().build();
+ List toAdd = List.of(p);
+ String filename = "data.csv";
+ List invalidFields = List.of();
+
+ Model model = new ModelManager();
+ model.addPerson(p);
+
+ assertTrue(model.hasPerson(p));
+ CommandResult commandResult = new ImportCommand(toAdd, filename, invalidFields).execute(model);
+
+
+ assertEquals(String.format(MESSAGE_NO_CONTACTS_ADDED, filename)
+ + String.format(MESSAGE_DUPLICATES_NOT_ADDED, 1)
+ + personListToString(List.of(p)), commandResult.toString());
+ }
+
+ @Test
+ void execute_invalidFieldsFound_success() throws Exception {
+ List toAdd = List.of();
+ String filename = "data.csv";
+ List invalidFields = List.of("foofoo", "hehe");
+
+ Model model = new ModelManager();
+
+ CommandResult commandResult = new ImportCommand(toAdd, filename, invalidFields).execute(model);
+
+ assertEquals(String.format(MESSAGE_NO_CONTACTS_ADDED, filename)
+ + String.format(MESSAGE_INVALID_FIELDS, 2)
+ + String.join("\n", invalidFields), commandResult.toString());
+ }
+
+ @Test
+ void execute_tagTooLong_success() throws Exception {
+ Person p = new PersonBuilder()
+ .withTags("joiwjfaioewfjoawefjowefjoiwejfowjfwijojoaewfjaiowfjiaeowjfaowjfiowf").build();
+ List toAdd = List.of(p);
+ String filename = "data.csv";
+ List invalidFields = List.of();
+
+ Model model = new ModelManager();
+
+ CommandResult commandResult = new ImportCommand(toAdd, filename, invalidFields).execute(model);
+
+ assertEquals(String.format(MESSAGE_NO_CONTACTS_ADDED, filename)
+ + String.format(MESSAGE_FOUND_TAGS_TOO_LONG, 1)
+ + personListToString(toAdd), commandResult.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
index 435ff1f7275..52f02262853 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
@@ -10,6 +10,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.TaskList;
import seedu.address.model.UserPrefs;
/**
@@ -22,8 +23,8 @@ public class ListCommandTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new TaskList());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList());
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/ListTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTaskCommandTest.java
new file mode 100644
index 00000000000..f4ed9385658
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListTaskCommandTest.java
@@ -0,0 +1,61 @@
+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.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+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;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for ListCommand.
+ */
+public class ListTaskCommandTest {
+
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(new AddressBook(), new UserPrefs(), getTypicalTaskList());
+ expectedModel = new ModelManager(new AddressBook(), new UserPrefs(), model.getTaskList());
+ }
+
+ @Test
+ public void execute_listIsNotFiltered_showsSameList() {
+ assertCommandSuccess(new ListTaskCommand(""), model,
+ ListTaskCommand.MESSAGE_SUCCESS_NOT_COMPLETED, expectedModel);
+ }
+
+ @Test
+ public void execute_listIsFiltered_showsEverything() {
+ showTaskAtIndex(model, INDEX_FIRST_TASK);
+ assertCommandSuccess(new ListTaskCommand("all"), model,
+ ListTaskCommand.MESSAGE_SUCCESS_ALL, expectedModel);
+ }
+
+ @Test
+ public void equals() {
+ ListTaskCommand listAllTaskCommand = new ListTaskCommand("all");
+ ListTaskCommand listCompleteTaskCommand = new ListTaskCommand("c");
+ ListTaskCommand listIncompleteTaskCommand = new ListTaskCommand("nc");
+
+ // same object -> returns true
+ assertTrue(listAllTaskCommand.equals(listAllTaskCommand));
+
+ // same values -> returns true
+ ListTaskCommand listAllTaskCommandCopy = new ListTaskCommand("all");
+ assertTrue(listAllTaskCommand.equals(listAllTaskCommandCopy));
+
+ // different prefix -> returns false
+ assertFalse(listCompleteTaskCommand.equals(listIncompleteTaskCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/MarkTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/MarkTaskCommandTest.java
new file mode 100644
index 00000000000..640d6184996
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/MarkTaskCommandTest.java
@@ -0,0 +1,67 @@
+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.INDEX_FIRST_TASK;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+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;
+
+public class MarkTaskCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ Task taskToMark = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased());
+ MarkTaskCommand markTaskCommand = new MarkTaskCommand(INDEX_FIRST_TASK);
+
+ String expectedMessage = String.format(MarkTaskCommand.MESSAGE_MARK_TASK_SUCCESS, taskToMark);
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getTaskList());
+ expectedModel.markTask(taskToMark);
+
+ assertCommandSuccess(markTaskCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1);
+ MarkTaskCommand markTaskCommand = new MarkTaskCommand(outOfBoundIndex);
+
+ assertCommandFailure(markTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ MarkTaskCommand markFirstCommand = new MarkTaskCommand(INDEX_FIRST_TASK);
+ MarkTaskCommand markSecondCommand = new MarkTaskCommand(INDEX_SECOND_TASK);
+
+ // same object -> returns true
+ assertTrue(markFirstCommand.equals(markFirstCommand));
+
+ // same values -> returns true
+ MarkTaskCommand markFirstCommandCopy = new MarkTaskCommand(INDEX_FIRST_TASK);
+ assertTrue(markFirstCommand.equals(markFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(markFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(markFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(markFirstCommand.equals(markSecondCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/UnassignCommandTest.java b/src/test/java/seedu/address/logic/commands/UnassignCommandTest.java
new file mode 100644
index 00000000000..0d99f79a343
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UnassignCommandTest.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+import org.junit.jupiter.api.Test;
+
+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.TaskList;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
+
+class UnassignCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ void execute_unassignExistingContact_success() throws Exception {
+ Person person = model.getFilteredPersonList().get(0);
+ Task taskToEdit = model.getFilteredTaskList().get(0);
+
+ Task updatedTask = new Task(taskToEdit.getName(), taskToEdit.getDateTime(),
+ taskToEdit.getPeople(), taskToEdit.getTags(), taskToEdit.getLink(), taskToEdit.isTaskMark());
+ updatedTask.addPerson(person);
+ TaskList updatedTasks = new TaskList(model.getTaskList());
+ updatedTasks.setTask(taskToEdit, updatedTask);
+
+ UnassignCommand unassignCommand =
+ new UnassignCommand(Index.fromZeroBased(0), Index.fromZeroBased(0));
+
+ Model m = new ModelManager(
+ new AddressBook(model.getAddressBook()), new UserPrefs(), updatedTasks);
+
+ String expectedMessage = String.format(UnassignCommand.NO_PERSON_ASSIGN,
+ person.getName(), person.getPhone(), updatedTask);
+
+ assertCommandSuccess(unassignCommand, m, expectedMessage, model);
+ }
+
+ @Test
+ void execute_unassignNonexistingContact_failure() {
+ Person personToRemove = model.getFilteredPersonList().get(0);
+ Task taskToEdit = model.getFilteredTaskList().get(0);
+ taskToEdit.removePerson(personToRemove);
+
+ assertFalse(taskToEdit.getPeople().contains(personToRemove));
+
+ UnassignCommand unassignCommand =
+ new UnassignCommand(Index.fromZeroBased(0), Index.fromZeroBased(0));
+
+ assertCommandFailure(unassignCommand, model, UnassignCommand.MESSAGE_PERSON_NOT_IN_TASK);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/UnmarkTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/UnmarkTaskCommandTest.java
new file mode 100644
index 00000000000..a3c4249834e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UnmarkTaskCommandTest.java
@@ -0,0 +1,67 @@
+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.INDEX_FIRST_TASK;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+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;
+
+public class UnmarkTaskCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ Task taskToUnmark = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased());
+ UnmarkTaskCommand unmarkTaskCommand = new UnmarkTaskCommand(INDEX_FIRST_TASK);
+
+ String expectedMessage = String.format(UnmarkTaskCommand.MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark);
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getTaskList());
+ expectedModel.unmarkTask(taskToUnmark);
+
+ assertCommandSuccess(unmarkTaskCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1);
+ UnmarkTaskCommand unmarkTaskCommand = new UnmarkTaskCommand(outOfBoundIndex);
+
+ assertCommandFailure(unmarkTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ UnmarkTaskCommand unmarkFirstCommand = new UnmarkTaskCommand(INDEX_FIRST_TASK);
+ UnmarkTaskCommand unmarkSecondCommand = new UnmarkTaskCommand(INDEX_SECOND_TASK);
+
+ // same object -> returns true
+ assertTrue(unmarkFirstCommand.equals(unmarkFirstCommand));
+
+ // same values -> returns true
+ UnmarkTaskCommand unmarkFirstCommandCopy = new UnmarkTaskCommand(INDEX_FIRST_TASK);
+ assertTrue(unmarkFirstCommand.equals(unmarkFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(unmarkFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(unmarkFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(unmarkFirstCommand.equals(unmarkSecondCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ViewCommandTest.java b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java
new file mode 100644
index 00000000000..fc71a9bd9f0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java
@@ -0,0 +1,72 @@
+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.testutil.TypicalAssignedTasks.getTypicalAssignedTaskList;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+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.TaskList;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.PersonContainInTask;
+
+
+public class ViewCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalTaskList());
+
+ private Model assignedModel =
+ new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalAssignedTaskList());
+
+ @Test
+ void execute_zeroAssigneesInTask_noPersonFound() {
+ Model expectedModel =
+ new ModelManager(model.getAddressBook(), new UserPrefs(), new TaskList(model.getTaskList()));
+
+ Index taskIndex = Index.fromZeroBased(0);
+
+ String expectedMessage = String.format(ViewCommand.NO_CONTACT_ASSIGN, taskIndex.getOneBased());
+
+ ViewCommand viewCommand = new ViewCommand(taskIndex);
+
+ assertCommandSuccess(viewCommand, model, expectedMessage, expectedModel);
+ }
+
+
+ @Test
+ void execute_assigneesInTask_personFound() throws CommandException {
+ Model expectedModel = new ModelManager(
+ assignedModel.getAddressBook(), new UserPrefs(), new TaskList(assignedModel.getTaskList()));
+
+ List assignedList = expectedModel.getFilteredTaskList().get(0).getPeople();
+ PersonContainInTask pred = new PersonContainInTask(assignedList);
+
+ ViewCommand viewCommand = new ViewCommand(Index.fromZeroBased(0));
+
+ viewCommand.execute(assignedModel);
+
+ assertEquals(assignedModel.getFilteredPersonList(), expectedModel.getFilteredPersonList().filtered(pred));
+
+ }
+
+ @Test
+ void execute_userInputIndex_greaterThanList() {
+ ViewCommand viewCommand = new ViewCommand(Index.fromZeroBased(100));
+
+ CommandException thrown = Assertions.assertThrows(CommandException.class, ()-> {
+ viewCommand.execute(model);
+ });
+ assertEquals(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX, thrown.getMessage());
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5cf487d7ebb..25e84a046c8 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -1,29 +1,30 @@
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.INVALID_EMAIL_DESC_LONG;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_LONG;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_SHORT;
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.USERNAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.USERNAME_DESC_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.commands.CommandTestUtil.VALID_USERNAME_BOB;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GIT_USERNAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -32,110 +33,117 @@
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));
+ + USERNAME_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));
+ + USERNAME_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));
+ + USERNAME_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));
+ assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_AMY
+ + USERNAME_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
+ assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags));
}
@Test
public void parse_optionalFieldsMissing_success() {
- // zero tags
+ // Phone number lesser than 3 digits
+ assertParseFailure(parser, NAME_DESC_AMY + INVALID_PHONE_SHORT + EMAIL_DESC_AMY + USERNAME_DESC_AMY
+ + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+ // Phone number lesser than 15 digits
+ assertParseFailure(parser, NAME_DESC_AMY + INVALID_PHONE_LONG + EMAIL_DESC_AMY + USERNAME_DESC_AMY
+ + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+ // Email longer than 54 characters
+ assertParseFailure(parser, NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC_LONG + USERNAME_DESC_AMY
+ + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
+ }
+
+ @Test
+ public void parse_incorrectFields_failure() {
+ // Phone number too short
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
- assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + USERNAME_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);
+ String missingNameMessage = missingParametersErrorMessage(false, true,
+ true, true).toString() + "\n" + AddCommand.MESSAGE_USAGE;
+ assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingNameMessage));
// missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
+ String missingPhoneMessage = missingParametersErrorMessage(true, false,
+ true, true).toString() + "\n" + AddCommand.MESSAGE_USAGE;
+ assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingPhoneMessage));
// missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
+ String missingEmailMessage = missingParametersErrorMessage(true, true,
+ false, true).toString() + "\n" + AddCommand.MESSAGE_USAGE;
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + USERNAME_DESC_BOB,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingEmailMessage));
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ // missing username prefix
+ String missingUsernameMessage = missingParametersErrorMessage(true, true,
+ true, false).toString() + "\n" + AddCommand.MESSAGE_USAGE;
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_USERNAME_BOB,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingUsernameMessage));
// all prefixes missing
- assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ String missingAllMessage = missingParametersErrorMessage(false, false,
+ false, false).toString() + "\n" + AddCommand.MESSAGE_USAGE;
+ assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_USERNAME_BOB,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingAllMessage));
}
- @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));
+ private StringBuffer missingParametersErrorMessage(boolean hasName, boolean hasPhone,
+ boolean hasEmail, boolean hasGit) {
+ String errorString = "Missing/Invalid parameters: ";
+ if (!hasName) {
+ errorString += PREFIX_NAME + ", ";
+ }
+ if (!hasPhone) {
+ errorString += PREFIX_PHONE + ", ";
+ }
+ if (!hasEmail) {
+ errorString += PREFIX_EMAIL + ", ";
+ }
+ if (!hasGit) {
+ errorString += PREFIX_GIT_USERNAME + ", ";
+ }
+ StringBuffer sb = new StringBuffer(errorString);
+ sb.delete(sb.length() - 2, sb.length() - 1); //Deleting last comma
+ return sb;
}
+
}
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..529c44d4bab
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddTaskCommandParserTest.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.commons.core.Messages.MESSAGE_INVALID_DATETIME;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_INTERVAL;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_RECURRENCE;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_RECURRENCE_INDEX;
+import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_DAY;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_LEAPYEAR;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_MONTH;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TASK_NAME_LONG;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TASK_NAME_SHORT;
+import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKA_PLUS_ONE_DAY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CHORES;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_RECURRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASKNAME;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalTasks.BRUSH_TEETH;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.model.task.Task;
+import seedu.address.model.util.SampleDataUtil;
+import seedu.address.testutil.TaskBuilder;
+
+
+public class AddTaskCommandParserTest {
+ private AddTaskCommandParser parser = new AddTaskCommandParser();
+ private final String dateTimePattern = "dd-MM-yyyy HHmm";
+ private final SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(dateTimePattern);
+
+ @Test
+ public void parseValidFieldsPresentSuccess() throws Exception {
+
+ //Completely valid fields
+ assertParseSuccess(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " "
+ + PREFIX_TAG + VALID_TAG_CHORES, new AddTaskCommand(BRUSH_TEETH));
+
+ //Whitespace in front of name
+ assertParseSuccess(parser, " " + PREFIX_TASKNAME + " " + VALID_NAME_TASKB + DATETIME_DESC_TASKA + " "
+ + PREFIX_TAG + VALID_TAG_CHORES, new AddTaskCommand(BRUSH_TEETH));
+
+ //Valid fields of only TASKNAME and DATETIME
+ Task simpleTask = new TaskBuilder().withTaskName(VALID_NAME_TASKB)
+ .withDateTime(LocalDateTime.of(2050, 12, 15, 21, 0)).build();
+ assertParseSuccess(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA, new AddTaskCommand(simpleTask));
+
+ //Ability to parse all fields including recurrence
+ AddTaskCommand testCommand = new AddTaskCommand(VALID_NAME_TASKB,
+ LocalDateTime.of(2050, 12, 15, 21, 0),
+ LocalDateTime.of(2050, 12, 16, 21, 0),
+ SampleDataUtil.getTagSet(VALID_TAG_CHORES), null, 2, 2);
+
+ assertParseSuccess(parser, " " + NAME_DESC_TASKB + DATETIME_DESC_TASKA
+ + " " + VALID_DATETIME_TASKA_PLUS_ONE_DAY + " " + PREFIX_TAG + VALID_TAG_CHORES + " "
+ + PREFIX_RECURRING + "2 2", testCommand);
+ }
+
+ @Test
+ public void parseCompulsoryFieldMissingFailure() {
+ //Missing Taskname
+ assertParseFailure(parser, DATETIME_DESC_TASKA, String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ missingTaskNameErrorMessage() + "\n" + AddTaskCommand.MESSAGE_USAGE));
+
+ //Missing DateTime
+ assertParseFailure(parser, NAME_DESC_TASKA, String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ missingDateTimeErrorMessage() + "\n" + AddTaskCommand.MESSAGE_USAGE));
+
+ //Missing both
+ assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ missingBothErrorMessage() + "\n" + AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parseInvalidFieldsPresentFailure() throws Exception {
+
+ //Task name shorter than 3 characters
+ assertParseFailure(parser, " " + PREFIX_TASKNAME + " " + INVALID_TASK_NAME_SHORT + DATETIME_DESC_TASKA + " "
+ + PREFIX_TAG + VALID_TAG_CHORES, Task.NAME_LENGTH_ERROR);
+
+ //Task name longer than 50 characters
+ assertParseFailure(parser, " " + PREFIX_TASKNAME + " " + INVALID_TASK_NAME_LONG + DATETIME_DESC_TASKA + " "
+ + PREFIX_TAG + VALID_TAG_CHORES, Task.NAME_LENGTH_ERROR);
+
+ }
+
+ @Test
+ public void invalidDateTimeFailure() {
+ String dateTimeErrorMessage = String.format(MESSAGE_INVALID_DATETIME, AddTaskCommand.MESSAGE_USAGE);
+
+ // Invalid datetime format
+ assertParseFailure(parser, NAME_DESC_TASKB + " " + INVALID_DATETIME_FORMAT, dateTimeErrorMessage);
+
+ // Invalid day
+ assertParseFailure(parser, NAME_DESC_TASKB + " " + INVALID_DATETIME_DAY, dateTimeErrorMessage);
+
+ // Invalid month
+ assertParseFailure(parser, NAME_DESC_TASKB + " " + INVALID_DATETIME_MONTH, dateTimeErrorMessage);
+
+ // Invalid leap year
+ assertParseFailure(parser, NAME_DESC_TASKB + " " + INVALID_DATETIME_LEAPYEAR, dateTimeErrorMessage);
+ }
+
+ @Test
+ public void invalidRecurrenceFailure() {
+ //Interval of 0
+ assertParseFailure(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " " + PREFIX_RECURRING + "0 2",
+ String.format(MESSAGE_INVALID_RECURRENCE_INDEX, AddTaskCommand.MESSAGE_USAGE));
+
+ //Negative interval
+ assertParseFailure(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " " + PREFIX_RECURRING + "-2 2",
+ String.format(MESSAGE_INVALID_RECURRENCE_INDEX, AddTaskCommand.MESSAGE_USAGE));
+
+ //Recurrence of 0
+ assertParseFailure(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " " + PREFIX_RECURRING + "2 0",
+ String.format(MESSAGE_INVALID_RECURRENCE_INDEX, AddTaskCommand.MESSAGE_USAGE));
+
+ //Negative Recurrence
+ assertParseFailure(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " " + PREFIX_RECURRING + "2 -2",
+ String.format(MESSAGE_INVALID_RECURRENCE_INDEX, AddTaskCommand.MESSAGE_USAGE));
+
+ //Non Integer Interval
+ assertParseFailure(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " " + PREFIX_RECURRING + "a 2",
+ String.format(MESSAGE_INVALID_INTERVAL, AddTaskCommand.MESSAGE_USAGE));
+
+ //Non Integer Recurrence
+ assertParseFailure(parser, NAME_DESC_TASKB + DATETIME_DESC_TASKA + " " + PREFIX_RECURRING + "2 $",
+ String.format(MESSAGE_INVALID_RECURRENCE, AddTaskCommand.MESSAGE_USAGE));
+
+ }
+
+
+ /**
+ * Returns missing TaskName error message as thrown in multiple Parser classes.
+ *
+ * @return Error message
+ */
+ private String missingTaskNameErrorMessage() {
+ return "Missing/Invalid parameters: tn/ ";
+ }
+
+ /**
+ * Returns missing DateTime error message as thrown in multiple Parser classes.
+ *
+ * @return Error message
+ */
+ private String missingDateTimeErrorMessage() {
+ return "Missing/Invalid parameters: dt/ ";
+ }
+
+ /**
+ * Returns missing TaskName and DateTime error message as thrown in multiple Parser classes.
+ *
+ * @return Error message
+ */
+ private String missingBothErrorMessage() {
+ return "Missing/Invalid parameters: tn/, dt/ ";
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index d9659205b57..2b97df3badc 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -4,9 +4,18 @@
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_UNKNOWN_COMMAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKA_STRING;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKB_STRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FILEPATH;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -14,32 +23,59 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.commands.AssignCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteTaskCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.EditTaskCommand;
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FilterByDateCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindTaskCommand;
+import seedu.address.logic.commands.GenerateEmailsCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ImportCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListTaskCommand;
+import seedu.address.logic.commands.MarkTaskCommand;
+import seedu.address.logic.commands.UnassignCommand;
+import seedu.address.logic.commands.UnmarkTaskCommand;
+import seedu.address.logic.commands.ViewCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.TaskBetweenDatesPredicate;
+import seedu.address.model.task.TaskNameContainsKeywordsPredicate;
import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.EditTaskDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
import seedu.address.testutil.PersonUtil;
+import seedu.address.testutil.TaskBuilder;
+import seedu.address.testutil.TaskUtil;
public class AddressBookParserTest {
private final AddressBookParser parser = new AddressBookParser();
@Test
- public void parseCommand_add() throws Exception {
+ public void parseCommand_addc() throws Exception {
Person person = new PersonBuilder().build();
AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person));
assertEquals(new AddCommand(person), command);
}
+ @Test
+ public void parseCommand_addt() throws Exception {
+ Task task = new TaskBuilder().build();
+ AddTaskCommand command = (AddTaskCommand) parser.parseCommand(TaskUtil.getAddCommand(task));
+ assertEquals(new AddTaskCommand(task), command);
+ }
+
@Test
public void parseCommand_clear() throws Exception {
assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand);
@@ -47,14 +83,37 @@ public void parseCommand_clear() throws Exception {
}
@Test
- public void parseCommand_delete() throws Exception {
+ public void parseCommand_deletec() throws Exception {
DeleteCommand command = (DeleteCommand) parser.parseCommand(
DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased());
assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command);
}
@Test
- public void parseCommand_edit() throws Exception {
+ public void parseCommand_deletet() throws Exception {
+ DeleteTaskCommand command = (DeleteTaskCommand) parser.parseCommand(
+ DeleteTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased());
+ assertEquals(new DeleteTaskCommand(INDEX_FIRST_TASK), command);
+ }
+
+ @Test
+ public void parseCommand_assign() throws Exception {
+ AssignCommand command = (AssignCommand) parser.parseCommand(
+ AssignCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased()
+ + " " + PREFIX_PERSON + INDEX_FIRST_PERSON.getOneBased());
+ assertEquals(new AssignCommand(INDEX_FIRST_TASK, INDEX_FIRST_PERSON), command);
+ }
+
+ @Test
+ public void parseCommand_unassign() throws Exception {
+ UnassignCommand command = (UnassignCommand) parser.parseCommand(
+ UnassignCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased()
+ + " " + PREFIX_PERSON + INDEX_FIRST_PERSON.getOneBased());
+ assertEquals(new UnassignCommand(INDEX_FIRST_TASK, INDEX_FIRST_PERSON), command);
+ }
+
+ @Test
+ public void parseCommand_editc() throws Exception {
Person person = new PersonBuilder().build();
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build();
EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " "
@@ -62,6 +121,36 @@ public void parseCommand_edit() throws Exception {
assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
}
+ @Test
+ public void parseCommand_editt() throws Exception {
+ Task task = new TaskBuilder().build();
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder(task).build();
+ EditTaskCommand command = (EditTaskCommand) parser.parseCommand(EditTaskCommand.COMMAND_WORD + " "
+ + INDEX_FIRST_TASK.getOneBased() + " " + TaskUtil.getEditTaskDescriptorDetails(descriptor));
+ assertEquals(new EditTaskCommand(INDEX_FIRST_TASK, descriptor), command);
+ }
+
+ @Test
+ public void parseCommand_view() throws Exception {
+ ViewCommand command = (ViewCommand) parser.parseCommand(
+ ViewCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased());
+ assertEquals(new ViewCommand(INDEX_FIRST_TASK), command);
+ }
+
+ @Test
+ public void parseCommand_mark() throws Exception {
+ MarkTaskCommand command = (MarkTaskCommand) parser.parseCommand(
+ MarkTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased());
+ assertEquals(new MarkTaskCommand(INDEX_FIRST_TASK), command);
+ }
+
+ @Test
+ public void parseCommand_unmark() throws Exception {
+ UnmarkTaskCommand command = (UnmarkTaskCommand) parser.parseCommand(
+ UnmarkTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased());
+ assertEquals(new UnmarkTaskCommand(INDEX_FIRST_TASK), command);
+ }
+
@Test
public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand);
@@ -69,13 +158,21 @@ public void parseCommand_exit() throws Exception {
}
@Test
- public void parseCommand_find() throws Exception {
+ public void parseCommand_findc() 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);
}
+ @Test
+ public void parseCommand_findt() throws Exception {
+ List keywords = Arrays.asList("meeting", "consultation", "cs2103");
+ FindTaskCommand command = (FindTaskCommand) parser.parseCommand(
+ FindTaskCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
+ assertEquals(new FindTaskCommand(new TaskNameContainsKeywordsPredicate(keywords)), command);
+ }
+
@Test
public void parseCommand_help() throws Exception {
assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand);
@@ -83,11 +180,34 @@ public void parseCommand_help() throws Exception {
}
@Test
- public void parseCommand_list() throws Exception {
+ public void parseCommand_listc() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand);
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
}
+ @Test
+ public void parseCommand_listt() throws Exception {
+ assertTrue(parser.parseCommand(ListTaskCommand.COMMAND_WORD + " all/") instanceof ListTaskCommand);
+ assertTrue(parser.parseCommand(ListTaskCommand.COMMAND_WORD + " c/") instanceof ListTaskCommand);
+ assertTrue(parser.parseCommand(ListTaskCommand.COMMAND_WORD + " nc/") instanceof ListTaskCommand);
+ }
+
+ @Test
+ public void parseCommand_generateEmail() throws Exception {
+ GenerateEmailsCommand command = (GenerateEmailsCommand) parser.parseCommand(
+ GenerateEmailsCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK.getOneBased());
+ assertEquals(new GenerateEmailsCommand(INDEX_FIRST_TASK), command);
+ }
+
+ @Test
+ public void parseCommand_import() throws Exception {
+ String filepath = Path.of("src", "test", "data", "ImportTestData", "empty.csv").toString();
+ ImportCommand command = (ImportCommand) parser.parseCommand(
+ ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH
+ + filepath);
+ assertEquals(new ImportCommand(List.of(), filepath, List.of()), command);
+ }
+
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
@@ -98,4 +218,27 @@ public void parseCommand_unrecognisedInput_throwsParseException() {
public void parseCommand_unknownCommand_throwsParseException() {
assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand"));
}
+
+ @Test
+ public void parseCommand_findTaskWithDate() throws Exception {
+ String userInput = FilterByDateCommand.COMMAND_WORD
+ + " "
+ + PREFIX_DATETIME
+ + VALID_DATETIME_TASKA_STRING
+ + ","
+ + VALID_DATETIME_TASKB_STRING;
+ FilterByDateCommand command = (FilterByDateCommand) parser.parseCommand(userInput);
+ assertEquals(new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_TASKB, VALID_DATETIME_TASKA))),
+ command);
+ }
+
+ @Test
+ public void parseCommand_findTask() throws Exception {
+ String userInput = FindTaskCommand.COMMAND_WORD + " Brush students";
+ FindTaskCommand command = (FindTaskCommand) parser.parseCommand(userInput);
+ assertEquals(new FindTaskCommand(new TaskNameContainsKeywordsPredicate(
+ Arrays.asList("Brush", "students"))),
+ command);
+ }
}
diff --git a/src/test/java/seedu/address/logic/parser/AssignCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AssignCommandParserTest.java
new file mode 100644
index 00000000000..4016b684499
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AssignCommandParserTest.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON;
+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 static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AssignCommand;
+
+class AssignCommandParserTest {
+ private AssignCommandParser parser = new AssignCommandParser();
+
+ @Test
+ void parse_allFieldsPresent_success() {
+
+ Index firstTask = INDEX_FIRST_TASK;
+ Index secondPerson = INDEX_SECOND_PERSON;
+
+ String userInput = firstTask.getOneBased() + " " + PREFIX_PERSON + secondPerson.getOneBased();
+ Logger.getLogger("a").log(Level.INFO, userInput);
+ assertParseSuccess(parser, userInput,
+ new AssignCommand(firstTask, secondPerson));
+ }
+
+ @Test
+ void parse_compulsoryFieldMissing_failure() {
+ String missingParameter = "Missing/Invalid parameters: p/\n";
+ String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingParameter
+ + AssignCommand.MESSAGE_USAGE);
+
+ assertParseFailure(parser, String.valueOf(INDEX_FIRST_TASK.getOneBased()), expectedMessage);
+ }
+
+ @Test
+ void parse_noInput_failure() {
+ String missingParameter = "Missing/Invalid parameters: p/\n";
+ String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, missingParameter
+ + AssignCommand.MESSAGE_USAGE);
+
+ assertParseFailure(parser, "", expectedMessage);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
index 27eaec84450..ef8869924a9 100644
--- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
@@ -1,6 +1,6 @@
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_PERSON_DISPLAYED_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_PERSON;
@@ -27,6 +27,6 @@ public void parse_validArgs_returnsDeleteCommand() {
@Test
public void parse_invalidArgs_throwsParseException() {
- assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, "a", MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
}
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..4487a0b2b0d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_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;
+
+/**
+ * As we are only doing white-box testing, our test cases do not cover path variations
+ * outside of the DeleteTaskCommand code. For example, inputs "1" and "1 abc" take the
+ * same path through the DeleteTaskCommand, 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 DeleteTaskCommandParserTest {
+
+ private DeleteTaskCommandParser parser = new DeleteTaskCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsDeleteCommand() {
+ assertParseSuccess(parser, "1", new DeleteTaskCommand(INDEX_FIRST_TASK));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "a", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index 2ff31522486..1a45707c142 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -1,47 +1,36 @@
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.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
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_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 static seedu.address.logic.commands.CommandTestUtil.VALID_USERNAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_USERNAME_BOB;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GIT_USERNAME;
+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.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 java.util.HashSet;
+import java.util.Set;
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.GitUsername;
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 {
@@ -53,159 +42,90 @@ public class EditCommandParserTest {
private EditCommandParser parser = new EditCommandParser();
@Test
- public void parse_missingParts_failure() {
+ public void parse_missingIndex_failure() {
// no index specified
- assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT);
-
- // no field specified
- assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED);
+ assertParseFailure(parser, " ", MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- // 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);
- }
+ public void parse_invalidIndex_failure() {
+ //negative index without parameters
+ assertParseFailure(parser, "-1 ", MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- @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);
+ //negative index with parameters
+ assertParseFailure(parser, "-1 " + PREFIX_NAME + VALID_USERNAME_BOB,
+ MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
- @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_noParameters_failure() {
+ assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED);
}
@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);
+ public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- assertParseSuccess(parser, userInput, expectedCommand);
- }
+ // zero index
+ assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- @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);
- }
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- @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);
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@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);
+ public void parse_validInput_success() {
+ // With name
+ EditPersonDescriptor descriptorWithName = new EditPersonDescriptor();
+ descriptorWithName.setName(new Name(VALID_NAME_BOB));
+ assertParseSuccess(parser, "1 " + PREFIX_NAME + VALID_NAME_BOB,
+ new EditCommand(Index.fromZeroBased(0), descriptorWithName));
+
+ //With phone
+ EditPersonDescriptor descriptorWithPhone = new EditPersonDescriptor();
+ descriptorWithPhone.setPhone(new Phone(VALID_PHONE_AMY));
+ assertParseSuccess(parser, "1 " + PREFIX_PHONE + VALID_PHONE_AMY,
+ new EditCommand(Index.fromZeroBased(0), descriptorWithPhone));
+
+ //With email
+ EditPersonDescriptor descriptorWithEmail = new EditPersonDescriptor();
+ descriptorWithEmail.setEmail(new Email(VALID_EMAIL_BOB));
+ assertParseSuccess(parser, "1 " + PREFIX_EMAIL + VALID_EMAIL_BOB,
+ new EditCommand(Index.fromZeroBased(0), descriptorWithEmail));
+
+ //With username
+ EditPersonDescriptor descriptorWithUsername = new EditPersonDescriptor();
+ descriptorWithUsername.setUsername(new GitUsername(VALID_USERNAME_AMY));
+ assertParseSuccess(parser, "1 " + PREFIX_GIT_USERNAME + VALID_USERNAME_AMY,
+ new EditCommand(Index.fromZeroBased(0), descriptorWithUsername));
+
+ //With tag
+ EditPersonDescriptor descriptorWithTag = new EditPersonDescriptor();
+ Set set = new HashSet<>();
+ set.add(new Tag(VALID_TAG_FRIEND));
+ set.add(new Tag(VALID_TAG_HUSBAND));
+ descriptorWithTag.setTags(set);
+ assertParseSuccess(parser, "3 " + PREFIX_TAG + VALID_TAG_FRIEND + " " + PREFIX_TAG + VALID_TAG_HUSBAND,
+ new EditCommand(Index.fromZeroBased(2), descriptorWithTag));
+
+ //With all valid
+ EditPersonDescriptor descriptorWithAll = new EditPersonDescriptor();
+ descriptorWithAll.setName(new Name(VALID_NAME_BOB));
+ descriptorWithAll.setPhone(new Phone(VALID_PHONE_AMY));
+ descriptorWithAll.setEmail(new Email(VALID_EMAIL_BOB));
+ descriptorWithAll.setUsername(new GitUsername(VALID_USERNAME_AMY));
+ Set allSet = new HashSet<>();
+ allSet.add(new Tag(VALID_TAG_FRIEND));
+ allSet.add(new Tag(VALID_TAG_HUSBAND));
+ descriptorWithAll.setTags(allSet);
+ assertParseSuccess(parser, "2 " + PREFIX_NAME + VALID_NAME_BOB + " " + PREFIX_PHONE + VALID_PHONE_AMY
+ + " " + PREFIX_EMAIL + VALID_EMAIL_BOB + " " + PREFIX_GIT_USERNAME + VALID_USERNAME_AMY + " "
+ + PREFIX_TAG + VALID_TAG_FRIEND + " " + PREFIX_TAG + VALID_TAG_HUSBAND,
+ new EditCommand(Index.fromZeroBased(1), descriptorWithAll));
}
- @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/EditTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditTaskCommandParserTest.java
new file mode 100644
index 00000000000..659f8b2b07e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/EditTaskCommandParserTest.java
@@ -0,0 +1,146 @@
+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_DATETIME;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+import static seedu.address.commons.core.Messages.MESSAGE_NEED_AT_LEAST_ONE_VALID_PARAMETER;
+import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_DAY;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_LEAPYEAR;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_MONTH;
+import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_TASKA;
+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 static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK;
+import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_TASK;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTaskCommand;
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
+import seedu.address.testutil.EditTaskDescriptorBuilder;
+
+public class EditTaskCommandParserTest {
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE);
+ private final String errorMessage = String.format(MESSAGE_NEED_AT_LEAST_ONE_VALID_PARAMETER,
+ EditTaskCommand.MESSAGE_USAGE);
+
+ private EditTaskCommandParser parser = new EditTaskCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+
+ // no index specified
+ assertParseFailure(parser, VALID_NAME_TASKA, errorMessage);
+
+ // no field specified
+ assertParseFailure(parser, "1", errorMessage);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", errorMessage);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5" + NAME_DESC_TASKA, MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+
+ // zero index
+ assertParseFailure(parser, "0" + NAME_DESC_TASKA, MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", errorMessage);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", errorMessage);
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+ String dateTimeErrorMessage = String.format(MESSAGE_INVALID_DATETIME, EditTaskCommand.MESSAGE_USAGE);
+
+ // Invalid datetime format
+ assertParseFailure(parser, "1 " + INVALID_DATETIME_FORMAT, dateTimeErrorMessage);
+
+ // Invalid day
+ assertParseFailure(parser, "1 " + INVALID_DATETIME_DAY, dateTimeErrorMessage);
+
+ // Invalid month
+ assertParseFailure(parser, "1 " + INVALID_DATETIME_MONTH, dateTimeErrorMessage);
+
+ // Invalid leap year
+ assertParseFailure(parser, "1 " + INVALID_DATETIME_LEAPYEAR, dateTimeErrorMessage);
+
+
+ }
+
+ @Test
+ public void parse_allFieldsSpecified_success() {
+ Index targetIndex = INDEX_SECOND_TASK;
+ String userInput = targetIndex.getOneBased() + NAME_DESC_TASKA + DATETIME_DESC_TASKB + TAG_DESC_TASKA;
+
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKA)
+ .withDateTime(VALID_DATETIME_TASKB).withTags(VALID_TAG_TASKA).build();
+ EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_someFieldsSpecified_success() {
+ Index targetIndex = INDEX_FIRST_TASK;
+ String userInput = targetIndex.getOneBased() + NAME_DESC_TASKA + DATETIME_DESC_TASKB;
+
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKA)
+ .withDateTime(VALID_DATETIME_TASKB).build();
+ EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_oneFieldSpecified_success() {
+ // name
+ Index targetIndex = INDEX_THIRD_TASK;
+ String userInput = targetIndex.getOneBased() + NAME_DESC_TASKA;
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKA).build();
+ EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // date time
+ userInput = targetIndex.getOneBased() + DATETIME_DESC_TASKA;
+ descriptor = new EditTaskDescriptorBuilder().withDateTime(VALID_DATETIME_TASKA).build();
+ expectedCommand = new EditTaskCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // tags
+ userInput = targetIndex.getOneBased() + TAG_DESC_TASKA;
+ descriptor = new EditTaskDescriptorBuilder().withTags(VALID_TAG_TASKA).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() + DATETIME_DESC_TASKA + DATETIME_DESC_TASKA
+ + TAG_DESC_TASKA + DATETIME_DESC_TASKA + TAG_DESC_TASKA + NAME_DESC_TASKA;
+
+ EditTaskDescriptor descriptor = new EditTaskDescriptorBuilder().withTaskName(VALID_NAME_TASKA)
+ .withDateTime(VALID_DATETIME_TASKA).withTags(VALID_TAG_TASKA).build();
+ EditTaskCommand expectedCommand = new EditTaskCommand(targetIndex, descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/FilterByDateTimeParserTest.java b/src/test/java/seedu/address/logic/parser/FilterByDateTimeParserTest.java
new file mode 100644
index 00000000000..09822a6a3fe
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FilterByDateTimeParserTest.java
@@ -0,0 +1,143 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.ERROR_MESSAGE_INVALID_FORMAT;
+import static seedu.address.commons.core.Messages.ERROR_MESSAGE_INVALID_PARAMETER;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_DAY;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_LEAPYEAR;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_MONTH;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_VALUE_NOTIME;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_WORDS;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_NOTIME;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_NOTIME_LATER;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_NOTIME_LATER_STRING;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_NOTIME_STRING;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKA;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_TASKA_STRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+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.FilterByDateCommand;
+import seedu.address.logic.commands.FindTaskCommand;
+import seedu.address.model.task.TaskBetweenDatesPredicate;
+
+public class FilterByDateTimeParserTest {
+
+
+ private FilterByDateTimeParser parser = new FilterByDateTimeParser();
+ private String toConcat = "," + VALID_DATETIME_TASKA_STRING;
+
+
+ @Test
+ public void parse_missingParts_failure() {
+
+ // empty args
+ assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindTaskCommand.MESSAGE_USAGE));
+
+ // missing date inputs
+ assertParseFailure(parser, String.valueOf(PREFIX_DATETIME), ERROR_MESSAGE_INVALID_PARAMETER);
+
+ // only 1 date input
+ assertParseFailure(parser, DATETIME_DESC_TASKA,
+ ERROR_MESSAGE_INVALID_PARAMETER);
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+
+
+ // Invalid time format
+ assertParseFailure(parser, INVALID_DATETIME_FORMAT + toConcat,
+ ERROR_MESSAGE_INVALID_PARAMETER);
+
+ // Words instead of numbers
+ assertParseFailure(parser, INVALID_DATETIME_WORDS + toConcat,
+ ERROR_MESSAGE_INVALID_FORMAT);
+
+ // Wrong Day
+ assertParseFailure(parser, INVALID_DATETIME_DAY + toConcat,
+ ERROR_MESSAGE_INVALID_FORMAT);
+
+ // Wrong Month
+ assertParseFailure(parser, INVALID_DATETIME_MONTH + toConcat,
+ ERROR_MESSAGE_INVALID_FORMAT);
+
+ // Wrong Leap Year
+ assertParseFailure(parser, INVALID_DATETIME_LEAPYEAR + toConcat,
+ ERROR_MESSAGE_INVALID_FORMAT);
+
+ }
+
+ @Test
+ public void parse_validValueBothHaveDateTime_success() {
+ String userInput = DATETIME_DESC_TASKA + toConcat;
+ FilterByDateCommand expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_TASKA, VALID_DATETIME_TASKA)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_validValueFirstNoTime_success() {
+
+ // dt/[earlier_date_noTime],[later_date_gotTime]
+ String userInput = PREFIX_DATETIME + VALID_DATETIME_NOTIME_STRING + toConcat;
+ FilterByDateCommand expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_NOTIME, VALID_DATETIME_TASKA)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // dt/[later_date_noTime],[earlier_date_gotTime]
+ userInput = PREFIX_DATETIME + VALID_DATETIME_TASKA_STRING + "," + VALID_DATETIME_NOTIME_STRING;
+ expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_NOTIME, VALID_DATETIME_TASKA)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_validValueSecondNoTime_success() {
+
+ // dt/[earlier_date_gotTime],[later_date_noTime]
+ String userInput = PREFIX_DATETIME + VALID_DATETIME_TASKA_STRING + "," + VALID_DATETIME_NOTIME_LATER_STRING;
+ FilterByDateCommand expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_TASKA, VALID_DATETIME_NOTIME_LATER)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // dt/[later_date_gotTime],[earlier_date_noTime]
+ userInput = PREFIX_DATETIME + VALID_DATETIME_TASKA_STRING + "," + VALID_DATETIME_NOTIME_STRING;
+ expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_NOTIME, VALID_DATETIME_TASKA)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_validValueBothNoTime_success() {
+
+ // dt/[earlier_date_noTime],later_date_noTime]
+ String userInput = PREFIX_DATETIME + VALID_DATETIME_NOTIME_STRING + "," + VALID_DATETIME_NOTIME_LATER_STRING;
+ FilterByDateCommand expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_NOTIME, VALID_DATETIME_NOTIME_LATER)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // dt/[later_date_noTime],[earlier_date_noTime]
+ userInput = PREFIX_DATETIME + VALID_DATETIME_NOTIME_LATER_STRING + "," + VALID_DATETIME_NOTIME_STRING;
+ expectedCommand = new FilterByDateCommand(new TaskBetweenDatesPredicate(
+ Arrays.asList(VALID_DATETIME_NOTIME, VALID_DATETIME_NOTIME_LATER)));
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_invalidValueNoTime_failure() {
+ assertParseFailure(parser, DATETIME_DESC_TASKA + "," + INVALID_DATETIME_VALUE_NOTIME,
+ ERROR_MESSAGE_INVALID_FORMAT);
+ }
+
+
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
new file mode 100644
index 00000000000..acd32422f21
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_NO_KEYWORDS;
+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.FindTaskCommand;
+import seedu.address.model.task.TaskNameContainsKeywordsPredicate;
+
+public class FilterCommandParserTest {
+
+ private FilterCommandParser parser = new FilterCommandParser();
+
+ @Test
+ public void parse_emptyArg_throwsParseException() {
+ assertParseFailure(parser, " ", String.format(MESSAGE_NO_KEYWORDS,
+ FindTaskCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_validArgs_returnsFindTaskCommand() {
+
+ FindTaskCommand expectedCommand =
+ new FindTaskCommand(new TaskNameContainsKeywordsPredicate(Arrays.asList("Brush", "students")));
+
+ // no leading and trailing whitespaces
+ assertParseSuccess(parser, "Brush students", expectedCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, " \n Brush \n \t students \t", expectedCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/GenerateEmailsCommandParserTest.java b/src/test/java/seedu/address/logic/parser/GenerateEmailsCommandParserTest.java
new file mode 100644
index 00000000000..cd32aa51cbc
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/GenerateEmailsCommandParserTest.java
@@ -0,0 +1,35 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+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.index.Index;
+import seedu.address.logic.commands.GenerateEmailsCommand;
+
+class GenerateEmailsCommandParserTest {
+ @Test
+ void parse_validIndex_success() {
+ Index index = Index.fromOneBased(1);
+
+ GenerateEmailsCommandParser parser = new GenerateEmailsCommandParser();
+
+ assertParseSuccess(parser, "1", new GenerateEmailsCommand(index));
+ }
+
+ @Test
+ void parse_nonPositiveIndex_failure() {
+ GenerateEmailsCommandParser parser = new GenerateEmailsCommandParser();
+
+ assertParseFailure(parser, "0", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ @Test
+ void parse_stringIndex_failure() {
+ GenerateEmailsCommandParser parser = new GenerateEmailsCommandParser();
+
+ assertParseFailure(parser, "abcd", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java
new file mode 100644
index 00000000000..5cc0d81ca6e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java
@@ -0,0 +1,123 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FILEPATH;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.logic.parser.ImportCommandParser.ERROR_INVALID_EMAIL;
+import static seedu.address.logic.parser.ImportCommandParser.ERROR_INVALID_GITHUB;
+import static seedu.address.logic.parser.ImportCommandParser.ERROR_INVALID_NAME;
+import static seedu.address.logic.parser.ImportCommandParser.ERROR_INVALID_PHONE;
+import static seedu.address.logic.parser.ImportCommandParser.ERROR_INVALID_TAG;
+import static seedu.address.logic.parser.ImportCommandParser.MESSAGE_CSV_MISSING_HEADERS;
+import static seedu.address.logic.parser.ImportCommandParser.MESSAGE_FILE_DOES_NOT_EXIST;
+import static seedu.address.logic.parser.ImportCommandParser.MESSAGE_FOLDER_SPECIFIED;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ImportCommand;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+class ImportCommandParserTest {
+
+ @Test
+ void parse_validEmptyFile_success() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Path path = Path.of("src", "test", "data", "ImportTestData", "empty.csv");
+
+ String filepath = path.toString();
+
+ assertParseSuccess(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ new ImportCommand(List.of(), filepath, List.of()));
+ }
+
+ @Test
+ void parse_validFileWithEntries_success() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Person a = new PersonBuilder().withName("Alex Tan").withEmail("e0315898@u.nus.edu").withUsername("alexcoder")
+ .withPhone("97013404").withTags("Student", "Lab 14C").build();
+
+ Person b = new PersonBuilder().withName("Brad Tay").withEmail("e03158448@u.nus.edu").withUsername("braddytay")
+ .withPhone("97553402").withTags("TA", "Lab 12F").build();
+
+ Path path = Path.of("src", "test", "data", "ImportTestData", "importTest.csv");
+
+ String filepath = path.toString();
+
+ assertParseSuccess(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ new ImportCommand(List.of(a, b), filepath, List.of()));
+ }
+
+ @Test
+ void parse_missingHeaders_failure() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Path path = Path.of("src", "test", "data", "ImportTestData", "missingHeaders.csv");
+
+ String filepath = path.toString();
+
+ assertParseFailure(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ String.format(MESSAGE_CSV_MISSING_HEADERS, filepath));
+ }
+
+ @Test
+ void parse_missingFile_failure() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Path path = Path.of("foo", "bar.csv");
+
+ String filepath = path.toString();
+
+ assertParseFailure(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ String.format(MESSAGE_FILE_DOES_NOT_EXIST, filepath));
+ }
+
+ @Test
+ void parse_invalidFields_success() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Path path = Path.of("src", "test", "data", "ImportTestData", "invalidFields.csv");
+
+ String filepath = path.toString();
+
+ List invalidFields = List.of(
+ String.format(ERROR_INVALID_NAME, "2"),
+ String.format(ERROR_INVALID_PHONE, "a"),
+ String.format(ERROR_INVALID_EMAIL, "k"),
+ String.format(ERROR_INVALID_GITHUB, "_"),
+ String.format(ERROR_INVALID_TAG, "日本人")
+ );
+
+ assertParseSuccess(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ new ImportCommand(List.of(), filepath, invalidFields));
+ }
+
+ @Test
+ void parse_extraneousHeaders_success() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Path path = Path.of("src", "test", "data", "ImportTestData", "extraneousHeaders.csv");
+
+ String filepath = path.toString();
+
+ assertParseSuccess(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ new ImportCommand(List.of(), filepath, List.of()));
+ }
+
+ @Test
+ void parse_folderSpecified_failure() {
+ ImportCommandParser parser = new ImportCommandParser();
+
+ Path path = Path.of("src");
+
+ String filepath = path.toString();
+
+ assertParseFailure(parser, ImportCommand.COMMAND_WORD + " " + PREFIX_FILEPATH + filepath,
+ String.format(MESSAGE_FOLDER_SPECIFIED, filepath));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ListTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ListTaskCommandParserTest.java
new file mode 100644
index 00000000000..396051db0e5
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ListTaskCommandParserTest.java
@@ -0,0 +1,46 @@
+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 org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ListTaskCommand;
+
+public class ListTaskCommandParserTest {
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListTaskCommand.MESSAGE_USAGE);
+
+ private ListTaskCommandParser parser = new ListTaskCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // Wrong input
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+
+ // Wrong prefix
+ assertParseFailure(parser, "all", MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_correctPrefix_success() {
+ String userInputAll = " all/";
+ ListTaskCommand expectedCommandAll = new ListTaskCommand("all");
+ assertParseSuccess(parser, userInputAll, expectedCommandAll);
+
+ String userInputNC = " nc/";
+ ListTaskCommand expectedCommandNC = new ListTaskCommand("nc");
+ assertParseSuccess(parser, userInputNC, expectedCommandNC);
+
+ String userInputC = " c/";
+ ListTaskCommand expectedCommandC = new ListTaskCommand("c");
+ assertParseSuccess(parser, userInputC, expectedCommandC);
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/MarkTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/MarkTaskCommandParserTest.java
new file mode 100644
index 00000000000..2ff63290e07
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/MarkTaskCommandParserTest.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_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.MarkTaskCommand;
+
+/**
+ * As we are only doing white-box testing, our test cases do not cover path variations
+ * outside of the MarkTaskCommand code. For example, inputs "1" and "1 abc" take the
+ * same path through the MarkTaskCommand, 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 MarkTaskCommandParserTest {
+
+ private MarkTaskCommandParser parser = new MarkTaskCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsMarkTaskCommand() {
+ assertParseSuccess(parser, "1", new MarkTaskCommand(INDEX_FIRST_TASK));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "a", MESSAGE_INVALID_TASK_DISPLAYED_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..82c0e9e2bb4 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -1,21 +1,19 @@
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.parser.ParserUtil.MESSAGE_INVALID_INDEX;
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.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.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -23,13 +21,13 @@
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_USERNAME = " ";
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_USERNAME = "rachel123";
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";
@@ -102,27 +100,18 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc
assertEquals(expectedPhone, ParserUtil.parsePhone(phoneWithWhitespace));
}
- @Test
- public void parseAddress_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null));
- }
-
- @Test
- public void parseAddress_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS));
- }
@Test
- public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception {
- Address expectedAddress = new Address(VALID_ADDRESS);
- assertEquals(expectedAddress, ParserUtil.parseAddress(VALID_ADDRESS));
+ public void parseUsername_validValueWithoutWhitespace_returnsAddress() throws Exception {
+ GitUsername expectedUsername = new GitUsername(VALID_USERNAME);
+ assertEquals(expectedUsername, ParserUtil.parseGitUsername(VALID_USERNAME));
}
@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));
+ String usernameWithWhitespace = WHITESPACE + VALID_USERNAME + WHITESPACE;
+ GitUsername expectedUsername = new GitUsername(VALID_USERNAME);
+ assertEquals(expectedUsername, ParserUtil.parseGitUsername(usernameWithWhitespace));
}
@Test
@@ -153,11 +142,6 @@ public void parseTag_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null));
}
- @Test
- public void parseTag_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG));
- }
-
@Test
public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception {
Tag expectedTag = new Tag(VALID_TAG_1);
@@ -176,15 +160,7 @@ public void parseTags_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null));
}
- @Test
- public void parseTags_collectionWithInvalidTags_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)));
- }
- @Test
- public void parseTags_emptyCollection_returnsEmptySet() throws Exception {
- assertTrue(ParserUtil.parseTags(Collections.emptyList()).isEmpty());
- }
@Test
public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
diff --git a/src/test/java/seedu/address/logic/parser/UnassignCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnassignCommandParserTest.java
new file mode 100644
index 00000000000..75ce1b08efc
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/UnassignCommandParserTest.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.commons.core.Messages.MESSAGE_INVALID_PARAMETERS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON;
+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 static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnassignCommand;
+
+class UnassignCommandParserTest {
+ private UnassignCommandParser parser = new UnassignCommandParser();
+
+ @Test
+ void parse_allFieldsPresent_success() {
+
+ Index firstTask = INDEX_FIRST_TASK;
+ Index secondPerson = INDEX_SECOND_PERSON;
+
+ String userInput = firstTask.getOneBased() + " " + PREFIX_PERSON + secondPerson.getOneBased();
+ Logger.getLogger("a").log(Level.INFO, userInput);
+ assertParseSuccess(parser, userInput,
+ new UnassignCommand(firstTask, secondPerson));
+ }
+
+ @Test
+ void parse_compulsoryFieldMissing_failure() {
+ String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_INVALID_PARAMETERS + "p/\n"
+ + UnassignCommand.MESSAGE_USAGE);
+
+ assertParseFailure(parser, String.valueOf(INDEX_FIRST_TASK.getOneBased()), expectedMessage);
+ }
+
+ @Test
+ void parse_noInput_failure() {
+ String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_INVALID_PARAMETERS + "p/\n"
+ + UnassignCommand.MESSAGE_USAGE);
+
+ assertParseFailure(parser, "", expectedMessage);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/UnmarkTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnmarkTaskCommandParserTest.java
new file mode 100644
index 00000000000..9aac7950386
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/UnmarkTaskCommandParserTest.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_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.UnmarkTaskCommand;
+
+/**
+ * As we are only doing white-box testing, our test cases do not cover path variations
+ * outside of the UnmarkTaskCommand code. For example, inputs "1" and "1 abc" take the
+ * same path through the UnmarkTaskCommand, 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 UnmarkTaskCommandParserTest {
+
+ private UnmarkTaskCommandParser parser = new UnmarkTaskCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsMarkTaskCommand() {
+ assertParseSuccess(parser, "1", new UnmarkTaskCommand(INDEX_FIRST_TASK));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "a", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java
new file mode 100644
index 00000000000..0334f0ec283
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX;
+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.index.Index;
+import seedu.address.logic.commands.ViewCommand;
+
+public class ViewCommandParserTest {
+ private ViewCommandParser parser = new ViewCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsViewCommand() {
+ assertParseSuccess(parser, "1", new ViewCommand(Index.fromOneBased(1)));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+
+ // Using an alphabets for index
+ assertParseFailure(parser, "a", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ // Using a negative integer for index
+ assertParseFailure(parser, "-1", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ // Using zero for index
+ assertParseFailure(parser, "0", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ // Using special character for index
+ assertParseFailure(parser, "&", MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 87782528ecd..b2d83b44bb7 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -3,24 +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.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.AMY;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
-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;
public class AddressBookTest {
@@ -43,17 +38,6 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() {
assertEquals(newData, addressBook);
}
- @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);
-
- assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData));
- }
-
@Test
public void hasPerson_nullPerson_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null));
@@ -72,10 +56,8 @@ public void hasPerson_personInAddressBook_returnsTrue() {
@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));
+ addressBook.addPerson(AMY);
+ assertTrue(addressBook.hasPerson(AMY));
}
@Test
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..1238876c946 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -100,8 +100,8 @@ public void equals() {
UserPrefs userPrefs = new UserPrefs();
// same values -> returns true
- modelManager = new ModelManager(addressBook, userPrefs);
- ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs);
+ modelManager = new ModelManager(addressBook, userPrefs, new TaskList());
+ ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs, new TaskList());
assertTrue(modelManager.equals(modelManagerCopy));
// same object -> returns true
@@ -114,12 +114,12 @@ public void equals() {
assertFalse(modelManager.equals(5));
// different addressBook -> returns false
- assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs, new TaskList())));
// 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)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs, new TaskList())));
// resets modelManager to initial state for upcoming tests
modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
@@ -127,6 +127,6 @@ public void equals() {
// different userPrefs -> returns false
UserPrefs differentUserPrefs = new UserPrefs();
differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
- assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs, new TaskList())));
}
}
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/GitUsernameTest.java b/src/test/java/seedu/address/model/person/GitUsernameTest.java
new file mode 100644
index 00000000000..bb4a09c3f3b
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/GitUsernameTest.java
@@ -0,0 +1,41 @@
+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 GitUsernameTest {
+ private String validUsername = "abc123"; //alnum
+ private String validUsername2 = "a-bc"; //hyphens
+ private String invalidUsername = "a___bc";
+ private String invalidUsername2 = "a!bc";
+ private String invalidUsername3 = "a|()";
+ private String invalidUsername4 = "a--bc";
+ private String invalidUsername5 = "-abc";
+ private String invalidUsername6 = "abc-";
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new GitUsername(null));
+ }
+
+ @Test
+ public void validUsername() {
+ assertTrue(GitUsername.isValidId(validUsername));
+
+ assertTrue(GitUsername.isValidId(validUsername2));
+ }
+
+ @Test
+ public void invalidUsername() {
+ assertFalse(GitUsername.isValidId(invalidUsername));
+
+ assertFalse(GitUsername.isValidId(invalidUsername2));
+
+ assertFalse(GitUsername.isValidId(invalidUsername3));
+
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
index f136664e017..9631bf8bc3f 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
@@ -68,8 +68,8 @@ public void test_nameDoesNotContainKeywords_returnsFalse() {
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"));
+ predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "alice123"));
assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345")
- .withEmail("alice@email.com").withAddress("Main Street").build()));
+ .withEmail("alice@email.com").withUsername("alice123").build()));
}
}
diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/person/NameTest.java
index c9801392874..6a49ffc15bc 100644
--- a/src/test/java/seedu/address/model/person/NameTest.java
+++ b/src/test/java/seedu/address/model/person/NameTest.java
@@ -27,14 +27,18 @@ public void isValidName() {
// 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
+ assertFalse(Name.isValidName("$#")); // only non-alphanumeric characters
+ assertFalse(Name.isValidName("12345")); // numbers only
+ assertFalse(Name.isValidName("peter*")); // contains invalid symbols
+ assertFalse(Name.isValidName("peter the 2nd")); // 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
+ assertTrue(Name.isValidName("peter jack")); // alphabets only
+ assertTrue(Name.isValidName("David Roger Jackson Ray Jr Second")); // long names
+ assertTrue(Name.isValidName("John O'Reilly")); // name with apostrophes
+ assertTrue(Name.isValidName("Martin Luther King, Jr.")); // name with full stop and comma
+ assertTrue(Name.isValidName("Alex Smith-Jones")); // name with hyphen
+ assertTrue(Name.isValidName("Rajesh s/o Koothrapali")); // name with slash
}
}
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index b29c097cfd4..26806a76c57 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -2,11 +2,11 @@
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.logic.commands.CommandTestUtil.VALID_USERNAME_BOB;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
@@ -31,23 +31,18 @@ public void isSamePerson() {
// 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();
+ // different name, all other attributes same -> returns true
+ Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).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));
+ assertFalse(BOB.getName().equals(editedBob.getName()));
// 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));
+ assertFalse(BOB.getName().equals(editedBob.getName()));
}
@Test
@@ -70,7 +65,7 @@ public void equals() {
// different name -> returns false
Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
- assertFalse(ALICE.equals(editedAlice));
+ assertFalse(editedAlice.getName().equals(ALICE.getName()));
// different phone -> returns false
editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build();
@@ -81,7 +76,7 @@ public void equals() {
assertFalse(ALICE.equals(editedAlice));
// different address -> returns false
- editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
+ editedAlice = new PersonBuilder(ALICE).withUsername(VALID_USERNAME_BOB).build();
assertFalse(ALICE.equals(editedAlice));
// different tags -> returns false
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 1cc5fe9e0fe..bf8dbcf1de8 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -3,10 +3,11 @@
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_USERNAME_BOB;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.AMY;
import static seedu.address.testutil.TypicalPersons.BOB;
import java.util.Arrays;
@@ -41,10 +42,8 @@ public void contains_personInList_returnsTrue() {
@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));
+ uniquePersonList.add(AMY);
+ assertTrue(uniquePersonList.contains(AMY));
}
@Test
@@ -85,7 +84,7 @@ public void setPerson_editedPersonIsSamePerson_success() {
@Test
public void setPerson_editedPersonHasSameIdentity_success() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Person editedAlice = new PersonBuilder(ALICE).withUsername(VALID_USERNAME_BOB).withTags(VALID_TAG_HUSBAND)
.build();
uniquePersonList.setPerson(ALICE, editedAlice);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
diff --git a/src/test/java/seedu/address/model/task/LinkTest.java b/src/test/java/seedu/address/model/task/LinkTest.java
new file mode 100644
index 00000000000..c8a87715aea
--- /dev/null
+++ b/src/test/java/seedu/address/model/task/LinkTest.java
@@ -0,0 +1,41 @@
+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 LinkTest {
+ @Test
+ public void constructor_null_throwsIllegalArgumentException() {
+ assertThrows(NullPointerException.class, () -> new Link(null));
+ }
+
+ @Test
+ public void isValidLink() {
+ // null link
+ assertTrue(() -> new Link().isEmpty());
+
+ // blank link
+ assertFalse(Link.isValidLink(""));
+ assertFalse(Link.isValidLink(" "));
+
+ // missing parts
+ assertFalse(Link.isValidLink("a.com"));
+ assertFalse(Link.isValidLink("google"));
+
+ // invalid links
+ assertFalse(Link.isValidLink("www.google.com"));
+ assertFalse(Link.isValidLink("www.google.com.sg"));
+
+ // valid link
+ assertTrue(Link.isValidLink("http://info.cern.ch/"));
+ assertTrue(Link.isValidLink("https:google.com"));
+ assertTrue(Link.isValidLink("https:google.com.sg"));
+ assertTrue(Link.isValidLink("https://www.coingecko.com"));
+ assertTrue(Link.isValidLink("https://www.thisisthelongesteuropeandomainnameallovertheworldandnowitismine.eu"));
+ assertTrue(
+ Link.isValidLink("https://nus-sg.zoom.us/j/92307270969?pwd=VVMvNWFPWFpyHRIcXR0VkJlNkg0dz09#success"));
+ }
+}
diff --git a/src/test/java/seedu/address/model/task/TaskBetweenDatesPredicateTest.java b/src/test/java/seedu/address/model/task/TaskBetweenDatesPredicateTest.java
new file mode 100644
index 00000000000..2e7ba3117ea
--- /dev/null
+++ b/src/test/java/seedu/address/model/task/TaskBetweenDatesPredicateTest.java
@@ -0,0 +1,61 @@
+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 java.time.LocalDateTime;
+import java.util.ArrayList;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.TaskBuilder;
+
+
+public class TaskBetweenDatesPredicateTest {
+ private final LocalDateTime beforeDateTime = LocalDateTime.of(2050, 6, 6, 6, 0);
+ private final LocalDateTime afterDateTime = LocalDateTime.of(2050, 12, 12, 12, 0);
+ private ArrayList list = new ArrayList<>();
+
+ @Test
+ public void testValidDate() {
+ list.add(beforeDateTime);
+ list.add(afterDateTime);
+ assertEquals(list.size(), 2);
+ TaskBetweenDatesPredicate predicate = new TaskBetweenDatesPredicate(list);
+
+ //In between
+ LocalDateTime validDateTime1 = LocalDateTime.of(2050, 9, 9, 9, 0);
+ Task taskValidDateTime1 = new TaskBuilder().withDateTime(validDateTime1).build();
+ assertTrue(predicate.test(taskValidDateTime1));
+
+ //At the start
+ LocalDateTime validDateTime2 = LocalDateTime.of(2050, 6, 6, 6, 1);
+ Task taskValidDateTime2 = new TaskBuilder().withDateTime(validDateTime2).build();
+ assertTrue(predicate.test(taskValidDateTime2));
+
+ //At the end
+ LocalDateTime validDateTime3 = LocalDateTime.of(2050, 12, 12, 11, 59);
+ Task taskValidDateTime3 = new TaskBuilder().withDateTime(validDateTime3).build();
+ assertTrue(predicate.test(taskValidDateTime3));
+ }
+
+ @Test
+ public void testInvalidDate() {
+ list.add(beforeDateTime);
+ list.add(afterDateTime);
+ assertEquals(list.size(), 2);
+ TaskBetweenDatesPredicate predicate = new TaskBetweenDatesPredicate(list);
+
+ //before
+ LocalDateTime invalidDateTime1 = LocalDateTime.of(2050, 6, 6, 5, 59);
+ Task invalidTask1 = new TaskBuilder().withDateTime(invalidDateTime1).build();
+ assertFalse(predicate.test(invalidTask1));
+
+ //after
+ LocalDateTime invalidDateTime2 = LocalDateTime.of(2050, 12, 12, 12, 1);
+ Task invalidTask2 = new TaskBuilder().withDateTime(invalidDateTime2).build();
+ assertFalse(predicate.test(invalidTask2));
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/task/TaskGetDateTimeStringTest.java b/src/test/java/seedu/address/model/task/TaskGetDateTimeStringTest.java
new file mode 100644
index 00000000000..82d43eb895a
--- /dev/null
+++ b/src/test/java/seedu/address/model/task/TaskGetDateTimeStringTest.java
@@ -0,0 +1,346 @@
+package seedu.address.model.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.model.task.Task.FORMAT_DAY_OF_WEEK;
+import static seedu.address.model.task.Task.FORMAT_MONTH;
+import static seedu.address.model.task.Task.FORMAT_TIME;
+import static seedu.address.model.task.Task.FORMAT_YEAR;
+import static seedu.address.model.task.Task.getUserFriendlyDateTime;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.TaskBuilder;
+
+public class TaskGetDateTimeStringTest {
+ @Test
+ public void getUserFriendlyDateTime_sameDay_today() {
+ LocalDateTime today = LocalDateTime.now();
+
+ String expected = String.format("Today, %s", today.format(FORMAT_TIME));
+
+ assertEquals(expected, getUserFriendlyDateTime(today));
+ }
+
+ @Test
+ public void getUserFriendlyDateTime_nextDay_tomorrow() {
+ LocalDateTime tomorrow = LocalDateTime.now().plusDays(1);
+
+ String expected = String.format("Tomorrow, %s", tomorrow.format(FORMAT_TIME));
+
+ assertEquals(expected, getUserFriendlyDateTime(tomorrow));
+ }
+
+ @Test
+ public void getUserFriendlyDateTime_twoDaysFromNow_sameWeek() {
+ LocalDateTime day = LocalDateTime.now().plusDays(2);
+
+ String expected = day.format(FORMAT_DAY_OF_WEEK);
+
+ assertEquals(expected, getUserFriendlyDateTime(day));
+ }
+
+ @Test
+ public void getUserFriendlyDateTime_sevenDaysFromNow_sameWeek() {
+ LocalDateTime day = LocalDateTime.now().plusDays(7);
+
+ String expected = day.format(FORMAT_DAY_OF_WEEK);
+
+ assertEquals(expected, getUserFriendlyDateTime(day));
+ }
+
+ /**
+ * To ensure that the day chosen is not within the next 7 days,
+ * If today is first half of the year (Jan - Jun), use Dec 31.
+ * Else use 1 Jan.
+ */
+ @Test
+ public void getUserFriendlyDateTime_sameYear_monthDisplay() {
+ LocalDateTime day;
+
+ if (LocalDate.now().getMonthValue() <= 6) {
+ day = LocalDateTime.now().withMonth(12).withDayOfMonth(31);
+ } else {
+ day = LocalDateTime.now().withMonth(1).withDayOfMonth(1);
+ }
+
+ String expected = day.format(FORMAT_MONTH);
+
+ assertEquals(expected, getUserFriendlyDateTime(day));
+ }
+
+ @Test
+ public void getUserFriendlyDateTime_nextYear_yearDisplay() {
+ LocalDateTime day = LocalDateTime.now().plusYears(1);
+
+ String expected = day.format(FORMAT_YEAR);
+
+ assertEquals(expected, getUserFriendlyDateTime(day));
+ }
+
+ @Test
+ public void getUserFriendlyDateTime_prevYear_yearDisplay() {
+ LocalDateTime day = LocalDateTime.now().minusYears(1);
+
+ String expected = day.format(FORMAT_YEAR);
+
+ assertEquals(expected, getUserFriendlyDateTime(day));
+ }
+
+ @Test
+ public void getDateTimeString_deadline_today() {
+ LocalDateTime today = LocalDateTime.now();
+
+ Task task = new TaskBuilder().withDateTime(today).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(today));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_deadline_tomorrow() {
+ LocalDateTime tomorrow = LocalDateTime.now().plusDays(1);
+
+ Task task = new TaskBuilder().withDateTime(tomorrow).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(tomorrow));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_deadline_twoDaysFromNow() {
+ LocalDateTime day = LocalDateTime.now().plusDays(2);
+
+ Task task = new TaskBuilder().withDateTime(day).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(day));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_deadline_sevenDaysFromNow() {
+ LocalDateTime day = LocalDateTime.now().plusDays(7);
+
+ Task task = new TaskBuilder().withDateTime(day).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(day));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_deadline_thisYear() {
+ LocalDateTime day;
+
+ if (LocalDate.now().getMonthValue() <= 6) {
+ day = LocalDateTime.now().withMonth(12).withDayOfMonth(31);
+ } else {
+ day = LocalDateTime.now().withMonth(1).withDayOfMonth(1);
+ }
+
+ Task task = new TaskBuilder().withDateTime(day).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(day));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_deadline_prevYear() {
+ LocalDateTime day = LocalDateTime.now().minusYears(1);
+
+ Task task = new TaskBuilder().withDateTime(day).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(day));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_deadline_nextYear() {
+ LocalDateTime day = LocalDateTime.now().plusYears(1);
+
+ Task task = new TaskBuilder().withDateTime(day).withEndDateTime(null).build();
+
+ String expected = String.format("Due: %s", getUserFriendlyDateTime(day));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_todayRange_noRepeated() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime start = today.withHour(0);
+ LocalDateTime end = today.withHour(23);
+
+ Task task = new TaskBuilder().withDateTime(start).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s", getUserFriendlyDateTime(start), end.format(FORMAT_TIME));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_tomorrowRange_noRepeated() {
+ LocalDateTime tomorrow = LocalDateTime.now();
+ LocalDateTime start = tomorrow.withHour(0);
+ LocalDateTime end = tomorrow.withHour(23);
+
+ Task task = new TaskBuilder().withDateTime(start).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s", getUserFriendlyDateTime(start), end.format(FORMAT_TIME));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_thisWeekRange_noRepeated() {
+ LocalDateTime day = LocalDateTime.now().plusDays(3);
+ LocalDateTime start = day.withHour(0);
+ LocalDateTime end = day.withHour(23);
+
+ Task task = new TaskBuilder().withDateTime(start).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s", getUserFriendlyDateTime(start), end.format(FORMAT_TIME));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_thisYearRange_noRepeated() {
+ LocalDateTime day;
+
+ if (LocalDate.now().getMonthValue() <= 6) {
+ day = LocalDateTime.now().withMonth(12).withDayOfMonth(31);
+ } else {
+ day = LocalDateTime.now().withMonth(1).withDayOfMonth(1);
+ }
+
+ LocalDateTime start = day.withHour(0);
+ LocalDateTime end = day.withHour(23);
+
+ Task task = new TaskBuilder().withDateTime(start).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s", getUserFriendlyDateTime(start), end.format(FORMAT_TIME));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_anotherYearRange_noRepeated() {
+ LocalDateTime day = LocalDateTime.now().withYear(1989);
+ LocalDateTime start = day.withHour(0);
+ LocalDateTime end = day.withHour(23);
+
+ Task task = new TaskBuilder().withDateTime(start).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s", getUserFriendlyDateTime(start), end.format(FORMAT_TIME));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_todayToTomorrow() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime tomorrow = today.plusDays(1);
+
+ Task task = new TaskBuilder().withDateTime(today).withEndDateTime(tomorrow).build();
+
+ String expected = String.format("From: %s - %s",
+ getUserFriendlyDateTime(today), getUserFriendlyDateTime(tomorrow));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_todayToThisWeek() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime end = today.plusDays(5);
+
+ Task task = new TaskBuilder().withDateTime(today).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s",
+ getUserFriendlyDateTime(today), getUserFriendlyDateTime(end));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_tomorrowToThisYear() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime tomorrow = today.plusDays(1);
+ LocalDateTime day;
+
+ if (LocalDate.now().getMonthValue() <= 6) {
+ day = today.withMonth(12).withDayOfMonth(31);
+ } else {
+ day = today.withMonth(1).withDayOfMonth(1);
+ }
+
+
+ Task task = new TaskBuilder().withDateTime(tomorrow).withEndDateTime(day).build();
+
+ String expected = String.format("From: %s - %s",
+ getUserFriendlyDateTime(tomorrow), getUserFriendlyDateTime(day));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getDateTimeString_thisYearToAnotherYear() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime day;
+ LocalDateTime end = today.plusYears(3);
+
+ if (LocalDate.now().getMonthValue() <= 6) {
+ day = today.withMonth(12).withDayOfMonth(31);
+ } else {
+ day = today.withMonth(1).withDayOfMonth(1);
+ }
+
+
+ Task task = new TaskBuilder().withDateTime(day).withEndDateTime(end).build();
+
+ String expected = String.format("From: %s - %s",
+ getUserFriendlyDateTime(day), getUserFriendlyDateTime(end));
+
+ String actual = task.getDateTimeString();
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/src/test/java/seedu/address/model/task/TaskGetEmailsTest.java b/src/test/java/seedu/address/model/task/TaskGetEmailsTest.java
new file mode 100644
index 00000000000..83227c30e33
--- /dev/null
+++ b/src/test/java/seedu/address/model/task/TaskGetEmailsTest.java
@@ -0,0 +1,25 @@
+package seedu.address.model.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.Person;
+import seedu.address.testutil.TypicalAssignedTasks;
+
+public class TaskGetEmailsTest {
+ @Test
+ void getEmails_multipleContacts_success() {
+ Task task = TypicalAssignedTasks.getTypicalAssignedTaskList().getTaskList().get(0);
+ List people = task.getPeople();
+ assert !people.isEmpty();
+
+ String expected = people.stream().map(p -> p.getEmail().toString()).collect(Collectors.joining(", "));
+ String actual = task.getEmails();
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/src/test/java/seedu/address/model/task/TaskNameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/task/TaskNameContainsKeywordsPredicateTest.java
new file mode 100644
index 00000000000..af25a756424
--- /dev/null
+++ b/src/test/java/seedu/address/model/task/TaskNameContainsKeywordsPredicateTest.java
@@ -0,0 +1,74 @@
+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 java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.TaskBuilder;
+
+public class TaskNameContainsKeywordsPredicateTest {
+ @Test
+ public void equals() {
+ List firstPredicateKeywordList = Collections.singletonList("first");
+ List secondPredicateKeywordList = Arrays.asList("first", "second");
+
+ TaskNameContainsKeywordsPredicate firstPredicate =
+ new TaskNameContainsKeywordsPredicate(firstPredicateKeywordList);
+ TaskNameContainsKeywordsPredicate secondPredicate =
+ new TaskNameContainsKeywordsPredicate(secondPredicateKeywordList);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ TaskNameContainsKeywordsPredicate firstPredicateCopy =
+ new TaskNameContainsKeywordsPredicate(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_taskNameContainsKeywords_returnsTrue() {
+ // One keyword
+ TaskNameContainsKeywordsPredicate predicate =
+ new TaskNameContainsKeywordsPredicate(Collections.singletonList("Consultation"));
+ assertTrue(predicate.test(new TaskBuilder().withTaskName("Consultation Alice").build()));
+
+ // Multiple keywords
+ predicate = new TaskNameContainsKeywordsPredicate(Arrays.asList("Consultation", "Slot"));
+ assertTrue(predicate.test(new TaskBuilder().withTaskName("Consultation Slot").build()));
+
+ // Only one matching keyword
+ predicate = new TaskNameContainsKeywordsPredicate(Arrays.asList("Consultation", "Meeting"));
+ assertTrue(predicate.test(new TaskBuilder().withTaskName("Consultation World Org").build()));
+
+ // Mixed-case keywords
+ predicate = new TaskNameContainsKeywordsPredicate(Arrays.asList("coNsULtAtion", "meEtIng"));
+ assertTrue(predicate.test(new TaskBuilder().withTaskName("Consultation Meeting").build()));
+ }
+
+ @Test
+ public void test_taskNameDoesNotContainKeywords_returnsFalse() {
+ // Zero keywords
+ TaskNameContainsKeywordsPredicate predicate = new TaskNameContainsKeywordsPredicate(Collections.emptyList());
+ assertFalse(predicate.test(new TaskBuilder().withTaskName("Consultation").build()));
+
+ // Non-matching keyword
+ predicate = new TaskNameContainsKeywordsPredicate(Arrays.asList("Apple"));
+ assertFalse(predicate.test(new TaskBuilder().withTaskName("Consultation Meeting").build()));
+
+ }
+}
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..c1b87368bcd
--- /dev/null
+++ b/src/test/java/seedu/address/model/task/TaskTest.java
@@ -0,0 +1,255 @@
+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.logic.commands.CommandTestUtil.VALID_DATETIME_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_LINK_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_TASKB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_TASKB;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_NAME;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_TAG;
+import static seedu.address.testutil.TaskBuilder.DEFAULT_ZOOMLINK;
+import static seedu.address.testutil.TypicalPersons.BOB;
+import static seedu.address.testutil.TypicalTasks.BRUSH_TEETH;
+import static seedu.address.testutil.TypicalTasks.CONSULTATION;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.TaskBuilder;
+
+public class TaskTest {
+ @Test
+ void hasInvalidDateRange_validRange_success() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime tomorrow = today.plusDays(1);
+
+ Task task = new TaskBuilder().withDateTime(today).withEndDateTime(tomorrow).build();
+
+ assertFalse(task.hasInvalidDateRange());
+ }
+
+ @Test
+ void hasInvalidDateRange_invalidDate_failure() {
+ LocalDateTime today = LocalDateTime.now();
+ LocalDateTime tomorrow = today.plusDays(1);
+
+ Task task = new TaskBuilder().withDateTime(tomorrow).withEndDateTime(today).build();
+
+ assertTrue(task.hasInvalidDateRange());
+ }
+
+ @Test
+ void hasInvalidDateRange_sameDateTimeTime_failure() {
+ LocalDateTime today = LocalDateTime.now();
+
+ Task task = new TaskBuilder().withDateTime(today).withEndDateTime(today).build();
+
+ assertTrue(task.hasInvalidDateRange());
+ }
+
+ @Test
+ void hasInvalidDateRange_endBeforeStart_failure() {
+ LocalDateTime today = LocalDateTime.now().withHour(2).withMinute(16);
+ LocalDateTime oneHourAgo = today.withMinute(15);
+
+ Task task = new TaskBuilder().withDateTime(today).withEndDateTime(oneHourAgo).build();
+
+ assertTrue(task.hasInvalidDateRange());
+ }
+
+ @Test
+ public void constructor_build_success() {
+ ArrayList tagList = new ArrayList<>();
+ tagList.add(new Tag(DEFAULT_TAG));
+ Set defaultTagSet = new HashSet(tagList);
+ ArrayList defaultPersonList = new ArrayList<>();
+ Link defaultLink = new Link(DEFAULT_ZOOMLINK);
+ LocalDateTime defaultStartDate = LocalDateTime.of(2050, 12, 15, 21, 0);
+ LocalDateTime defaultEndDate = LocalDateTime.of(2050, 12, 20, 21, 0);
+ boolean defaultIsMark = false;
+
+ // Default constructor
+ Task defaultTask = new Task(DEFAULT_NAME, defaultStartDate, defaultEndDate,
+ defaultPersonList, defaultTagSet, defaultLink, defaultIsMark);
+
+ assertEquals(DEFAULT_NAME, defaultTask.getName());
+ assertEquals(defaultStartDate, defaultTask.getDateTime());
+ assertEquals(defaultEndDate, defaultTask.getEndDateTime());
+ assertEquals(defaultPersonList, defaultTask.getPeople());
+ assertEquals(defaultTagSet, defaultTask.getTags());
+ assertEquals(defaultLink, defaultTask.getLink());
+ assertEquals(defaultIsMark, defaultTask.isTaskMark());
+
+ // Constructor with no endDateTime
+ Task taskWithNoEndDate = new Task(DEFAULT_NAME, defaultStartDate,
+ defaultPersonList, defaultTagSet, defaultLink, defaultIsMark);
+
+ assertEquals(DEFAULT_NAME, taskWithNoEndDate.getName());
+ assertEquals(defaultStartDate, taskWithNoEndDate.getDateTime());
+ assertEquals(null, taskWithNoEndDate.getEndDateTime());
+ assertEquals(defaultPersonList, taskWithNoEndDate.getPeople());
+ assertEquals(defaultTagSet, taskWithNoEndDate.getTags());
+ assertEquals(defaultLink, taskWithNoEndDate.getLink());
+ assertEquals(defaultIsMark, taskWithNoEndDate.isTaskMark());
+
+ // Constructor without people or endDateTime
+ Task taskWithNoPeopleAndEndDate = new Task(DEFAULT_NAME, defaultStartDate,
+ defaultTagSet, defaultLink, defaultIsMark);
+
+ assertEquals(DEFAULT_NAME, taskWithNoPeopleAndEndDate.getName());
+ assertEquals(defaultStartDate, taskWithNoPeopleAndEndDate.getDateTime());
+ assertEquals(null, taskWithNoPeopleAndEndDate.getEndDateTime());
+ assertEquals(new ArrayList(), taskWithNoPeopleAndEndDate.getPeople());
+ assertEquals(defaultTagSet, taskWithNoPeopleAndEndDate.getTags());
+ assertEquals(defaultLink, taskWithNoPeopleAndEndDate.getLink());
+ assertEquals(defaultIsMark, taskWithNoPeopleAndEndDate.isTaskMark());
+ }
+
+ @Test
+ public void changeName() {
+ Task currentTask = new TaskBuilder().build();
+ Task expectedMixedCaseTask = new TaskBuilder().withTaskName("New Name").build();
+ Task expectedLowerCaseTask = new TaskBuilder().withTaskName("all lower case").build();
+ Task expectedUpperCaseTask = new TaskBuilder().withTaskName("ALL UPPER CASE").build();
+
+ currentTask.changeName("New Name");
+ assertEquals(expectedMixedCaseTask.getName(), currentTask.getName());
+
+ currentTask.changeName("all lower case");
+ assertEquals(expectedLowerCaseTask.getName(), currentTask.getName());
+
+ currentTask.changeName("ALL UPPER CASE");
+ assertEquals(expectedUpperCaseTask.getName(), currentTask.getName());
+ }
+
+ @Test
+ public void changeDateTime() {
+ Task currentTask = new TaskBuilder().build();
+ Task expectedTask = new TaskBuilder().withDateTime(LocalDateTime.of(2055, 12, 15, 21, 0)).build();
+
+ currentTask.changeDateTime(LocalDateTime.of(2055, 12, 15, 21, 0));
+ assertEquals(expectedTask.getDateTime(), currentTask.getDateTime());
+ }
+
+ @Test
+ public void hasEndDateTime() {
+ Task noEndDateTask = new TaskBuilder().build();
+ assertTrue(noEndDateTask.hasEndDateTime());
+
+ Task withEndDateTask = new TaskBuilder().withEndDateTime(null).build();
+ assertFalse(withEndDateTask.hasEndDateTime());
+ }
+
+ @Test
+ public void asObservableList_modifyList_throwsUnsupportedOperationException() {
+ Task task = new TaskBuilder().build();
+ assertThrows(UnsupportedOperationException.class, () -> task.getTags().remove(0));
+ }
+
+ @Test
+ public void isSameTask() {
+ // same object -> returns true
+ assertTrue(CONSULTATION.isSameTask(CONSULTATION));
+
+ // null -> returns false
+ assertFalse(CONSULTATION.isSameTask(null));
+ }
+
+ @Test
+ public void createTask_success() {
+ Set tagToTest = new HashSet();
+ String taskNameToTest = "Consultation With Students";
+ tagToTest.add(new Tag("Consultation"));
+ ArrayList assignedPersonToTest = new ArrayList<>();
+ assignedPersonToTest.add(BOB);
+ Link linkToTest = new Link("https://google.com");
+ LocalDateTime startDateToTest = LocalDateTime.of(2050, 12, 15, 21, 0);
+ LocalDateTime endDateToTest = LocalDateTime.of(2050, 12, 16, 21, 0);
+
+ Task expectedTask = new Task(taskNameToTest, startDateToTest, endDateToTest, assignedPersonToTest, tagToTest,
+ linkToTest, false);
+
+ assertEquals(expectedTask.getName(), taskNameToTest);
+ assertEquals(expectedTask.getDateTime(), startDateToTest);
+ assertEquals(expectedTask.getEndDateTime(), endDateToTest);
+ assertEquals(expectedTask.getPeople(), assignedPersonToTest);
+ assertEquals(expectedTask.getTags(), tagToTest);
+ assertEquals(expectedTask.getLink(), linkToTest);
+ assertEquals(expectedTask.isTaskMark(), false);
+ }
+
+ @Test
+ public void changeTaskName_success() {
+ String changeTaskName = "MEETING WITH CS2103 PROF";
+ Task currentTask = new TaskBuilder(CONSULTATION).build();
+ Task expectTask = new TaskBuilder(CONSULTATION).withTaskName(changeTaskName).build();
+ currentTask.changeName(changeTaskName);
+ assertEquals(currentTask.getName(), expectTask.getName());
+ }
+
+ @Test
+ public void changeTaskName_failure() {
+ String changeTaskName = "MEETING WITH CS2103 PROF";
+ String incorrectTaskName = "this is incorrect name";
+ Task currentTask = new TaskBuilder(CONSULTATION).build();
+ Task expectTask = new TaskBuilder(CONSULTATION).withTaskName(changeTaskName).build();
+ currentTask.changeName(incorrectTaskName);
+ assertFalse(currentTask.getName().equals(expectTask.getName()));
+ }
+
+ @Test
+ public void changeDateTime_success() {
+ LocalDateTime changeTaskDate = LocalDateTime.of(2051, 12, 15, 21, 0);
+ Task currentTask = new TaskBuilder(CONSULTATION).build();
+ Task expectTask = new TaskBuilder(CONSULTATION).withDateTime(changeTaskDate).build();
+ currentTask.changeDateTime(changeTaskDate);
+ assertEquals(currentTask.getDateTime(), expectTask.getDateTime());
+ }
+
+ @Test
+ public void changeDateTime_failure() {
+ LocalDateTime changeTaskDate = LocalDateTime.of(2051, 12, 15, 21, 0);
+ LocalDateTime incorrectChangeTaskDate = LocalDateTime.of(2052, 12, 15, 21, 0);
+ Task currentTask = new TaskBuilder(CONSULTATION).build();
+ Task expectTask = new TaskBuilder(CONSULTATION).withDateTime(changeTaskDate).build();
+ currentTask.changeDateTime(incorrectChangeTaskDate);
+ assertFalse(currentTask.getDateTime().equals(expectTask.getDateTime()));
+ }
+
+ @Test
+ public void equals() {
+ Task consultationCopy = new TaskBuilder(CONSULTATION).build();
+ assertTrue(CONSULTATION.equals(consultationCopy));
+
+ assertTrue(CONSULTATION.equals(CONSULTATION));
+
+ assertFalse(CONSULTATION.equals(null));
+
+ assertFalse(CONSULTATION.equals(BRUSH_TEETH));
+
+ // different name -> returns false
+ Task editedConsultation = new TaskBuilder(CONSULTATION).withTaskName(VALID_NAME_TASKB).build();
+ assertFalse(editedConsultation.getName().equals(CONSULTATION.getName()));
+
+ // different link -> returns false
+ editedConsultation = new TaskBuilder(CONSULTATION).withLink(VALID_LINK_TASKB).build();
+ assertFalse(editedConsultation.getLink().equals(CONSULTATION.getLink()));
+
+ // different date -> returns false
+ editedConsultation = new TaskBuilder(CONSULTATION).withDateTime(VALID_DATETIME_TASKB).build();
+ assertFalse(editedConsultation.getDateTime().equals(CONSULTATION.getDateTime()));
+
+ // different tags -> return false
+ editedConsultation = new TaskBuilder(CONSULTATION).withTags(VALID_TAG_TASKB).build();
+ assertFalse(editedConsultation.getTags().equals(CONSULTATION.getTags()));
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..755fb7b71ff 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -12,22 +12,22 @@
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.GitUsername;
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_USERNAME = " ";
private static final String INVALID_EMAIL = "example.com";
- private static final String INVALID_TAG = "#friend";
+ private static final String INVALID_TAG = "£";
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 String VALID_USERNAME = BENSON.getUsername().toString();
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
@@ -41,14 +41,14 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_USERNAME, 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);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_USERNAME, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +56,14 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_USERNAME, 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);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_USERNAME, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +71,14 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_USERNAME, 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);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_USERNAME, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,15 +86,15 @@ public void toModelType_nullEmail_throwsIllegalValueException() {
@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;
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_USERNAME, VALID_TAGS);
+ String expectedMessage = GitUsername.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());
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, GitUsername.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -103,7 +103,7 @@ 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);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_USERNAME, 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..143bc1703a8
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java
@@ -0,0 +1,87 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.storage.JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalTasks.CONSULTATION;
+
+import java.time.format.DateTimeParseException;
+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.task.Link;
+
+public class JsonAdaptedTaskTest {
+ private static final String INVALID_LINK = "a.com";
+ private static final String INVALID_TAG = "£";
+ private static final String INVALID_DATETIME = "28/15/2022";
+
+ // Task name will always be valid
+ private static final String TASKNAME = CONSULTATION.getName();
+
+ private static final String VALID_LINK = CONSULTATION.getLink().toString();
+ private static final String VALID_DATETIME = CONSULTATION.getDateTime().toString();
+ private static final List VALID_TAGS = CONSULTATION.getTags().stream()
+ .map(JsonAdaptedTag::new)
+ .collect(Collectors.toList());
+
+ @Test
+ public void toModelType_nullTaskName_throwsIllegalValueException() {
+ JsonAdaptedTask task = new JsonAdaptedTask(null, VALID_DATETIME, "null",
+ new ArrayList<>(), VALID_TAGS, VALID_LINK, "false");
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Name");
+ assertThrows(IllegalValueException.class, expectedMessage, task::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullDateTime_throwsIllegalValueException() {
+ JsonAdaptedTask task = new JsonAdaptedTask(TASKNAME, null, "null",
+ new ArrayList<>(), VALID_TAGS, VALID_LINK, "false");
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "dateTime");
+ assertThrows(IllegalValueException.class, expectedMessage, task::toModelType);
+ }
+
+ @Test
+ public void toModelType_validTaskDetails_returnsTask() throws Exception {
+ JsonAdaptedTask task = new JsonAdaptedTask(CONSULTATION);
+ assertEquals(CONSULTATION, task.toModelType());
+ }
+
+ @Test
+ public void toModelType_invalidLink_throwsIllegalValueException() {
+ JsonAdaptedTask task =
+ new JsonAdaptedTask(TASKNAME, VALID_DATETIME, "null",
+ new ArrayList<>(), VALID_TAGS, INVALID_LINK, "false");
+
+ String expectedMessage = Link.MESSAGE_CONSTRAINTS;
+
+ assertThrows(IllegalValueException.class, expectedMessage, task::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDateTime_throwsIllegalValueException() {
+ JsonAdaptedTask task =
+ new JsonAdaptedTask(TASKNAME, INVALID_DATETIME, "null",
+ new ArrayList<>(), VALID_TAGS, VALID_LINK, "false");
+
+ assertThrows(DateTimeParseException.class, task::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidTags_throwsIllegalValueException() {
+ List invalidTags = new ArrayList<>(VALID_TAGS);
+ invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
+
+ JsonAdaptedTask task =
+ new JsonAdaptedTask(TASKNAME, VALID_DATETIME, "null",
+ new ArrayList<>(), invalidTags, VALID_LINK, "false");
+
+ assertThrows(IllegalValueException.class, task::toModelType);
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
index 188c9058d20..57339cea6d7 100644
--- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
+++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
@@ -19,6 +19,10 @@ public class 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 DUPLICATE_PHONE_FILE = TEST_DATA_FOLDER.resolve("duplicatePhoneAddressBook.json");
+ private static final Path DUPLICATE_GIT_USERNAME_FILE =
+ TEST_DATA_FOLDER.resolve("duplicateGitUsernameAddressBook.json");
+ private static final Path DUPLICATE_EMAIL_FILE = TEST_DATA_FOLDER.resolve("duplicateEmailAddressBook.json");
@Test
public void toModelType_typicalPersonsFile_success() throws Exception {
@@ -44,4 +48,27 @@ public void toModelType_duplicatePersons_throwsIllegalValueException() throws Ex
dataFromFile::toModelType);
}
+ @Test
+ public void toModeType_duplicatePhone_throwsIllegalValueException() throws Exception {
+ JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PHONE_FILE,
+ JsonSerializableAddressBook.class).get();
+ assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PHONE,
+ dataFromFile::toModelType);
+ }
+
+ @Test
+ public void toModeType_duplicateGitUsername_throwsIllegalValueException() throws Exception {
+ JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_GIT_USERNAME_FILE,
+ JsonSerializableAddressBook.class).get();
+ assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_GIT_USERNAME,
+ dataFromFile::toModelType);
+ }
+
+ @Test
+ public void toModeType_duplicateEmail_throwsIllegalValueException() throws Exception {
+ JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_EMAIL_FILE,
+ JsonSerializableAddressBook.class).get();
+ assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_EMAIL,
+ dataFromFile::toModelType);
+ }
}
diff --git a/src/test/java/seedu/address/storage/JsonSerializableTaskListTest.java b/src/test/java/seedu/address/storage/JsonSerializableTaskListTest.java
new file mode 100644
index 00000000000..8bc6232a92a
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonSerializableTaskListTest.java
@@ -0,0 +1,36 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.model.TaskList;
+import seedu.address.testutil.TypicalTasks;
+
+public class JsonSerializableTaskListTest {
+ private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableTaskListTest");
+ private static final Path TYPICAL_TASKS_FILE = TEST_DATA_FOLDER.resolve("typicalTasksTaskList.json");
+ private static final Path INVALID_TASK_FILE = TEST_DATA_FOLDER.resolve("invalidTasksTaskList.json");
+
+ @Test
+ public void toModelType_typicalTasksFile_success() throws Exception {
+ JsonSerializableTaskList dataFromFile = JsonUtil.readJsonFile(TYPICAL_TASKS_FILE,
+ JsonSerializableTaskList.class).get();
+ TaskList taskListFromFile = dataFromFile.toModelType();
+ TaskList typicalTasksTaskList = TypicalTasks.getTypicalTaskList();
+ assertEquals(taskListFromFile, typicalTasksTaskList);
+ }
+
+ @Test
+ public void toModelType_invalidTaskFile_throwsIllegalValueException() throws Exception {
+ JsonSerializableTaskList dataFromFile = JsonUtil.readJsonFile(INVALID_TASK_FILE,
+ JsonSerializableTaskList.class).get();
+ assertThrows(IllegalValueException.class, dataFromFile::toModelType);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonTaskListStorageTest.java b/src/test/java/seedu/address/storage/JsonTaskListStorageTest.java
new file mode 100644
index 00000000000..6394733cb76
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonTaskListStorageTest.java
@@ -0,0 +1,107 @@
+package seedu.address.storage;
+
+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.TypicalTasks.CONSULATION_CS2103;
+import static seedu.address.testutil.TypicalTasks.EXAM_INVIGILATION;
+import static seedu.address.testutil.TypicalTasks.getTypicalTaskList;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.model.ReadOnlyTaskList;
+import seedu.address.model.TaskList;
+
+public class JsonTaskListStorageTest {
+ private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonTaskListStorageTest");
+
+ @TempDir
+ public Path testFolder;
+
+ @Test
+ public void readTaskList_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> readTaskList(null));
+ }
+
+ private Optional readTaskList(String filePath) throws Exception {
+ return new JsonTaskListStorage(Paths.get(filePath)).readTaskList(addToTestDataPathIfNotNull(filePath));
+ }
+
+ private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) {
+ return prefsFileInTestDataFolder != null
+ ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder)
+ : null;
+ }
+ @Test
+ public void read_missingFile_emptyResult() throws Exception {
+ assertFalse(readTaskList("NonExistentFile.json").isPresent());
+ }
+
+ @Test
+ public void read_notJsonFormat_exceptionThrown() {
+ assertThrows(DataConversionException.class, () -> readTaskList("notJsonFormatTaskList.json"));
+ }
+
+ @Test
+ public void readAddressBook_invalidTasksTaskList_throwDataConversionException() {
+ assertThrows(DataConversionException.class, () -> readTaskList("invalidTasksTaskList.json"));
+ }
+
+ @Test
+ public void readAddressBook_invalidAndValidTasksTaskList_throwDataConversionException() {
+ assertThrows(DataConversionException.class, () -> readTaskList("invalidAndValidTaskList.json"));
+ }
+
+ @Test
+ public void readAndSaveAddressBook_allInOrder_success() throws Exception {
+ Path filePath = testFolder.resolve("TempTaskList.json");
+ TaskList original = getTypicalTaskList();
+ JsonTaskListStorage jsonTaskListStorage = new JsonTaskListStorage(filePath);
+
+ // Save in new file and read back
+ jsonTaskListStorage.saveTaskList(original, filePath);
+ ReadOnlyTaskList readBack = jsonTaskListStorage.readTaskList(filePath).get();
+ assertEquals(original, new TaskList(readBack));
+
+ // Modify data, overwrite exiting file, and read back
+ original.addTask(EXAM_INVIGILATION);
+ jsonTaskListStorage.saveTaskList(original, filePath);
+ readBack = jsonTaskListStorage.readTaskList(filePath).get();
+ assertEquals(original, new TaskList(readBack));
+
+ // Save and read without specifying file path
+ original.addTask(CONSULATION_CS2103);
+ jsonTaskListStorage.saveTaskList(original); // file path not specified
+ readBack = jsonTaskListStorage.readTaskList().get(); // file path not specified
+ assertEquals(original, new TaskList(readBack));
+ }
+
+ @Test
+ public void saveTaskList_nullTaskList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveTaskList(null, "SomeFile.json"));
+ }
+
+ /**
+ * Saves {@code addressBook} at the specified {@code filePath}.
+ */
+ private void saveTaskList(ReadOnlyTaskList taskList, String filePath) {
+ try {
+ new JsonTaskListStorage(Paths.get(filePath))
+ .saveTaskList(taskList, addToTestDataPathIfNotNull(filePath));
+ } catch (IOException ioe) {
+ throw new AssertionError("There should not be an error writing to the file.", ioe);
+ }
+ }
+
+ @Test
+ public void saveTaskList_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveTaskList(new TaskList(), null));
+ }
+}
diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java
index 99a16548970..484e311d602 100644
--- a/src/test/java/seedu/address/storage/StorageManagerTest.java
+++ b/src/test/java/seedu/address/storage/StorageManagerTest.java
@@ -26,7 +26,8 @@ public class StorageManagerTest {
public void setUp() {
JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs"));
- storageManager = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTaskListStorage taskListStorage = new JsonTaskListStorage(getTempFilePath("tasks"));
+ storageManager = new StorageManager(addressBookStorage, userPrefsStorage, taskListStorage);
}
private Path getTempFilePath(String fileName) {
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..d60d88c2969 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -5,8 +5,8 @@
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.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -35,7 +35,7 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor.setName(person.getName());
descriptor.setPhone(person.getPhone());
descriptor.setEmail(person.getEmail());
- descriptor.setAddress(person.getAddress());
+ descriptor.setUsername(person.getUsername());
descriptor.setTags(person.getTags());
}
@@ -64,10 +64,10 @@ public EditPersonDescriptorBuilder withEmail(String email) {
}
/**
- * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building.
+ * Sets the {@code Username} of the {@code EditPersonDescriptor} that we are building.
*/
- public EditPersonDescriptorBuilder withAddress(String address) {
- descriptor.setAddress(new Address(address));
+ public EditPersonDescriptorBuilder withUsername(String username) {
+ descriptor.setUsername(new GitUsername(username));
return this;
}
@@ -81,6 +81,11 @@ public EditPersonDescriptorBuilder withTags(String... tags) {
return this;
}
+ /**
+ * Builds an EditPersonDescriptor
+ *
+ * @return descriptor object
+ */
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..9d8bcb6f5fa
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/EditTaskDescriptorBuilder.java
@@ -0,0 +1,66 @@
+package seedu.address.testutil;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Task;
+
+/**
+ * A utility class to help with building EditTaskDescriptor objects.
+ */
+public class EditTaskDescriptorBuilder {
+
+ private EditTaskDescriptor descriptor;
+
+ public EditTaskDescriptorBuilder() {
+ descriptor = new EditTaskDescriptor();
+ }
+
+ public EditTaskDescriptorBuilder(EditTaskDescriptor descriptor) {
+ this.descriptor = new EditTaskDescriptor(descriptor);
+ }
+
+ /**
+ * Returns an {@code EditTaskDescriptor} with fields containing {@code task}'s details
+ */
+ public EditTaskDescriptorBuilder(Task task) {
+ descriptor = new EditTaskDescriptor();
+ descriptor.setName(task.getName());
+ descriptor.setDate(task.getDateTime());
+ descriptor.setTags(task.getTags());
+ }
+
+ /**
+ * Sets the {@code Name} of the {@code EditTaskDescriptor} that we are building.
+ */
+ public EditTaskDescriptorBuilder withTaskName(String name) {
+ descriptor.setName(name);
+ return this;
+ }
+
+ /**
+ * Sets the {@code DateTime} of the {@code EditTaskDescriptor} that we are building.
+ */
+ public EditTaskDescriptorBuilder withDateTime(LocalDateTime dateTime) {
+ descriptor.setDate(dateTime);
+ return this;
+ }
+
+ /**
+ * Parses the {@code tag} into a {@code Tag} and set it to the {@code EditTaskDescriptor}
+ * that we are building.
+ */
+ public EditTaskDescriptorBuilder withTags(String... tags) {
+ Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet());
+ descriptor.setTags(tagSet);
+ return this;
+ }
+
+ public EditTaskDescriptor build() {
+ return descriptor;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..7b47a40c7bf 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -3,8 +3,8 @@
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.GitUsername;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -19,12 +19,12 @@ 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";
+ public static final String DEFAULT_USERNAME = "AmyEnjoysCoding";
private Name name;
private Phone phone;
private Email email;
- private Address address;
+ private GitUsername gitUsername;
private Set tags;
/**
@@ -34,7 +34,7 @@ public PersonBuilder() {
name = new Name(DEFAULT_NAME);
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
- address = new Address(DEFAULT_ADDRESS);
+ gitUsername = new GitUsername(DEFAULT_USERNAME);
tags = new HashSet<>();
}
@@ -45,7 +45,7 @@ public PersonBuilder(Person personToCopy) {
name = personToCopy.getName();
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
- address = personToCopy.getAddress();
+ gitUsername = personToCopy.getUsername();
tags = new HashSet<>(personToCopy.getTags());
}
@@ -68,8 +68,8 @@ public PersonBuilder withTags(String ... tags) {
/**
* Sets the {@code Address} of the {@code Person} that we are building.
*/
- public PersonBuilder withAddress(String address) {
- this.address = new Address(address);
+ public PersonBuilder withUsername(String username) {
+ this.gitUsername = new GitUsername(username);
return this;
}
@@ -89,8 +89,13 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Builds a Person.
+ *
+ * @return Person with name, phone, email, gitUsername, tags.
+ */
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, gitUsername, tags);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..937f68383e2 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,7 +1,7 @@
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_GIT_USERNAME;
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;
@@ -33,7 +33,7 @@ public static String getPersonDetails(Person person) {
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 + " ");
+ sb.append(PREFIX_GIT_USERNAME + person.getUsername().getUsername() + " ");
person.getTags().stream().forEach(
s -> sb.append(PREFIX_TAG + s.tagName + " ")
);
@@ -48,7 +48,8 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
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(" "));
+ descriptor.getUsername()
+ .ifPresent(username -> sb.append(PREFIX_GIT_USERNAME).append(username.getUsername()).append(" "));
if (descriptor.getTags().isPresent()) {
Set tags = descriptor.getTags().get();
if (tags.isEmpty()) {
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..352fc555370
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TaskBuilder.java
@@ -0,0 +1,129 @@
+package seedu.address.testutil;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.Task;
+import seedu.address.model.util.SampleDataUtil;
+
+/**
+ * A utility class to help with building Task objects.
+ */
+public class TaskBuilder {
+
+ public static final String DEFAULT_NAME = "SchoolWork";
+ public static final LocalDateTime DEFAULT_DATETIME =
+ LocalDateTime.of(2050, 12, 15, 21, 0);
+ public static final LocalDateTime DEFAULT_ENDDATETIME =
+ LocalDateTime.of(2050, 12, 15, 22, 0);
+ public static final String DEFAULT_TAG = "School";
+ public static final String DEFAULT_ZOOMLINK = "https://google.com";
+ public static final boolean DEFAULT_ISTASKMARKDONE = false;
+
+ private String name;
+ private LocalDateTime dateTime;
+ private LocalDateTime endDateTime;
+ private Set tags;
+ private Link link;
+ private List people;
+ private boolean isTaskMarkDone;
+
+ /**
+ * Creates a {@code TaskBuilder} with the default details.
+ */
+ public TaskBuilder() {
+ name = DEFAULT_NAME;
+ dateTime = DEFAULT_DATETIME;
+ endDateTime = DEFAULT_ENDDATETIME;
+ tags = new HashSet<>();
+ link = new Link(DEFAULT_ZOOMLINK);
+ people = new ArrayList();
+ isTaskMarkDone = false;
+ }
+
+ /**
+ * Initializes the TaskBuilder with the data of {@code taskToCopy}.
+ */
+ public TaskBuilder(Task taskToCopy) {
+ name = taskToCopy.getName();
+ dateTime = taskToCopy.getDateTime();
+ endDateTime = taskToCopy.getEndDateTime();
+ tags = new HashSet<>(taskToCopy.getTags());
+ link = taskToCopy.getLink();
+ people = taskToCopy.getPeople();
+ isTaskMarkDone = taskToCopy.isTaskMark();
+ }
+
+ /**
+ * Sets the {@code Name} of the {@code Task} that we are building.
+ */
+ public TaskBuilder withTaskName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Parses the {@code tag} into a {@code Tag} and set it to the {@code Task} that we are building.
+ */
+ public TaskBuilder withTags(String ... tags) {
+ this.tags = SampleDataUtil.getTagSet(tags);
+ return this;
+ }
+
+ /**
+ * Sets the {@code DateTime} of the {@code Task} that we are building.
+ */
+ public TaskBuilder withDateTime(LocalDateTime dateTime) {
+ this.dateTime = dateTime;
+ return this;
+ }
+
+ /**
+ * Sets the {@code EndDateTime} of the {@code Task} that we are building.
+ */
+ public TaskBuilder withEndDateTime(LocalDateTime endDateTime) {
+ this.endDateTime = endDateTime;
+ return this;
+ }
+
+ /**
+ * Sets the {@code link} of the {@code Task} that we are building.
+ */
+ public TaskBuilder withLink(String link) {
+ this.link = new Link(link);
+ return this;
+ }
+
+ /**
+ * Sets the {@code link} of the {@code Task} that we are building.
+ */
+ public TaskBuilder withNoLink() {
+ this.link = new Link();
+ return this;
+ }
+
+
+ /**
+ * Sets the {@code people} of the {@code Task} that we are building.
+ */
+ public TaskBuilder withPeople(List people) {
+ this.people = people;
+ return this;
+ }
+
+ /**
+ * Builds a Task.
+ *
+ * @return Task object with the attributes in TaskBuilder
+ */
+ public Task build() {
+ return new Task(name, dateTime, endDateTime, people, tags, link, isTaskMarkDone);
+ }
+
+}
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..a0231d5d3ef
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TaskUtil.java
@@ -0,0 +1,69 @@
+package seedu.address.testutil;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LINK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASKNAME;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Set;
+
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Task;
+
+/**
+ * A utility class for Task.
+ */
+public class TaskUtil {
+ private static final String dateTimePattern = "dd-MM-yyyy HHmm";
+ private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimePattern);
+
+ /*
+ * Returns an add command string for adding the {@code task}.
+ */
+ public static String getAddCommand(Task task) {
+ return 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_TASKNAME + task.getName() + " ");
+ sb.append(PREFIX_DATETIME + formatLocalDateTime(task.getDateTime()) + " ");
+ sb.append(PREFIX_LINK + task.getLink().toString() + " ");
+ task.getTags().stream().forEach(
+ s -> sb.append(PREFIX_TAG + s.tagName + " ")
+ );
+ return sb.toString();
+ }
+
+ /**
+ * Returns the part of command string for the given {@code EditTaskDescriptor}'s details.
+ */
+ public static String getEditTaskDescriptorDetails(EditTaskDescriptor descriptor) {
+ StringBuilder sb = new StringBuilder();
+ descriptor.getName().ifPresent(name -> sb.append(PREFIX_TASKNAME).append(name).append(" "));
+ descriptor.getDate().ifPresent(date -> sb.append(PREFIX_DATETIME)
+ .append(formatLocalDateTime(date)).append(" "));
+ descriptor.getLink().ifPresent(link -> sb.append(PREFIX_LINK).append(link).append(" "));
+
+ if (descriptor.getTags().isPresent()) {
+ Set