diff --git a/README.md b/README.md index 13f5c77403f..82be72608ef 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,178 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) - -![Ui](docs/images/Ui.png) - -* 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. +[![codecov](https://codecov.io/gh/AY2122S2-CS2103T-W11-4/tp/branch/master/graph/badge.svg?token=TO5WF437LI)](https://codecov.io/gh/AY2122S2-CS2103T-W11-4/tp) + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Issues][issues-shield]][issues-url] + + +
+
+ + + + +

NUSearch

+ +

+ A GUI application to help you quickly search for the contacts of NUS faculty members! +
+ Explore the docs » +
+
+ View Demo + · + Report Bug + · + Request Feature +

+
+ + + + +
+ Table of Contents +
    +
  1. + About The Project + +
  2. +
  3. + Getting Started + +
  4. +
  5. Usage
  6. +
  7. Roadmap
  8. +
  9. Contributing
  10. +
  11. Contact
  12. +
  13. Acknowledgments
  14. +
+
+ + + + +## About The Project + +NUSearch is a GUI application that allows students to easily and conveniently find the contacts of faculty members so that +they may contact members of faculty more easily. + +The product is not an official NUS project, although the project was conceived by and worked on by NUS students +during the CS2103T module. + +

(back to top)

+ +### Built With + +This section should list any major frameworks/libraries used to bootstrap your project. Leave any add-ons/plugins for the acknowledgements section. Here are a few examples. + +* [java](https://www.java.com/) +* [javaFX](https://openjfx.io/) + + +

(back to top)

+ + + + +## Getting Started + +Read the getting started segment to understand how to get started on using NUSearch + + +### Installation + +Follow these steps closely to get started. + +1. Download our latest .jar file here +2. Locate the .jar file in your downloads folder +3. Open the .jar file to run the application + + + + +## Usage + +_For more usage examples, please refer to the [Documentation](https://github.com/AY2122S2-CS2103T-W11-4/tp#readme)_ + +

(back to top)

+ + + + +## Roadmap + +- [ ] Add Changelog +- [x] Add back to top links +- [ ] Add Additional Templates w/ Examples +- [ ] Add "components" document to easily copy & paste sections of the readme + +See the [open issues](https://github.com/AY2122S2-CS2103T-W11-4/tp/issues) for a full list of proposed features (and known issues). + +

(back to top)

+ + + + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". +Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +

(back to top)

+ + + +## Contact + +Eugene Chia - [@linkedin](https://twitter.com/your_username) - email@example.com + +Sim Jia Ming - [@linkedin](https://twitter.com/your_username) - email@example.com + +Shurvir Arora - [@linkedin](https://twitter.com/your_username) - email@example.com + +Tan Wei En - [@linkedin](https://twitter.com/your_username) - email@example.com + + +Project Link: [https://github.com/AY2122S2-CS2103T-W11-4/tp](https://github.com/AY2122S2-CS2103T-W11-4/tp) + +

(back to top)

+ + + + +## Acknowledgments + +The team would like to give a big shout out these projects, from which we have used parts of in our project. Special shout out to +you Prof. Damith <3. + +* [AddressBook 3](https://se-education.org) +* [Img Shields](https://shields.io) +* [GitHub Pages](https://pages.github.com) + +

(back to top)

+ + + + + +[contributors-shield]: https://img.shields.io/github/contributors/AY2122S2-CS2103T-W11-4/tp.svg +[contributors-url]: https://github.com/AY2122S2-CS2103T-W11-4/tp/people +[forks-shield]: https://img.shields.io/github/forks/AY2122S2-CS2103T-W11-4/tp.svg +[forks-url]: https://github.com/AY2122S2-CS2103T-W11-4/tp/network/members +[issues-shield]: https://img.shields.io/github/issues/AY2122S2-CS2103T-W11-4/tp.svg +[issues-url]: https://github.com/AY2122S2-CS2103T-W11-4/tp/issues + diff --git a/build.gradle b/build.gradle index be2d2905dde..35e412a28e2 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'NUSearch.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..376d3ee0a48 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,42 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Shurvir Arora - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +* [[github](http://github.com/shurvirarora)] +* [[portfolio](team/shurvirarora.md)] -* Role: Project Advisor - -### Jane Doe - - - -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] - -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: `Logic`, `Model` components. -### Johnny Doe +### Eugene Chia - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +* [[github](http://github.com/eugenechiaay)] +* [[portfolio](team/eugenechiaay.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: `Logic`, `Storage` components. -### Jean Doe +### Jia Ming - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +* [[github](http://github.com/simjm)] +* [[portfolio](team/simjm.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: `Model`, `UI` components. -### James Doe +### Wei En - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +* [[github](http://github.com/tanweien)] +* [[portfolio](team/tanweien.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: `Logic`, `Testing` components. diff --git a/docs/DevOps.md b/docs/DevOps.md index 26354050fa4..44d0b59beda 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -43,8 +43,7 @@ This project uses GitHub Actions for CI. The project comes with the necessary Gi As part of CI, this project uses Codecov to generate coverage reports. When CI runs, it will generate code coverage data (based on the tests run by CI) and upload that data to the CodeCov website, which in turn can provide you more info about the coverage of your testes. Here are the steps to set up CodeCov for a fork of this repository. 1. Sign up with Codecov using your GitHub account [here](https://codecov.io/signup). -1. Once you are inside Codecov web app, add your org (that contains the fork) to CodeCov. -1. Wait for the next run of CI in your fork (or push a dummy commit to it to trigger CI) to confirm CI is able to upload generated coverage data to CodeCov. If CodeCov is not set up correctly, the CI run will fail with an error message that mentions CodeCov. +1. Once you are inside Codecov web app, add your fork to CodeCov. 1. Get the Markdown code for the Codecov badge provided in `Settings > Badges` and update the `docs/index.md` of your repo with it so that the badge [![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) in that page reflects the coverage of your project. ### Repository-wide checks diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..d9951fe6ffa 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,17 +1,68 @@ --- layout: page -title: Developer Guide +title: NUSearch Developer Guide --- -* Table of Contents -{:toc} - -------------------------------------------------------------------------------------------------------------------- + +
+ Table of Contents +
    +
  1. + Acknowledgements +
  2. +
  3. + Setting up, Getting started +
  4. +
  5. + Design + +
  6. +
  7. + Implementation + +
  8. +
  9. + Documentation, logging, testing, configuration, dev-ops +
  10. +
  11. + Appendix + +
  12. +
  13. Instructions for manual testing
  14. +
+
+ ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* Adapted from [AddressBook3](https://github.com/nus-cs2103-AY2122S2/tp) --------------------------------------------------------------------------------------------------------------------- +___________________________________________________________________________________________________________________ ## **Setting up, getting started** @@ -19,20 +70,24 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- -## **Design** +## Design -
+> :bulb: **TIP:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2122S2-CS2103T-W11-4/tp/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](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. -
### Architecture - +
+ + + +
1.1. Architecture Diagram
+
The ***Architecture Diagram*** given above explains the high-level design of the App. -Given below is a quick overview of main components and how they interact with each other. + +Given below is a quick overview of Main components and how they interact with each other. **Main components of the architecture** @@ -42,102 +97,167 @@ Given below is a quick overview of main components and how they interact with ea [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. -The rest of the App consists of four components. +The rest of the App consists of five components. * [**`UI`**](#ui-component): The UI of the App. * [**`Logic`**](#logic-component): The command executor. * [**`Model`**](#model-component): Holds the data of the App in memory. * [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. - +* [`CommandManager`](#command-manager-component): Receives commands from the logic component and handles its execution/un-execution appropriately. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command delete 1. + +
+ + + +
Architecture Sequence Diagram (Delete command)
+
+ +Each of the five main components (also shown in the diagram above), - +* defines its *API* in an interface with the same name as the Component. +* implements its functionality using a concrete {Component Name}Manager class (which follows the corresponding API interface mentioned in the previous point. -Each of the four main components (also shown in the diagram above), +> :bulb: Eg. The Model component defines its API in the Model.java interface and implements its functionality using the ModelManager.java class which follows the Model interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. -* defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +The following section gives more details of each component. -For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. +### Components - +
+ + + +
Component Managers
+
-The sections below give more details of each component. +#### **The UI Component** -### UI component +**API** : [`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/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +
+ + + +
UI Class Diagram
+
-![Structure of the UI Component](images/UiClassDiagram.png) +The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI. -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `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/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, -The `UI` component, +* executes user commands using the Logic component. +* listens for changes to Model data so that the UI can be updated with the modified data. +* keeps a reference to the Logic component, because the UI relies on the Logic to execute commands. +* depends on some classes in the Model component, as it displays Person object residing in the Model. -* executes user commands using the `Logic` component. -* listens for changes to `Model` data so that the UI can be updated with the modified data. -* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +> :memo: **NOTE** The Command Manager component is not reflected in the diagram since there are no direct dependencies with the UI component -### Logic component +#### **The Logic Component** **API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: - +
+ + + +
Logic Class Diagram (Partial)
+
How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. The command can communicate with the `CommandManager` when it is executed (e.g. to add a person). +1. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`. The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) - -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
+ + + +
Delete Command Sequence Diagram
+> :memo: **NOTE:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: - +
+ + + +
Parser Class Diagram
+
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) +#### **The CommandManageable Component** + +**API** : [`CommandManageable.java`](https://github.com/eugenechiaay/tp/blob/master/src/main/java/seedu/address/logic/commands/CommandManageable.java) + +
+ + + +
CM Class Diagram
+
- +The `CommandManageable` component, +* receives command objects from the `Logic` component e.g. a `DeleteCommand`. +* keeps track of commands and states in a stack. +* any `UndoCommand` or `RedoCommand` it receives will be processed differently from other commands. +* a component between logic and model, commands are executed and un-execute here. + +> :bulb: **COOL:** The Command Manager component was built as a means to support the newly implemented undo and redo functions! + +#### **The Model Component** + +**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) + +
+ + + +
Model Class Diagram
+
The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). +* stores the NUSearch 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 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.
- - +>:memo: **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.
+
+ + + +
Better Model Class Diagram
- -### Storage component +#### **The Storage Component** **API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) - +
+ + + +
Storage Class Diagram
+
The `Storage` component, * can save both address book data and user preference data in json format, and read them back into corresponding objects. @@ -152,92 +272,178 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa ## **Implementation** -This section describes some noteworthy details on how certain features are implemented. +### Add Person feature -### \[Proposed\] Undo/redo feature +This section describes how a `Person` object is added to the list of Contacts. -#### Proposed Implementation +**Implementation** -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: +A `Person` object in NUSearch consists of `Name`, `Phone`, `Faculty`, `Email`, `Role`, `Faculty`, `Tag`, `Telegram`. The latter two fields are Optional fields. +When a `add` command is being input to the command input box, a `Person` will be added to the `UniquePersonsList`. -* `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. +Here is how an example of how the `add` command behaves: -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +1. The user inputs - `add n/Shurvir Arora p/92212429 e/hello@gmail.com r/Professor f/Science t/Hello tele/@Shuvy123`. +2. The user's input is received by the `LogicManager` class and passed into the `parseCommand` method of the `AddressBookParser` class. +3. In the `parseCommand` method, the `add` command format is being matched. +5. An `AddCommand` object is created using the user's input as arguments. +6. The `AddCommand` object is then returned to the `LogicManager` class and then passed to the `CommandManager` class. +7. The `AddCommand` is executed in the `CommandManager` class, whereby a `Person` object with the given fields is constructed and added to the `UniquePersonsList`. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +**Sequence Diagram** -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +The given sequence diagram shows the execution of the feature. -![UndoRedoState0](images/UndoRedoState0.png) +
+ + + +
Add Sequence 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. +**Activity Diagram** -![UndoRedoState1](images/UndoRedoState1.png) +
+ + + +
Add Activity Diagram
+
-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`. +### Favourite/Un-favourite Person Feature -![UndoRedoState2](images/UndoRedoState2.png) +This section describes how a `Person` object is "favourited" or "Un-favourited". -
: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`. +**Implementation** -
+Apart from the fields stated above, a `Person` object also has a `Favourite` boolean field to indicate whether a person is a favourite one or not. -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. +Here is how an example of how the `fav` command behaves: -![UndoRedoState3](images/UndoRedoState3.png) +1. The user inputs - `fav 1`. +2. The user's input is received by the `LogicManager` class and passed into the `parseCommand` method of the `AddressBookParser` class. +3. In the `parseCommand` method, the `fav` command format is being matched. +4. A `FavouriteCommand` object is created using the user's input as arguments. +5. The `FavouriteCommand` object is then returned to the `LogicManager` class and then passed to the `CommandManager` class. +6. The `FavouriteCommand` is executed in the `CommandManager` class, whereby a clone of the target `Person` is created, albeit its `Favourite` field set to true. -
: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. +> The programme behaves in a similar way for "Unfavourite" command. -
+**Sequence Diagram** -The following sequence diagram shows how the undo operation works: +The given sequence diagram shows the execution of the feature. -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +
+ + + +
Fav Sequence 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. +**Activity Diagram** +
+ + + +
Fav Activity Diagram
-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. +### Find Person(s) by Tag Feature + +This section describes how the find by `Tag` feature works. + +**Implementation** + +The find by`Tag` feature operates through the use of the `Predicate` class. Where a `FilteredPersonsList` is updated using a `Predicate` object. + +Here is how an example of how the `tag` command behaves: + +1. The user inputs - `tag friends`. +2. The user's input is received by the `LogicManager` class and passed into the `parseCommand` method of the `AddressBookParser` class. +3. In the `parseCommand` method, the `tag` command format is being matched. +4. The user's input is used to create a `Predicate` object. With this `Predicate` object, a `TagCommand` is created. +5. The `TagCommand` object is then returned to the `LogicManager` class and then passed to the `CommandManager` class. +6. The `TagCommand` is executed in the `CommandManager` class, whereby the `FilteredPersonsList` is update with the `Predicate` object. -
: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. +**Sequence Diagram** +The given sequence diagram shows the execution of the feature. + +
+ + + +
Tag Sequence Diagram
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +### Undo/Redo Command Feature + +This section describes how the Undo/Redo feature works. + +**Implementation** + +Here is how an example of how the `Undo/Redo` command behaves: + +Example starting state with 2 states prior: -![UndoRedoState4](images/UndoRedoState4.png) +
+ + + +
Undo Redo Initial State Diagram
+
-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. +1. The user inputs - `Undo`. +2. The user's input is received by the `LogicManager` class and passed into the `parseCommand` method of the `AddressBookParser` class. +3. In the `parseCommand` method, the `undo` command format is being matched. +4. An `UndoCommand` is created. +5. The `UndoCommand` object is then returned to the `LogicManager` class and then passed to the `CommandManager` class. +6. The `UndoCommand` is executed in the `CommandManager` class, whereby the state of the `Addressbook` is updated to the previous state. + +
+ + + +
Undo Redo State After Redo Diagram
+
-![UndoRedoState5](images/UndoRedoState5.png) +**Sequence Diagram** -The following activity diagram summarizes what happens when a user executes a new command: +The given sequence diagram shows the execution of the feature. - +
+ + + +
Undo Sequence Diagram
+
-#### Design considerations: +### Copy Email/Phone Number Feature -**Aspect: How undo & redo executes:** +This section describes how the Copy Email/Phone Number feature works. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +**Implementation** -* **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. +Here is how an example of how the Copy Email/Phone Number command behaves: -_{more aspects and alternatives to be added}_ +1. The user inputs - `copy-email 2`. +2. The user's input is received by the `LogicManager` class and passed into the `parseCommand` method of the `AddressBookParser` class. +3. In the `parseCommand` method, the `copy-email` command format is being matched. +4. A `CopyEmailCommand` command is created. +5. The `CopyEmailCommand` object is then returned to the `LogicManager` class and then passed to the `CommandManager` class. +6. The `CopyEmailCommand` is executed in the `CommandManager` class, whereby the `Email` of a `Person` object at the specified index is copied unto the device's clipboard. -### \[Proposed\] Data archiving +**Sequence Diagram** -_{Explain here how the data archiving feature will be implemented}_ +The given sequence diagram shows the execution of the feature. +
+ + + +
Copy Email Sequence Diagram
+
-------------------------------------------------------------------------------------------------------------------- @@ -251,48 +457,184 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## **Appendix** + +**Requirements** ### Product scope **Target user profile**: -* 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 +* Has a need to manage a significant number of contacts +* Prefers desktop apps over other types +* Can type fast +* Prefers typing to mouse interactions +* Is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: manage NUS contacts faster than a typical mouse/GUI driven app ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +| Priority | As a …​ | I want to …​ | So that I can…​ | +|-----| ------------------------------------------ |-----------------------------------------------|-----------------------------------------------------------------------------| +| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | +| `* * *` | user | add a new staff | | +| `* * *` | user | delete a staff | remove entries that I no longer need | +| `* * *` | user | find a staff by name | locate details of staff without having to go through the entire list | +| `* * *` | user | find a staff by tag | locate details of staff associated with the tag without knowing their names | +| `* * *` | user | list all staff contacts | view all the staff contacts in NUSearch | +| `* * ` | user | add an existing staff to favourites list | | +| `* * ` | user | delete an existing staff from favourites list | | +| `* * ` | user | list all favourite contacts | view all my favourite staff contacts | +| `* *` | 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 | +| `*` | user | click on hyperlinks on a contact | contact staff easily | *{More to be added}* ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is the `NUSearch` and the **Actor** is the `user`, unless specified otherwise) + +**Use case 1: Viewing help** + +**MSS** + +1. User requests to access the help page +2. NUSearch shows list of commands and syntax on how to use them + + Use case ends. + + +**Use case 2: Adding a contact** + +**MSS** + +1. User requests to add contact to the list +2. NUSearch adds the contact in the database. + + Use case ends. + +**Extensions** + +* 2a. The given contact exist in the NUSearch database. + + * 2a1. NUSearch shows an error message. + + Use case resumes at step 2. -**Use case: Delete a person** + + +**Use case 3: Listing all contacts** **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 all contacts. +2. NUSearch shows a list of contacts. + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + + +**Use case 4: Locating contacts by name** + +**MSS** + +1. User requests to find contacts with name. +2. NUSearch shows a list of contacts with the name. + + Use case ends. + +**Extensions** + +* 2a. The given name does not exist in the NUSearch database. + + * 2a1. NUSearch shows an error message. + + Use case resumes at step 2. + +**Use case 5: Locate contact by tag** + +**MSS** + +1. User requests to list all contacts that contain the specified tag +2. NUSearch shows a list of contacts + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + + +**Use case 6: Add contacts to favourites list** + +**MSS** + +1. User requests to add a contact to the favourites list +2. NUSearch shows an updated list of the user's favourite contacts + + Use case ends. + +**Extensions** + +* 1a. The contact specified does not exist in the system. + + The system prompts the user to key in another contact to be added + +**Use case 7: Remove contacts from favourites list** + +**MSS** + +1. User requests to remove a contact from the favourites list +2. NUSearch shows an updated list of the user's favourite contacts + + Use case ends. + +**Extensions** + +* 1a. The contact specified does not exist in the system. + + The system prompts the user to key in another contact to be added + +* 2a. The list is empty. + + Use case ends. + +**Use case 8: Listing all favourite contacts** + +**MSS** + +1. User requests to list all favourite contacts. +2. NUSearch shows a list of contacts. + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + + +**Use case 9: Delete a contact** + +**MSS** + +1. User requests to list contacts +2. NUSearch shows a list of contacts +3. User requests to delete a specific contact in the list +4. NUSearch deletes the contact Use case ends. @@ -304,17 +646,26 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 3a. The given index is invalid. - * 3a1. AddressBook shows an error message. + * 3a1. NUSearch shows an error message. Use case resumes at step 2. -*{More to be added}* +**Use case 10: Exit the program** + +**MSS** + +1. User requests to exit the program +2. NUSearch says bye + + Use case ends. ### Non-Functional Requirements -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -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. +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. +4. Should be usable by a novice who is unfamiliar with typing commands. +5. Should quickly display requested information within 2 seconds. *{More to be added}* @@ -322,10 +673,13 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * **Mainstream OS**: Windows, Linux, Unix, OS-X * **Private contact detail**: A contact detail that is not meant to be shared with others +* **Command-line interface**: A command-line interface processes commands to a computer program in the form of lines of text +* **Student**: A student from NUS +* **Staff**: Staff member of NUS, including teaching assistants and administrative staff -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## **Instructions for manual testing** Given below are instructions to test the app manually. @@ -338,40 +692,43 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. 1. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ ### Deleting a person 1. Deleting a person while all persons are being shown - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 1. Test case: `delete 1`
+ Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + + 1. Test case: `delete 0`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. - 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. +### Adding a person -1. _{ more test cases …​ }_ +1. Adding a person while all persons are being shown -### Saving data + 1. Prerequisites: Person to be added is not a duplicate person. -1. Dealing with missing/corrupted data files + 1. Test case: `add n/Sim sim e/simsim@gmail.com p/92214993 r/TA f/FASS`
+ Expected: Sim Sim is added. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ + 1. Other incorrect add commands to try: `add`, `add n/nameWithoutEmailOrPhone`, `...`
+ Expected: Error thrown. -1. _{ more test cases …​ }_ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..1c91dcd4e05 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,192 +1,691 @@ --- layout: page -title: User Guide +title: NUSearch 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} +NUSearch is a **desktop app for managing NUS staff 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, NUSearch can get your contact management tasks done faster than traditional GUI applications. -------------------------------------------------------------------------------------------------------------------- +## Introduction -## Quick start - -1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). + NUS students may find it tough to manage university related contacts. + Despite platforms such as Luminus that provides students with relevant contact information for modules, there is no dedicated system that is customised to assist students in maintaining their NUS contacts. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +> Examples of how NUSearch operates as a university specific contact list: +>1. Faculty and role are mandatory fields to be filled in to make sure that each contact has a given faculty and role. +>2. As NUS students and staff often use Telegram as a form a communication, there is an optional field to store each contact's telegram username. +>3. Each user is able to filter out their contacts based on faculty and role. -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.
- ![Ui](images/Ui.png) +-------------------------------------------------------------------------------------------------------------------- -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: + +
+ Table of Contents +
    +
  1. + Quickstart +
  2. +
  3. + Notes before use +
  4. +
  5. + Commands +
    + General Commands +
      +
    1. Exit NUSearch
    2. +
    3. View Help Page
    4. +
    +
    +
    + Basic Commands +
      +
    1. Add a contact
    2. +
    3. Clear all contacts
    4. +
    5. Delete a contact
    6. +
    7. Edit a contact
    8. +
    9. List all contacts
    10. +
    11. Undo a command
    12. +
    13. Redo a command
    14. +
    +
    +
    + Find Commands +
      +
    1. Find contacts matching ALL keywords
    2. +
    3. Find contacts matching ANY keywords
    4. +
    5. Find contacts by tags
    6. +
    +
    +
    + Favourite Commands +
      +
    1. Favourite a contact
    2. +
    3. List all favourite contacts
    4. +
    5. Unfavourite a contact
    6. +
    +
    +
    + Copy Commands +
      +
    1. Copy a contact's email address
    2. +
    3. Copy a contact's phone number
    4. +
    +
    +
  6. +
  7. + Data matters + +
  8. +
  9. + Frequently Asked Questions (FAQ) +
      +
    +
  10. +
  11. + Command Summary +
      +
    +
  12. +
+
+ +## Quick start: + +1. Ensure that you have Java `11` or above installed in your Computer. + + +2. Download the latest `NUSearch.jar` from [here](https://github.com/AY2122S2-CS2103T-W11-4/tp/releases). + + +3. Copy the file to the folder you want to use to store NUSearch. + + +4. Double-click the file to start the app. The GUI (aka Screen) similar to the one below should appear in a few seconds.
+ +> :rainbow: **Colour Scheme:** NUSearch's colour scheme is blue and orange to match the university's traditional colours. + +
+ + + +
Figure 1. NUSearch home page
+
- * **`list`** : Lists all contacts. - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list. - * **`clear`** : Deletes all contacts. + > Some example commands you can try: - * **`exit`** : Exits the app. + > * **`list`** : Lists all contacts. + > * **`add`**`n/John Doe p/98765432 e/johnd@example.com f/Computing r/Professor` : Adds a contact named `John Doe` to the NUSearch. + > * **`delete`**`3` : Deletes the 3rd contact shown in the current list. + > * **`clear`** : Deletes all contacts. + > * **`exit`** : Exits the app. -1. Refer to the [Features](#features) below for details of each command. +_____________________________________________________ --------------------------------------------------------------------------------------------------------------------- -## Features +## Notes before use:
-**:information_source: Notes about the command format:**
+* Words in `UPPER_CASE` are the information to be supplied by the user.
+ e.g. in `add n/NAME`, + > Example: `NAME` is a type of input which can be used as `add n/John Doe`. -* 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`. * 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 `n/NAME [tele/USERNAME]`, + > Example: `[tele/TELEGRAM]` is an optional input, hence`n/John Doe tele/@JohnDoe` and `n/John Doe` are both valid commands. + + +* Items with `…`​ after them can be used multiple times or not at all (i.e zero times).
+ e.g. `[t/TAG]…​`, + > Example: `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/CS2103T`, `t/CS2103T t/friend` etc. + + +* Data being entered after the command can be in any order.
+ > Example: The command `add n/NAME p/PHONE_NUMBER e/EMAIL f/FACULTY r/ROLE` is the same as `add p/PHONE_NUMBER n/NAME f/FACULTY e/EMAIL r/ROLE`. + -* 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. +* If the data entered is expected to be keyed in only once in the command, but you specified it multiple times, only the last occurrence of the data will be taken.
+ > Example: The Phone Number field only stores 1 phone number. Hence, if you entered 2 phone numbers `p/12341234 p/56785678`, only the last entry `p/56785678` will be stored. -* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +* Extraneous input for commands that do not take in additional information (such as `help`, `list`, `exit` and `clear`) will be ignored.
+ > Example: if the command specifies `help 123`, it will be interpreted as `help`. + +* Faculty and Role fields can **only** take the following values as input: + +| Faculty | Role | +|:---------:|:----------:| +| Business | Admin | +| CDE | Lecturer | +| CHS | Professor | +| Computing | Researcher | +| Dentistry | TA | +| Law | Tutor | +| Medicine | Other | +| Pharmacy | +| Music | +| Others | -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-### Viewing help : `help` +__________________________________________________________________________________________________________________________________ + +## Commands: + +
+ Command List +
    +
    + General Commands +
      +
    1. Exit NUSearch
    2. +
    3. View Help Page
    4. +
    +
    +
    + Basic Commands +
      +
    1. Add a contact
    2. +
    3. Clear all contacts
    4. +
    5. Delete a contact
    6. +
    7. Edit a contact
    8. +
    9. List all contacts
    10. +
    11. Undo a command
    12. +
    13. Redo a command
    14. +
    +
    +
    + Find Commands +
      +
    1. Find contacts matching ALL keywords
    2. +
    3. Find contacts matching ANY keywords
    4. +
    5. Find contacts by tags
    6. +
    +
    +
    + Favourite Commands +
      +
    1. Favourite a contact
    2. +
    3. List all favourite contacts
    4. +
    5. Unfavourite a contact
    6. +
    +
    +
    + Copy Commands +
      +
    1. Copy a contact's email address
    2. +
    3. Copy a contact's phone number
    4. +
    +
    +
+
+ +## General Commands + +### Exiting the program : `exit` + +Exits the program. + +Format: `exit` -Shows a message explaning how to access the help page. +### View help : `help` -![help message](images/helpMessage.png) +Shows a summative list of available commands for you to input. + +
+ + + +
Figure 2. Help message screen
+
Format: `help` +> :bulb: **TIP:** Click on `Copy URL button to copy the link to our user guide.` -### Adding a person: `add` +## Basic Commands -Adds a person to the address book. +### Add a contact: `add ...` -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Adds a contact to the contact list. -
:bulb: **Tip:** -A person can have any number of tags (including 0) +
+ + + +
Figure 3. Add a contact
+Format: `add n/NAME p/PHONE_NUMBER e/EMAIL f/FACULTY r/ROLE [tele/TELEGRAM] [t/TAG]…​` + +> :spiral_notepad: **NOTE:** When adding faculty and role of a contact, only the following [values](#acceptable-values) are accepted as input. +> +> :bulb: **TIP:** A contact can have any number of tags, or none at all. + 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` +* `add n/Shurvir Arora p/98765432 e/shurvir@example.com f/Computing r/Professor` +* `add n/Betsy Crowe p/98193898 e/betsycrowe@example.com f/Law r/TA tele/@BetsyCrowe t/CS2103T t/Friend` -### Listing all persons : `list` +### Clear all contacts : `clear` -Shows a list of all persons in the address book. +Clears all contacts from the NUSearch database. -Format: `list` +Format: `clear` + +> :exclamation: **CAUTION:** This command clears **ALL** contacts in the NUSearch database! +> +> :bulb: **TIP:** Accidentally cleared the database? Don't worry, checkout our undo function! + +### Delete a contact : `delete ...` + +Deletes a contact from the contact list by an index. + +
+ + + +
Figure 4. Delete a contact
+
+ +* Deletes the person at the specified `INDEX`. +* The index refers to the index number shown in the displayed person list. +* The index **must be a positive integer** 1, 2, 3, …​ + +Format: `delete INDEX` -### Editing a person : `edit` +> :bulb: **TIP:** Accidentally deleted the wrong contact? Don't worry, checkout our undo function! -Edits an existing person in the address book. +Examples: +* `delete 2` deletes the 2nd person in the current displayed list. +* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. + +### Edit a contact : `edit ...` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Edits an existing contact in NUSearch database. + +
+ + + +
Figure 5. Edit a contact's information
+
* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* Existing values will be updated according to the input values. +* When editing tags, the existing tags 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. + +Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [f/FACULTY] [r/ROLE] [tele/TELEGRAM] [t/TAG]…​` + +> :spiral_notepad: **NOTE:** When editing the faculty and role of a contact, only the following [values](#acceptable-values) are accepted as input. +> +> :bulb: **TIP:** The edit command allows you to edit multiple fields of a single contact in one command. + +Examples: +* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st contact in the current displayed list to be `91234567` and `johndoe@example.com` respectively. +* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd contact in the current displayed list to be `Betsy Crower` and clears all existing tags. +* `edit 3 f/Computing r/TA` Edits the faculty of the 3rd contact in the current displayed list to be `Computing` and role to be `TA` +* `edit 1 tele/@hackerway101` Edits the Telegram username of the 1st contact in the current displayed list to `@hackerway101` + +### List all contacts : `list` + +Displays all contacts in the contact list. + +
+ + + +
Figure 6. List all contacts
+
+ +Format: `list` + +> :spiral_notepad: **NOTE:** Contacts listed will be sorted according to the time of addition. + +### Undo a command : `undo` + +Undo a command that was entered previously. + +
+ + + +
Figure 7. Undo a delete command
+
+ +Format: `undo` + +> :bulb: **TIP:** This function only works if there are commands to undo. +> +> :spiral_notepad: **NOTE:** If no command has been previously entered, the undo command will not work. +> `Undo` does not work on `copy-email` and `copy-phone` commands. 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. +* `If you just added a person named John Doe, you can simply revert that action by keying in "undo".` +* `If you just deleted a person named Jessica Tan, you can simply revert that action by keying in "undo".` + +### Redo a command : `redo` -### Locating persons by name: `find` +Redo a command that was previously done. + +
+ + + +
Figure 8. Redo a delete command
+
-Finds persons whose names contain any of the given keywords. +Format: `redo` + +> :bulb: **TIP:** This function only works if there are commands to redo. +> +> :spiral_notepad: **NOTE:** The “redo” command is the inverse of the “undo” command. It redoes an action that was undone. +> This is valuable if you accidentally execute the "undo" command too many times. +> `Redo` does not work on `copy-email` and `copy-phone` commands. + +Examples: +* `If you just added a person named John Doe, proceeded to undo that action, and then perform the "redo" command, the person John Doe will still be added as a contact.` + +## Find Commands + +### Find contacts matching **ALL** keywords : `find ...` + +Find contacts that contain **ALL** the given keywords. + +
+ + + +
Figure 9. Finding contacts with the keywords Daniel and TA
+
+ +* Keywords can match names, faculty, role e.g. `Computing` will return all contacts with `Computing` in either their name, faculty or role field. +* The search is case-insensitive. e.g `shur` will match `Shur` +* The order of the keywords do not matter. e.g. `Wei En` will match `En Wei` +* Only full words will be matched e.g. `Jiamin` will not match `Jiaming` +* Only persons matching **ALL** keywords will be returned e.g. `find David Computing` will only return contacts with `David` **AND** `Computing` as part of their keywords. Format: `find KEYWORD [MORE_KEYWORDS]` -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +> :bulb: **TIP:** Use more keywords if you want to **narrow** your search down to a specific contact. Examples: * `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `find John Doe` returns `John Doe Lee`, `John Loo Doe` but not `John Moo` +* `find David Computing` returns `David` from `Computing` but not `David` from `Business` +* `find David Professor` returns contacts with the name `David` **and** have `Professor` as their role. -### Deleting a person : `delete` +### Find contacts matching **ANY** keywords : `find-wide ...` -Deletes the specified person from the address book. +Find contacts that contain **ANY** the given keywords. -Format: `delete INDEX` +
+ + + +
Figure 10. Finding contacts with the keywords Daniel and TA
+
-* Deletes the person at the specified `INDEX`. +* Keywords can match names, faculty, role e.g. `Computing` will return all contacts with `Computing` in either their name, faculty or role field. +* The search is case-insensitive. e.g `shur` will match `Shur` +* The order of the keywords do not matter. e.g. `Wei En` will match `En Wei` +* Only full words will be matched e.g. `Jiamin` will not match `Jiaming` +* Persons matching at least one keyword will be returned e.g. `find-wide Eug ene` will return `Eug in`, `Nal g ene` and `ene eug` + +Format: `find-wide KEYWORD [MORE_KEYWORDS]` + +> :bulb: **TIP:** Use more keywords if you want to **broaden** your search range. + +Examples: +* `find-wide John` returns `john` and `John Doe` +* `find-wide John Doe` returns `John Doe Lee`, `John Loo Doe` and `John Moo` +* `find-wide David Computing` returns **ALL** contacts with the name `David` **or** are from `Computing` +* `find-wide David Professor` returns **ALL** contacts with the name `David` **or** have `Professor` as their role + +### Find contacts by tags: `tag ...` + +Find contacts whose attributed tags meet the given keywords. + +
+ + + +
Figure 11. Finding contacts with the 'friend' tag
+
+ +* The search is case-insensitive. e.g `colleague` will match `Colleague` +* Only tags are included in the search, other fields are ignored. + +Format: `tag TAG [MORE_TAGS]` + +> :bulb: **TIP:** Attaching tags to a contact is a way for you to attach your own meaning to the contact. +> +>For example, adding the `CS2103T` tag to your professor's contact to indicate that this professor teaches the `CS2103T` module. + +Examples: +* `tag CS2103T` Lists all contacts in the current displayed list that have the `CS2103T` tag. +* `tag colleague bestie` Lists all contacts in the current displayed list that have the `colleague` or `bestie` tag. + +## Favourite Commands + +### Favourite a contact : `fav ...` + +Adds a contact to the favorite list. + +
+ + + +
Figure 12. Favourite contact
+
+ +Format: `fav INDEX` + +> :bulb: **TIP:** Use this function on contacts that you view frequently! + +Examples: +* `fav 1` adds the 1st contact in the current displayed list to the favourites list. +* `fav 2` adds the 2nd contact in the current displayed list to the favourites list. + +### List all favourite contacts : `list-fav` + +Displays all favourite contacts in the contact list. + +
+ + + +
Figure 13. List favourite contacts
+
+ +Format: `list-fav` + +### Unfavourite a contact : `unfav ...` + +Removes a contact from the favorite list. + +
+ + + +
Figure 14. Favourite contact
+
+ +Format: `unfav INDEX` + +Examples: +* `unfav 1` unfavourites the 1st contact from the current displayed list. +* `unfav 2` unfavourites the 2nd contact from the current displayed list. + +## Copy Commands + +### Copy email address : `copy-email ...` + +Copies a contact's email address to your clipboard. + +
+ + + +
Figure 15. Copy a contact's email address
+
+ +* Copies the email address of the contact 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, …​ -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. +Format: `copy-email INDEX` -### Clearing all entries : `clear` +> :bulb: **TIP:** You can paste the copied email into any text field using the shortcut key combination `Ctrl + V` on a PC or `Command + V` on a Mac. -Clears all entries from the address book. +Examples +* `copy-email 1 will copy the email of the contact at index 1.` +* `copy-email 4 will copy the email of the contact at index 4.` -Format: `clear` +### Copy phone number : `copy-phone ...` -### Exiting the program : `exit` +Copies a contact's phone number to your clipboard. -Exits the program. +
+ + + +
Figure 16. Copy a contact's phone number
+
-Format: `exit` +* Copies the phone number of the contact 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, …​ + +Format: `copy-phone INDEX` + +> :bulb: **TIP:** You can paste the copied phone number into any text field using the shortcut key combination `Ctrl + V` on a PC or `Command + V` on a Mac. + +Examples +* `copy-phone 1 will copy the phone number of the contact at index 1.` +* `copy-phone 4 will copy the phone number of the contact at index 4.` + + +______________________________________________________________________ + +## Data Matters: ### 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. +NUSearch 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. +NUSearch data are saved as a JSON file `[JAR file location]/data/NUSearch.json`. Advanced users are welcome to update data directly by editing that data file. -
: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. -
+> :exclamation: **CAUTION:** If your changes to the data file makes its format invalid, NUSearch will discard all data and start with an empty data file at the next run. + +______________________________________________________________________________________ -### Archiving data files `[coming in v2.0]` +## Frequently Asked Questions (FAQ) +**Q**: Is my data private?
+**A**: Your data is not saved online and is only accessible by you. -_Details coming soon ..._ +**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 NUSearch home folder. + +**Q**: How do I report a bug?
+**A**: You can create an `Issue` on our team's Github page [here](https://github.com/AY2122S2-CS2103T-W11-4/tp/issues). -------------------------------------------------------------------------------------------------------------------- -## FAQ +## Command Summary + + +### Category: General Commands + +The following commands can be used in any context and are for general purposes. + +| Function | Format Of Command | +|------------------------------|-------------------| +| **Exit the program** | `exit` | +| **Display help page pop-up** | `help` | + + +### Category: Basic Commands + +The following commands are used in dealing with contacts. + +| Function | Format Of Command | +|-----------------------------------|--------------------------------------------------------------------------------| +| **Add a new contact** | `add n/NAME p/PHONE_NUMBER e/EMAIL f/FACULTY r/ROLE [tele/TELEGRAM] [t/TAG]…​` | +| **Clear all contacts** | `clear` | +| **Delete an existing contact** | `delete INDEX` | +| **Edit an existing contact** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [tele/TELEGRAM] [t/TAG]…​` | +| **List all contacts** | `list` | +| **Undo previous commands** | `undo` | +| **Redo commands** | `redo` | + +### Category: Find Commands + +The following commands are used in dealing with finding contacts. + +| Function | Format Of Command | +|---------------------------------------------|-------------------------------------| +| **Find contact(s) matching ALL keywords** | `find KEYWORD [MORE_KEYWORDS]` | +| **Find contact(s) matching ANY keywords** | `find-wide KEYWORD [MORE_KEYWORDS]` | +| **Find contact(s) by tags** | `tag TAG` | + +> :spiral_notepad: **NOTE:** KEYWORD refers to either NAME, ROLE or FACULTY + +### Category: Favourite Commands + +The following commands are used in dealing with favourite contacts. + +| Function | Format Of Command | +|-----------------------------|-------------------| +| **Favourite a contact** | `fav INDEX` | +| **List favourite contacts** | `list-fav` | +| **Unfavourite a contact** | `unfav INDEX` | + +### Category: Copy Commands + +The following commands are used in dealing with copying information of contacts. + +| Function | Format Of Command | +|-------------------|-----------------------------| +| **Copy Email** | `copy-email INDEX` | +| **Copy Phone** | `copy-phone INDEX` | + + +
+
+ +

NUSearch

+ +

+ A GUI application to help you manage your university contacts! +
+ Explore the docs » +
+
+ View Demo + · + Report Bug + · + Request Feature +

+
+ + + -**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. --------------------------------------------------------------------------------------------------------------------- -## 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` diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..fc3135e150a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,5 +1,5 @@ -title: "AB-3" -theme: minima +title: "NUSearch" +theme: jekyll-theme-minimal header_pages: - UserGuide.md @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S2-CS2103T-W11-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..d642fc442bc 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "NUSearch"; font-size: 32px; } } diff --git a/docs/diagrams/AddActivityDiagram.puml b/docs/diagrams/AddActivityDiagram.puml new file mode 100644 index 00000000000..e3751200923 --- /dev/null +++ b/docs/diagrams/AddActivityDiagram.puml @@ -0,0 +1,28 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User inputs add command; +:AddressBookParser parses the command; +:AddCommandParser parses user input; +if () then ([format is valid]) + if () then ([no duplicate names]) + if () then ([no duplicate phone numbers]) + if() then ([no duplicate email]) + :Create person ⋔; + else() + :Throw CommandException with \nduplicate Email message; + endif + else ([else]) + :Throw CommandException with \nduplicate Number message; + endif + else ([else]) + :Throw CommandException with\ninvalid Name message; + endif +else ([else]) +:Throws ParseException with invalid command\nformat message; +endif + +stop + +@enduml diff --git a/docs/diagrams/AddSequenceDiagram.puml b/docs/diagrams/AddSequenceDiagram.puml new file mode 100644 index 00000000000..e3faab65340 --- /dev/null +++ b/docs/diagrams/AddSequenceDiagram.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml +skinparam BoxPadding 10 + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR +participant "a:AddCommand" as AddCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box CommandManageable CM_COLOR_T1 +participant ":CommandManager" as CommandManager CM_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("add ...") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("add ...") +activate AddressBookParser + +create AddCommandParser +AddressBookParser -> AddCommandParser +activate AddCommandParser + +AddCommandParser --> AddressBookParser +deactivate AddCommandParser + +AddressBookParser -> AddCommandParser : parse("...") +activate AddCommandParser + +create AddCommand +AddCommandParser -> AddCommand +activate AddCommand + +AddCommand --> AddCommandParser : a +deactivate AddCommand + +AddCommandParser --> AddressBookParser : a +deactivate AddCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddCommandParser -[hidden]-> AddressBookParser +destroy AddCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddCommand : execute() +activate AddCommand + +AddCommand -> CommandManager : execute() +activate CommandManager + +CommandManager -> Model : AddPerson(...) +activate Model + +Model --> CommandManager +deactivate Model + +CommandManager --> AddCommand +deactivate CommandManager + +create CommandResult +AddCommand -> CommandResult +activate CommandResult + +CommandResult --> AddCommand +deactivate CommandResult + +AddCommand --> LogicManager : result +deactivate AddCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml index 4c5cf58212e..16979595d5f 100644 --- a/docs/diagrams/ArchitectureDiagram.puml +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -11,6 +11,7 @@ Package " "<>{ Class Model MODEL_COLOR Class Main #grey Class Commons LOGIC_COLOR_T2 + Class CommandManager CM_COLOR } Class "<$user>" as User MODEL_COLOR_T2 @@ -20,7 +21,8 @@ Class "<$documents>" as File UI_COLOR_T1 UI -[#green]> Logic UI -right[#green]-> Model Logic -[#blue]-> Storage -Logic -down[#blue]-> Model +Logic -down[#blue]-> CommandManager +CommandManager -[#purple]-> Model Main -[#grey]-> UI Main -[#grey]-> Logic Main -[#grey]-> Storage diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..ba3ef5f1924 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -1,28 +1,33 @@ @startuml !include style.puml -Actor User as user USER_COLOR +Actor User as user #pink Participant ":UI" as ui UI_COLOR Participant ":Logic" as logic LOGIC_COLOR +Participant ":CommandManager" as cm CM_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) -activate model MODEL_COLOR +logic -[LOGIC_COLOR]> cm : deletePerson(p) +activate cm CM_COLOR + +cm -[CM_COLOR]> model : deletePerson(p) +deactivate cm +activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) activate storage STORAGE_COLOR -storage -[STORAGE_COLOR]> storage : Save to file +storage -[STORAGE_COLOR]> storage : Save to file activate storage STORAGE_COLOR_T1 storage --[STORAGE_COLOR]> storage deactivate storage diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 5731f9cbaa1..f7ad487881e 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -17,5 +17,7 @@ Person -up-> "*" Tag Person *--> Name Person *--> Phone Person *--> Email -Person *--> Address +Person *--> Role +Person *--> Faculty +Person *--> "0..1" Telegram @enduml diff --git a/docs/diagrams/CMClassDiagram.puml b/docs/diagrams/CMClassDiagram.puml new file mode 100644 index 00000000000..2ea99ce57f4 --- /dev/null +++ b/docs/diagrams/CMClassDiagram.puml @@ -0,0 +1,26 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor CM_COLOR +skinparam classBackgroundColor CM_COLOR + +Package CommandManageable <>{ + +Class "<>\nCommandManageable" as CM +Class CommandManager +Class CommandStackPointer +Class CommandStack + +} + +Class HiddenOutside #Teal +HiddenOutside ..> CM +Class Model MODEL_COLOR + +CommandManager --> Model + +CommandManager .up.|> CM + +CommandManager *--> CommandStackPointer +CommandManager *--> CommandStack +@enduml diff --git a/docs/diagrams/ComponentManagers.puml b/docs/diagrams/ComponentManagers.puml index 5e907dc1115..b50113512cf 100644 --- a/docs/diagrams/ComponentManagers.puml +++ b/docs/diagrams/ComponentManagers.puml @@ -1,31 +1,38 @@ @startuml !include style.puml skinparam arrowThickness 1.1 -skinparam arrowColor LOGIC_COLOR_T4 -skinparam classBackgroundColor LOGIC_COLOR +skinparam arrowColor #orange -package Logic { -Class "<>\nLogic" as Logic -Class LogicManager +package Logic <> LOGIC_COLOR_T1 { +Class "<>\nLogic" as Logic LOGIC_COLOR +Class LogicManager LOGIC_COLOR } -package Model{ -Class "<>\nModel" as Model -Class ModelManager +package Model <> MODEL_COLOR_T1 { +Class "<>\nModel" as Model MODEL_COLOR +Class ModelManager MODEL_COLOR } -package Storage{ -Class "<>\nStorage" as Storage -Class StorageManager +package CommandManager <> CM_COLOR_T1 { +Class "<>\nCommandManageable" as CommandManageable CM_COLOR +Class CommandManager CM_COLOR } -Class HiddenOutside #FFFFFF +package Storage <> STORAGE_COLOR_T1 { +Class "<>\nStorage" as Storage STORAGE_COLOR +Class StorageManager STORAGE_COLOR +} + +Class HiddenOutside #Teal HiddenOutside ..> Logic LogicManager .up.|> Logic ModelManager .up.|> Model StorageManager .up.|> Storage +CommandManager .up.|> CommandManageable -LogicManager --> Model +LogicManager --> CommandManageable +CommandManager --> Model LogicManager --> Storage @enduml + diff --git a/docs/diagrams/CopyEmailSequenceDiagram.puml b/docs/diagrams/CopyEmailSequenceDiagram.puml new file mode 100644 index 00000000000..6d00ab17ed1 --- /dev/null +++ b/docs/diagrams/CopyEmailSequenceDiagram.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml +skinparam BoxPadding 10 + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":CopyEmailCommandParser" as CopyEmailCommandParser LOGIC_COLOR +participant "c:CopyEmailCommand" as CopyEmailCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box CommandManageable CM_COLOR_T1 +participant ":CommandManager" as CommandManager CM_COLOR +end box + +[-> LogicManager : execute("copy-email 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("copy-email 1") +activate AddressBookParser + +create CopyEmailCommandParser +AddressBookParser -> CopyEmailCommandParser +activate CopyEmailCommandParser + +CopyEmailCommandParser --> AddressBookParser +deactivate CopyEmailCommandParser + +AddressBookParser -> CopyEmailCommandParser : parse("1") +activate CopyEmailCommandParser + +create CopyEmailCommand +CopyEmailCommandParser -> CopyEmailCommand +activate CopyEmailCommand + +CopyEmailCommand --> CopyEmailCommandParser : c +deactivate CopyEmailCommand + +CopyEmailCommandParser --> AddressBookParser : c +deactivate CopyEmailCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CopyEmailCommandParser -[hidden]-> AddressBookParser +destroy CopyEmailCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> CopyEmailCommand : execute() +activate CopyEmailCommand + +CopyEmailCommand -> CommandManager : execute() +activate CommandManager + +CommandManager --> CopyEmailCommand +deactivate CommandManager + +create CommandResult +CopyEmailCommand -> CommandResult +activate CommandResult + +CommandResult --> CopyEmailCommand +deactivate CommandResult + +CopyEmailCommand --> LogicManager : result +deactivate CopyEmailCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..b8df8125a3f 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -1,7 +1,8 @@ @startuml !include style.puml +skinparam BoxPadding 10 -box Logic LOGIC_COLOR_T1 +box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR @@ -9,14 +10,18 @@ participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box -box Model MODEL_COLOR_T1 +box CommandManageable CM_COLOR_T1 +participant ":CommandManager" as CommandManager CM_COLOR +end box + +box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete 1") activate AddressBookParser create DeleteCommandParser @@ -26,34 +31,40 @@ activate DeleteCommandParser DeleteCommandParser --> AddressBookParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +AddressBookParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand DeleteCommandParser -> DeleteCommand activate DeleteCommand -DeleteCommand --> DeleteCommandParser : d +DeleteCommand --> DeleteCommandParser : d deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> AddressBookParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. DeleteCommandParser -[hidden]-> AddressBookParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d +AddressBookParser --> LogicManager : d deactivate AddressBookParser -LogicManager -> DeleteCommand : execute() +LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> CommandManager : execute() +activate CommandManager + +CommandManager -> Model : deletePerson(1) activate Model -Model --> DeleteCommand +Model --> CommandManager deactivate Model +CommandManager --> DeleteCommand +deactivate CommandManager + create CommandResult DeleteCommand -> CommandResult activate CommandResult @@ -61,7 +72,7 @@ activate CommandResult CommandResult --> DeleteCommand deactivate CommandResult -DeleteCommand --> LogicManager : result +DeleteCommand --> LogicManager : result deactivate DeleteCommand [<--LogicManager diff --git a/docs/diagrams/FavActivityDiagram.puml b/docs/diagrams/FavActivityDiagram.puml new file mode 100644 index 00000000000..ae9b9573e3d --- /dev/null +++ b/docs/diagrams/FavActivityDiagram.puml @@ -0,0 +1,15 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User inputs add command; +:AddressBookParser parses the command; +:FavCommandParser parses user input; +if () then ([index is valid]) +else ([else]) +:Throws ParseException with invalid command\nformat message; +endif + +stop + +@enduml diff --git a/docs/diagrams/FavSequenceDiagram.puml b/docs/diagrams/FavSequenceDiagram.puml new file mode 100644 index 00000000000..96dd1dae3f4 --- /dev/null +++ b/docs/diagrams/FavSequenceDiagram.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml +skinparam BoxPadding 10 + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FavouriteCommandParser" as FavouriteCommandParser LOGIC_COLOR +participant "f:FavouriteCommand" as FavouriteCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box CommandManageable CM_COLOR_T1 +participant ":CommandManager" as CommandManager CM_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("fav 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("fav 1") +activate AddressBookParser + +create FavouriteCommandParser +AddressBookParser -> FavouriteCommandParser +activate FavouriteCommandParser + +FavouriteCommandParser --> AddressBookParser +deactivate FavouriteCommandParser + +AddressBookParser -> FavouriteCommandParser : parse("1") +activate FavouriteCommandParser + +create FavouriteCommand +FavouriteCommandParser -> FavouriteCommand +activate FavouriteCommand + +FavouriteCommand --> FavouriteCommandParser : f +deactivate FavouriteCommand + +FavouriteCommandParser --> AddressBookParser : f +deactivate FavouriteCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FavouriteCommandParser -[hidden]-> AddressBookParser +destroy FavouriteCommandParser + +AddressBookParser --> LogicManager : f +deactivate AddressBookParser + +LogicManager -> FavouriteCommand : execute() +activate FavouriteCommand + +FavouriteCommand -> CommandManager : execute() +activate CommandManager + +CommandManager -> Model : FavouritePerson(1) +activate Model + +Model --> CommandManager +deactivate Model + +CommandManager --> FavouriteCommand +deactivate CommandManager + +create CommandResult +FavouriteCommand -> CommandResult +activate CommandResult + +CommandResult --> FavouriteCommand +deactivate CommandResult + +FavouriteCommand --> LogicManager : result +deactivate FavouriteCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..74432815271 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -4,7 +4,7 @@ skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR -package Logic { +package Logic <> LOGIC_COLOR_T1 { Class AddressBookParser Class XYZCommand @@ -16,31 +16,37 @@ Class "<>\nLogic" as Logic Class LogicManager } -package Model{ -Class HiddenModel #FFFFFF +package Model <> MODEL_COLOR_T1 { +Class HiddenModel MODEL_COLOR } -package Storage{ +package Storage <> STORAGE_COLOR_T1 { +Class HiddenStorage STORAGE_COLOR } -Class HiddenOutside #FFFFFF +package CommandManager <> CM_COLOR_T1 { +Class HiddenCommandManager CM_COLOR +} + +Class HiddenOutside #Teal HiddenOutside ..> Logic LogicManager .right.|> Logic -LogicManager -right->"1" AddressBookParser -AddressBookParser ..> XYZCommand : creates > +LogicManager -right-> "1" AddressBookParser +AddressBookParser ..> XYZCommand : creates > XYZCommand -up-|> Command -LogicManager .left.> Command : executes > +LogicManager .left.> Command : executes > -LogicManager --> Model +LogicManager --> CommandManager +CommandManager --> Model LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage -Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +Command .right.> CommandManager +note left of XYZCommand #lightblue: XYZCommand = AddCommand, \nFindCommand, etc Logic ..> CommandResult LogicManager .down.> CommandResult -Command .up.> CommandResult : produces > +Command .up.> CommandResult : produces > @enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..d7ba7697143 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -4,7 +4,7 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -Package Model <>{ +Package Model <>{ Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs Class "<>\nModel" as Model @@ -14,15 +14,17 @@ Class UserPrefs Class UniquePersonList Class Person -Class Address +Class Faculty Class Email Class Name Class Phone Class Tag +Class Role +Class Telegram } -Class HiddenOutside #FFFFFF +Class HiddenOutside #Teal HiddenOutside ..> Model AddressBook .up.|> ReadOnlyAddressBook @@ -30,21 +32,23 @@ AddressBook .up.|> ReadOnlyAddressBook ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook -ModelManager -right-> "1" UserPrefs +ModelManager -left-> "1" AddressBook +ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person +AddressBook *--> "1" UniquePersonList +UniquePersonList --> "~* all" Person Person *--> Name Person *--> Phone Person *--> Email -Person *--> Address -Person *--> "*" Tag +Person *--> Faculty +Person *--> Role +Person *--> "0..1" Telegram +Person *--> "*" Tag Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Phone -[hidden]right-> Faculty +Faculty -[hidden]right-> Email -ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Person @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index 0c7424de6e0..c691a4735a9 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -7,7 +7,7 @@ skinparam classBackgroundColor LOGIC_COLOR Class "{abstract}\nCommand" as Command Class XYZCommand -package "Parser classes"{ +package "Parser classes" <> LOGIC_COLOR_T1 { Class "<>\nParser" as Parser Class AddressBookParser Class XYZCommandParser @@ -18,13 +18,13 @@ Class ArgumentTokenizer Class Prefix } -Class HiddenOutside #FFFFFF +Class HiddenOutside #Teal HiddenOutside ..> AddressBookParser -AddressBookParser .down.> XYZCommandParser: creates > +AddressBookParser .down.> XYZCommandParser: creates > -XYZCommandParser ..> XYZCommand : creates > -AddressBookParser ..> Command : returns > +XYZCommandParser ..> XYZCommand : creates > +AddressBookParser ..> Command : returns > XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..5dbaae38cdf 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -4,9 +4,9 @@ skinparam arrowThickness 1.1 skinparam arrowColor STORAGE_COLOR skinparam classBackgroundColor STORAGE_COLOR -package Storage{ +package Storage <> { -package "UserPrefs Storage" #F4F6F6{ +package "UserPrefs Storage" <> STORAGE_COLOR_T1{ Class "<>\nUserPrefsStorage" as UserPrefsStorage Class JsonUserPrefsStorage } @@ -14,7 +14,7 @@ Class JsonUserPrefsStorage Class "<>\nStorage" as Storage Class StorageManager -package "AddressBook Storage" #F4F6F6{ +package "AddressBook Storage" <> STORAGE_COLOR_T1{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook @@ -24,12 +24,12 @@ Class JsonAdaptedTag } -Class HiddenOutside #FFFFFF +Class HiddenOutside #Teal HiddenOutside ..> Storage StorageManager .up.|> Storage -StorageManager -up-> "1" UserPrefsStorage -StorageManager -up-> "1" AddressBookStorage +StorageManager -up-> "1" UserPrefsStorage +StorageManager -up-> "1" AddressBookStorage Storage -left-|> UserPrefsStorage Storage -right-|> AddressBookStorage @@ -37,7 +37,7 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedPerson +JsonAdaptedPerson --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/TagSequenceDiagram.puml b/docs/diagrams/TagSequenceDiagram.puml new file mode 100644 index 00000000000..a54d58ff884 --- /dev/null +++ b/docs/diagrams/TagSequenceDiagram.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml +skinparam BoxPadding 10 + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TagCommandParser" as TagCommandParser LOGIC_COLOR +participant "t:TagCommand" as TagCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box CommandManageable CM_COLOR_T1 +participant ":CommandManager" as CommandManager CM_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("tag ...") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("tag ...") +activate AddressBookParser + +create TagCommandParser +AddressBookParser -> TagCommandParser +activate TagCommandParser + +TagCommandParser --> AddressBookParser +deactivate TagCommandParser + +AddressBookParser -> TagCommandParser : parse("...") +activate TagCommandParser + +create TagCommand +TagCommandParser -> TagCommand +activate TagCommand + +TagCommand --> TagCommandParser : t +deactivate TagCommand + +TagCommandParser --> AddressBookParser : t +deactivate TagCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TagCommandParser -[hidden]-> AddressBookParser +destroy TagCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> TagCommand : execute() +activate TagCommand + +TagCommand -> CommandManager : execute() +activate CommandManager + +CommandManager -> Model : Tag(Predicate) +activate Model + +Model --> CommandManager +deactivate Model + +CommandManager --> TagCommand +deactivate CommandManager + +create CommandResult +TagCommand -> CommandResult +activate CommandResult + +CommandResult --> TagCommand +deactivate CommandResult + +TagCommand --> LogicManager : result +deactivate TagCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..5f9aefcbdbd 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -17,15 +17,15 @@ Class StatusBarFooter Class CommandBox } -package Model <> { -Class HiddenModel #FFFFFF +package Model <> MODEL_COLOR_T1 { +Class HiddenModel MODEL_COLOR } -package Logic <> { -Class HiddenLogic #FFFFFF +package Logic <> LOGIC_COLOR_T1 { +Class HiddenLogic LOGIC_COLOR } -Class HiddenOutside #FFFFFF +Class HiddenOutside #Teal HiddenOutside ..> Ui UiManager .left.|> Ui diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..34885420931 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -15,6 +15,6 @@ State2 -[hidden]right-> State3 hide State2 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State1 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..2856b59e321 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -1,20 +1,20 @@ @startuml !include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 +skinparam ClassFontColor #00000 +skinparam ClassBorderColor #00000 -title After command "add n/David" +title After 2 undoable Commands -package States <> { +package States <> { class State1 as "__ab0:AddressBook__" class State2 as "__ab1:AddressBook__" class State3 as "__ab2:AddressBook__" } -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 +State1 -right-> State2 +State2 -right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #000000 Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..c11a0b9711f 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -1,20 +1,20 @@ @startuml !include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 +skinparam ClassFontColor #00000 +skinparam ClassBorderColor #00000 -title After command "undo" +title After Undo Command -package States <> { +package States <> { class State1 as "__ab0:AddressBook__" class State2 as "__ab1:AddressBook__" class State3 as "__ab2:AddressBook__" } -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 +State1 -right-> State2 +State2 -right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #000000 Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 410aab4e412..885db36367b 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -1,52 +1,62 @@ @startuml !include style.puml +skinparam BoxPadding 10 -box Logic LOGIC_COLOR_T1 +box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR participant "u:UndoCommand" as UndoCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR end box -box Model MODEL_COLOR_T1 +box CommandManageable CM_COLOR_T1 +participant ":CommandManager" as CommandManager CM_COLOR +end box + +box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR end box -[-> LogicManager : execute(undo) + +[-> LogicManager : execute("undo") activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) +LogicManager -> AddressBookParser : parseCommand("undo") activate AddressBookParser create UndoCommand AddressBookParser -> UndoCommand activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand -> AddressBookParser : u deactivate UndoCommand -AddressBookParser --> LogicManager : u +AddressBookParser --> LogicManager : u deactivate AddressBookParser -LogicManager -> UndoCommand : execute() +LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> CommandManager : execute() +activate CommandManager + +CommandManager -> Model : Undo() activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook +Model --> CommandManager +deactivate Model -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook +CommandManager --> UndoCommand +deactivate CommandManager -Model --> UndoCommand -deactivate Model +create CommandResult +UndoCommand -> CommandResult +activate CommandResult + +CommandResult --> UndoCommand +deactivate CommandResult -UndoCommand --> LogicManager : result +UndoCommand --> LogicManager : result deactivate UndoCommand -UndoCommand -[hidden]-> LogicManager : result -destroy UndoCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml index fad8b0adeaa..cb5a1c685f6 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/style.puml @@ -7,6 +7,9 @@ 'T1 through T4 are shades of the original color from lightest to darkest +!theme black-knight + + !define UI_COLOR #1D8900 !define UI_COLOR_T1 #83E769 !define UI_COLOR_T2 #3FC71B @@ -26,49 +29,58 @@ !define MODEL_COLOR_T4 #51000A !define STORAGE_COLOR #A38300 -!define STORAGE_COLOR_T1 #FFE374 +!define STORAGE_COLOR_T1 #khaki !define STORAGE_COLOR_T2 #EDC520 !define STORAGE_COLOR_T3 #806600 !define STORAGE_COLOR_T2 #544400 +!define CM_COLOR #purple +!define CM_COLOR_T1 #plum +!define CM_COLOR_T2 #violet +!define CM_COLOR_T3 #purple +!define CM_COLOR_T4 #maroon + !define USER_COLOR #000000 skinparam BackgroundColor #FFFFFFF -skinparam Shadowing false +skinparam Shadowing true skinparam Class { FontColor #FFFFFF BorderThickness 1 BorderColor #FFFFFF StereotypeFontColor #FFFFFF - FontName Arial + FontName Berlin Sans FB } skinparam Actor { BorderColor USER_COLOR Color USER_COLOR - FontName Arial + FontName Berlin Sans FB + FontColor #000000 } skinparam Sequence { MessageAlign center BoxFontSize 15 BoxPadding 0 - BoxFontColor #FFFFFF - FontName Arial + BoxFontColor #000000 + FontName Berlin Sans FB } skinparam Participant { FontColor #FFFFFFF + FontName Berlin Sans FB Padding 20 } skinparam MinClassWidth 50 skinparam ParticipantPadding 10 -skinparam Shadowing false +skinparam Shadowing true skinparam DefaultTextAlignment center skinparam packageStyle Rectangle +skinparam FontName Berlin Sans FB hide footbox hide members diff --git a/docs/images/AddActivityDiagram.png b/docs/images/AddActivityDiagram.png new file mode 100644 index 00000000000..718076b482f Binary files /dev/null and b/docs/images/AddActivityDiagram.png differ diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png new file mode 100644 index 00000000000..d8a59f99529 Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png index 86c60246ccb..4c5c1136b43 100644 Binary files a/docs/images/ArchitectureDiagram.png and b/docs/images/ArchitectureDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..a6c938cf6c7 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 1ec62caa2a5..2fb82c2a944 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CMClassDiagram.png b/docs/images/CMClassDiagram.png new file mode 100644 index 00000000000..9a4e9547652 Binary files /dev/null and b/docs/images/CMClassDiagram.png differ diff --git a/docs/images/ComponentManagers.png b/docs/images/ComponentManagers.png index b5764ff9273..74033b50c01 100644 Binary files a/docs/images/ComponentManagers.png and b/docs/images/ComponentManagers.png differ diff --git a/docs/images/CopyEmailSequenceDiagram.png b/docs/images/CopyEmailSequenceDiagram.png new file mode 100644 index 00000000000..7558d90732a Binary files /dev/null and b/docs/images/CopyEmailSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..e108d55e936 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/FavActivityDiagram.png b/docs/images/FavActivityDiagram.png new file mode 100644 index 00000000000..88045260644 Binary files /dev/null and b/docs/images/FavActivityDiagram.png differ diff --git a/docs/images/FavSequenceDiagram.png b/docs/images/FavSequenceDiagram.png new file mode 100644 index 00000000000..242caeabb07 Binary files /dev/null and b/docs/images/FavSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..04d1e303400 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..6aad44008c7 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..c56a2050a18 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..30abef00e42 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/TagSequenceDiagram.png b/docs/images/TagSequenceDiagram.png new file mode 100644 index 00000000000..e74e72e887f Binary files /dev/null and b/docs/images/TagSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..aad877e8f0b 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..3bd22790d37 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png index 36519c1015b..be5e79569e8 100644 Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png index 19959d01712..c02f285948f 100644 Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png index 6addcd3a8d9..70033299a24 100644 Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ diff --git a/docs/images/add.png b/docs/images/add.png new file mode 100644 index 00000000000..644851d6398 Binary files /dev/null and b/docs/images/add.png differ diff --git a/docs/images/copy-email.png b/docs/images/copy-email.png new file mode 100644 index 00000000000..89ad79490d4 Binary files /dev/null and b/docs/images/copy-email.png differ diff --git a/docs/images/copy-phone.png b/docs/images/copy-phone.png new file mode 100644 index 00000000000..be53ade492a Binary files /dev/null and b/docs/images/copy-phone.png differ diff --git a/docs/images/delete.png b/docs/images/delete.png new file mode 100644 index 00000000000..54ecfecaa68 Binary files /dev/null and b/docs/images/delete.png differ diff --git a/docs/images/edit.png b/docs/images/edit.png new file mode 100644 index 00000000000..c75f1d4a9a3 Binary files /dev/null and b/docs/images/edit.png differ diff --git a/docs/images/eugenechiaay.png b/docs/images/eugenechiaay.png new file mode 100644 index 00000000000..7e92b7757ba Binary files /dev/null and b/docs/images/eugenechiaay.png differ diff --git a/docs/images/fav.png b/docs/images/fav.png new file mode 100644 index 00000000000..d4b529d61fb Binary files /dev/null and b/docs/images/fav.png differ diff --git a/docs/images/find-wide.png b/docs/images/find-wide.png new file mode 100644 index 00000000000..24558d4d0ad Binary files /dev/null and b/docs/images/find-wide.png differ diff --git a/docs/images/find.png b/docs/images/find.png new file mode 100644 index 00000000000..1fadaabf6bc Binary files /dev/null and b/docs/images/find.png differ diff --git a/docs/images/help-message.png b/docs/images/help-message.png new file mode 100644 index 00000000000..626fdf674f5 Binary files /dev/null and b/docs/images/help-message.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/list-fav.png b/docs/images/list-fav.png new file mode 100644 index 00000000000..7dc79107790 Binary files /dev/null and b/docs/images/list-fav.png differ diff --git a/docs/images/list.png b/docs/images/list.png new file mode 100644 index 00000000000..69a039c1476 Binary files /dev/null and b/docs/images/list.png differ diff --git a/docs/images/redo.png b/docs/images/redo.png new file mode 100644 index 00000000000..1f1e3031f9e Binary files /dev/null and b/docs/images/redo.png differ diff --git a/docs/images/shurvirarora.png b/docs/images/shurvirarora.png new file mode 100644 index 00000000000..f354c0b167c Binary files /dev/null and b/docs/images/shurvirarora.png differ diff --git a/docs/images/simjm.png b/docs/images/simjm.png new file mode 100644 index 00000000000..6bda229e357 Binary files /dev/null and b/docs/images/simjm.png differ diff --git a/docs/images/tag-friend.png b/docs/images/tag-friend.png new file mode 100644 index 00000000000..a732874e4af Binary files /dev/null and b/docs/images/tag-friend.png differ diff --git a/docs/images/tanweien.png b/docs/images/tanweien.png new file mode 100644 index 00000000000..23f1f4c5739 Binary files /dev/null and b/docs/images/tanweien.png differ diff --git a/docs/images/undo.png b/docs/images/undo.png new file mode 100644 index 00000000000..e9ab1c7dc65 Binary files /dev/null and b/docs/images/undo.png differ diff --git a/docs/images/unfav.png b/docs/images/unfav.png new file mode 100644 index 00000000000..15232da9a4d Binary files /dev/null and b/docs/images/unfav.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..723bcfb2ae1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,18 @@ --- layout: page -title: AddressBook Level-3 +title: NUSearch --- [![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![codecov](https://codecov.io/gh/AY2122S2-CS2103T-W11-4/tp/branch/master/graph/badge.svg?token=TO5WF437LI)](https://codecov.io/gh/AY2122S2-CS2103T-W11-4/tp) ![Ui](images/Ui.png) -**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). +NUSearch is a **desktop app for managing NUS staff 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, NUSearch can get your contact management tasks done faster than traditional GUI applications. -* 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. +* If you are interested in using NUSearch, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing NUSearch, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/eugenechiaay.md b/docs/team/eugenechiaay.md new file mode 100644 index 00000000000..6b6a054df25 --- /dev/null +++ b/docs/team/eugenechiaay.md @@ -0,0 +1,56 @@ +--- +layout: page +title: Eugene Chia's Project Portfolio Page +--- + +### Project: NUSearch + +NUSearch is a desktop app for managing NUS staff contacts, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). +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 commands. + * What it does: allows the user to undo 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 the future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: Took inspiration from the [memento model](https://medium.com/design-patterns-in-python/memento-pattern-eba610b3b59c) + +* **New Feature**: Add optional Telegram field to contacts + * What it does: allows the user to create contacts with optional Telegram fields + * Justification: This simulates the use case of faculty members having the choice of including their Telegram handle. + * Highlights: This enhancement uses the Optional Java class which helps to ensure errors are thrown at appropriate junctures. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=W11-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=eugenechiaay&tabRepo=AY2122S2-CS2103T-W11-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false) + +* **Project management**: + * Managed releases `v1.1` - `v1.4` (5 releases) on GitHub. + +* **Enhancements to existing features**: + * Wrote additional tests for existing features to increase coverage from 72% to 77% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added mock up for UI [/#26](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/26) + * Added documentation for the features `delete`, `fav` and `tag` [\#30](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/30) + * Did cosmetic tweaks to existing documentation of features `add`, `edit`, `delete`, `tag`, `undo`, `redo`, `copy-email`, `copy-phone`, `help`, `clear`, `exit` [\#30](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/30) + * Added screenshots for the features `delete`, `fav`, `help`, `list-fav`, `redo-delete` and `undo-delete` [\#79](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/79) + * Developer Guide: + * Added implementation details of the `delete` feature. + * Added use case for NUSearch [\#39](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/39). + * Diagrams: + * Updated CM Class Diagram, Delete Sequence Diagram, Logic Class Diagram, Model Class Diagram, Parser Classes, Storage Class Diagram, UiClassDiagram. [\#92](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/92) + * README: + * Updated README for NUSearch [\#34](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/34) + +* **Team-Based tasks**: + * Maintained the issue tracker + * Managed releases + * Assigned and delegated issues to members + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#23](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/23), [\#74](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/74), [\#77](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/77), [\#80](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/80) + * Number of PRs reviewed and approved: 22 [\#23](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/23), [\#29](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/29), [\#36](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/36), [\#37](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/37), [\#38](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/38), [\#74](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/74), [\#77](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/77), [\#80](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/80), [\#87](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/87), [\#88](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/88), [\#89](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/89), [\#90](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/90), [\#91](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/91), [\#93](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/93), [\#98](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/98), [\#99](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/99), [\#103](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/103), [\#154](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/154), [\#157](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/157), [\#160](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/160), [\#162](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/162), [\#166](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/166) + * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2122S2/forum/issues/191), [2](https://github.com/nus-cs2103-AY2122S2/forum/issues/117)) + * Reported bugs and suggestions for other teams in the class: [1](https://github.com/eugenechiaay/ped/issues/1), [2](https://github.com/eugenechiaay/ped/issues/2), [3](https://github.com/eugenechiaay/ped/issues/3), [4](https://github.com/eugenechiaay/ped/issues/4), [5](https://github.com/eugenechiaay/ped/issues/5), [6](https://github.com/eugenechiaay/ped/issues/6), [7](https://github.com/eugenechiaay/ped/issues/7), [8](https://github.com/eugenechiaay/ped/issues/8), [9](https://github.com/eugenechiaay/ped/issues/9), [10](https://github.com/eugenechiaay/ped/issues/10), [11](https://github.com/eugenechiaay/ped/issues/11), [12](https://github.com/eugenechiaay/ped/issues/12), [13](https://github.com/eugenechiaay/ped/issues/13), [14](https://github.com/eugenechiaay/ped/issues/14), [15](https://github.com/eugenechiaay/ped/issues/15) + 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/shurvirarora.md b/docs/team/shurvirarora.md new file mode 100644 index 00000000000..8f0ee11436d --- /dev/null +++ b/docs/team/shurvirarora.md @@ -0,0 +1,67 @@ +--- +layout: page +title: Shurvir Arora's Project Portfolio Page +--- + +### Project: NUSearch + +NUSearch is a desktop app for managing NUS contacts, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). +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 find contacts via their tag. (tag ...) + * What it does: allows the user to find all contacts based on the tag they're associated with. + * Justification: This feature improves the product significantly because a user could find trouble in finding contacts initially and may want to organise their contacts further by assigning a tag to them. As such, with this feature they can filter out contacts based on the tag specified. + * Highlights: This enhancement affects some existing commands. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **New Feature**: Added the Role field for each contact. + * What it does: allows the user to add a role for each contact + * Justification: This feature improves the product significantly because a user could now further organise their contacts by specifying the role of each contact. This also makes the application university specific, hence contributing to the context of the application. + * Highlights: This enhancement affects some existing commands. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **New Feature**: Added the ability to find contacts matching **any** of the keywords (keywords: Name, Faculty, Role). (find-wide ...) + * What it does: allows the user to find all contacts that match **any** of the keywords specified. + * Justification: This feature improves the product significantly because a user could now find contacts not just by Name but also by their Faculty and Role. This also allows users to broaden their search results as contacts that match **any** of the keywords specified will show up in the result list. + * Highlights: This enhancement affects some existing commands. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **New Feature**: Added the ability to find contacts matching **all** of the keywords (keywords: Name, Faculty, Role). (find ...) + * What it does: allows the user to find all contacts that match **all** of the keywords specified. + * Justification: This feature improves the product significantly because a user could now find contacts not just by Name but also by their Faculty and Role. This also allows users to narrow down their search results as only contacts that match **all** the keywords specified will show up in the result list. + * Highlights: This enhancement affects some existing commands. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=shurvirarora&breakdown=true) + +* **Project management**: + * Managed releases `v1.1` - `v1.4` (5 releases) on GitHub. + +* **Enhancements to existing features**: + * Improved the Find feature to include Faculty and Role in addition to Name. Users can now find contacts via Name, Role and Faculty. + * Wrote additional tests for existing features to increase coverage. (Pull requests [\#101](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/101), [\#77](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/77), [\#59](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/59)) + +* **Documentation**: + * User Guide: + * Added `Introduction` section in UG, explaining aim and purpose of NUSearch. + * Added documentation for the features `list`,`fav`, `unfav`, `list-fav`, `find`, `find-wide` [\#29](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/29/files), [\#154](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/154) + * Created Command Summary tables comprising `General Commands`, `Basic Commands`, `Favourite Commands`, `Find Commands` and `Copy Commands`. [\#99](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/99), [\#154](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/154) + * Did cosmetic tweaks to existing documentation of features `add`, `edit`, `delete`, `tag`, `undo`, `redo`, `copy-email`, `copy-phone`, `help`, `clear`, `exit` : [\#35](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/35), [\#40](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/40), [\#154](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/154) + * Organised structure and commands into sub-categories: `General Commands`, `Basic Commands`, `Favourite Commands`, `Find Commands` and `Copy Commands`. [\#99](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/99) + * Improved tips and notes for every feature. [\#160](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/160) + * Added table of acceptable values for `faculty` and `role` fields. [\#160](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/160) + * Developer Guide: + * Added implementation details of `how architecture components interact with each other` and `sequence diagram`. [\#90](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/90) + * Diagrams: + * Updated Architecture Diagram and Architecture Sequence Diagram. [\#87](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/87) + * About Us: + * Update roles and responsibilities of team members [\#53](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/53) + +* **Team-Based tasks**: + * Maintaining the issue tracker + * Release management + * Assigning of tasks to members + + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#75](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/75), [\#98](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/98) + * Number of PRs reviewed and approved: 15 [\#26](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/26), [\#28](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/28), [\#34](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/34), [\#38](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/38), [\#39](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/39), [\#52](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/52), [\#67](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/67), [\#71](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/71), [\#75](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/75), [\#77](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/77), [\#78](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/78), [\#86](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/86), [\#98](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/98), [\#161](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/161), [\#163](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/163) + diff --git a/docs/team/simjm.md b/docs/team/simjm.md new file mode 100644 index 00000000000..1f3efd300ab --- /dev/null +++ b/docs/team/simjm.md @@ -0,0 +1,56 @@ +--- +layout: page +title: Jia Ming's Project Portfolio Page +--- + +### Project: NUSearch + +NUSearch is a desktop app for managing NUS contacts, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). +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 Faculty field for each contact. + * What it does: allows the user to add a faculty for each contact + * Justification: This feature improves the product significantly because a user could now further organise their contacts by specifying the faculty of each contact. This also makes the application university specific, hence contributing to the context of the application. + * Highlights: This enhancement affects some existing commands. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **New Feature**: Added the ability to copy email of contacts. + * What it does: allows the user to copy the email of contacts. + * Justification: This feature improves the product significantly because a user could now copy email of a contact just by a short command and then paste it efficiently on third party application should they require it. + * Highlights: This enhancement affects some parts of the model and it required an in-depth analysis of design alternatives. The implementation was challenging as it required imports from java.awt package which was unfamiliar to the team. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=W11-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=SimJM&tabRepo=AY2122S2-CS2103T-W11-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +* **Project management**: + * Managed releases `v1.1` - `v1.4` (5 releases) on GitHub. + +* **Enhancements to existing features**: + * Updated the GUI color scheme and interface (Pull requests [\#80](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/80), [\#103](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/103)) + * Justification: This improves the user interface significantly because it binds the culture of the university into NUSearch, allowing users to have sense of their school while using NUSearch. + * Highlight: The font used for NUSearch is specially chosen to give off a classy yet bookish vibes. + * Updated the User Inferface of NUSearch Help Window (Pull requests [\#67](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/67)) + * What it does: This allows the user to learn about basic commands without going to the User Guide + * Justification: This improves the user experience significantly because a user could conveniently access guides for commands with just a command `help`. + * Highlight: This improvement is accompanied by a scrollable window which allows the user scroll through for help related to commands. + +* **Documentation**: + * User Guide: + * Modified documentation for adaptation from AB-3 to NUSearch [\#52](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/52) + * Added documentation for the Quick start: features and future features [\#28](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/28) + * Developer Guide: + * Added use case for NUSearch [\#38](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/38). + * Added implementation details of `Components Manager`. [\#91](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/91) + * Diagrams: + * Updated Delete Sequence Diagram and Logic Class Diagram. [\#91](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/91) + +* **Team-Based tasks**: + * Maintaining the issue tracker + * Release management + * Assigning of tasks to members + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#72](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/72), [\#159](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/159) + * Number of PRs reviewed and approved: 22 [\#27](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/27), [\#29](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/29), [\#33](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/33), [\#39](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/39), [\#40](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/40), [\#53](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/53), [\#54](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/54), [\#59](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/59), [\#72](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/72), [\#73](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/73), [\#79](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/79), [\#92](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/92), [\#94](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/94), [\#97](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/97), [\#98](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/98), [\#101](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/101), [\#105](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/105), [\#155](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/155), [\#156](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/156), [\#159](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/159), [\#165](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/165), [\#167](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/167) + * Reported bugs and suggestions for other teams in the class (total: 12): [1](https://github.com/SimJM/ped/issues/1), [2](https://github.com/SimJM/ped/issues/2), [3](https://github.com/SimJM/ped/issues/3), [4](https://github.com/SimJM/ped/issues/4), [5](https://github.com/SimJM/ped/issues/5), [6](https://github.com/SimJM/ped/issues/6), [7](https://github.com/SimJM/ped/issues/7), [8](https://github.com/SimJM/ped/issues/8), [9](https://github.com/SimJM/ped/issues/9), [10](https://github.com/SimJM/ped/issues/10), [11](https://github.com/SimJM/ped/issues/11), [12](https://github.com/SimJM/ped/issues/12)) + diff --git a/docs/team/tanweien.md b/docs/team/tanweien.md new file mode 100644 index 00000000000..96b37df7f82 --- /dev/null +++ b/docs/team/tanweien.md @@ -0,0 +1,65 @@ +--- +layout: page +title: Tan Wei En's Project Portfolio Page +--- + +### Project: NUSearch + +NUSearch is a desktop app for managing NUS contacts, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). +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 save frequently used contacts to a favourites list. + * What it does: This feature allows the user to select contacts to be saved to a separate favourites list. + * Justification: This feature improves the product significantly because it provides users easy access to contacts that they may frequently use, or contacts that deem important. + * Highlights: The implementation of this feature required improving the existing attribute fields of contacts, which affected existing commands and test cases. Many existing commands had to be improved to accommodate this new feature, and test cases had to be supplemented. + +* **New Feature**: Added the ability to delete frequently used contacts from a favourites list. + * What it does: This feature allows the user to delete selected contacts from their favourites list. + * Justification: This feature improves the product significantly because it allows users to make amendments to their favourites list, improving the functionality of the product. + * Highlights: The implementation of this feature required improving the existing attribute fields of contacts, which affected existing commands and test cases. Many existing commands had to be improved to accommodate this new feature, and test cases had to be supplemented. + +* **New Feature**: Added the ability for users to display a list of their favourite contacts. + * What it does: This feature allows the user to display a list of their favourite contacts. The user can then execute commands on this list of favourites (eg edit, delete). + * Justification: This feature allows users to view a list of their favourite contacts, granting users access to contacts that they may frequently use, or contacts that they deem are important. This function reduces the clutter from unimportant or unused contacts on the user’s screen and increases the ease of managing contacts. + * Highlights: The implementation of this feature required an in-depth analysis of design alternatives as there were many potential ways to implement the feature. The implementation too was challenging as it required improving the existing attribute fields of contacts, which affected existing commands and test cases. Many existing commands had to be improved to accommodate this new feature, and test cases had to be supplemented. + +* **New Feature**: Added the ability for users to copy the phone number of contacts to their clipboard. + * What it does: This feature allows the user to copy the phone number of selected contacts to their clipboard. + * Justification: This feature improves the product significantly because it allows the user to copy the phone number of a contact easily, which can then be pasted to other applications or text fields. + * Highlights: This enhancement affects some parts of the model and it required an in-depth analysis of design alternatives. The implementation was challenging as it required imports from an external java.awt package which was unfamiliar to the team. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=zoom&zFR=false&zA=tanweien&zR=AY2122S2-CS2103T-W11-4%2Ftp%5Bmaster%5D&zACS=198.13128430296376&zS=2022-02-18&zFS=&zU=2022-04-08&zMG=false&zFTF=commit&zFGS=groupByRepos) + +* **Project management**: + * Managed releases `v1.1` - `v1.4` (5 releases) on GitHub + +* **Enhancements to existing features**: + * Improved the `role` feature by allowing users to only select from a limited selection of pre-determined roles to reduce bugs: [\#165](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/165) + * Improved the `faculty` feature by allowing users to only select from a limited selection of pre-determined faculties to reduce bugs: [\#165](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/165) + * Improved how the `role` and `faculty` fields are displayed in the UI: [\#165](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/165) + * Wrote additional tests for existing features to increase coverage: [\#74](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/74), [\#97](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/97), [\#165](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/165) + +* **Documentation**: + * User Guide: + * Added documentation for the features `fav`, `unfav`, `list-fav`, `copy-email` and `copy-phone` [\#98](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/98) + * Improved on Frequently Asked Questions: [\#33](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/33) + * Added a command summary table for existing commands: [\#33](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/33) + * Developer Guide: + * Updated `Design` and `Architecture` sections in the Developer Guide: [\#88](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/88) + * Updated non-functional requirements in the Developer Guide: [\#36](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/36) + * Diagrams: + * Updated BetterModelClass Diagram: [\#88](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/88), [\#89](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/89) + * Updated ComponentManagers Diagram: : [\#88](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/88), [\#89](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/89) + +* **Community**: + * PRs reviewed (with non-trivial review comments): [#164](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/164) + * Reported bugs and suggestions for other teams in the class: [1](https://github.com/tanweien/ped/issues/1), [2](https://github.com/tanweien/ped/issues/2), [3](https://github.com/tanweien/ped/issues/3), [4](https://github.com/tanweien/ped/issues/4), [5](https://github.com/tanweien/ped/issues/5), [6](https://github.com/tanweien/ped/issues/6), [7](https://github.com/tanweien/ped/issues/7), [8](https://github.com/tanweien/ped/issues/8) + * Reviewed and approved PRs: [\#22](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/22), [\#30](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/30), [\#35](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/35), [\#38](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/38), [\#164](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/164), [\#168](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/168), [\#169](https://github.com/AY2122S2-CS2103T-W11-4/tp/pull/169) + +* **Team-based tasked**: + * Maintained the issue tracker + * Managed and published releases + * Assigned tasks to members + diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..31099fdf304 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -8,6 +8,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandManager; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.AddressBookParser; @@ -27,6 +28,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; private final AddressBookParser addressBookParser; + private final CommandManager commandManager; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -35,6 +37,7 @@ public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; addressBookParser = new AddressBookParser(); + commandManager = new CommandManager(model); } @Override @@ -43,7 +46,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE CommandResult commandResult; Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); + + //pass command to command handler with command and model. + commandResult = commandManager.insertCommand(command); + + //During this, model address book is getting updated. try { storage.saveAddressBook(model.getAddressBook()); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..305f01c98bd 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,21 +1,28 @@ 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.util.CollectionUtil.requireAllNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FACULTY; 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_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; import seedu.address.model.person.Person; /** * Adds a person to the address book. */ public class AddCommand extends Command { - public static final String COMMAND_WORD = "add"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " @@ -23,20 +30,30 @@ public class AddCommand extends Command { + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_FACULTY + "FACULTY " + + PREFIX_ROLE + "ROLE " + + "[" + PREFIX_TELEGRAM + "TELEGRAM] " + "[" + 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_FACULTY + "Computing " + + PREFIX_ROLE + "Professor " + + PREFIX_TELEGRAM + "@JohnyD845 " + 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"; + public static final String MESSAGE_DUPLICATE_EMAIL = "A contact with the same email address already " + + "exists in the address book"; + public static final String MESSAGE_DUPLICATE_PHONE = "A contact with the same number already " + + "exists in the address book"; + private static final Logger logger = LogsCenter.getLogger(AddCommand.class); private final Person toAdd; + private ModelMemento modelMemento; /** * Creates an AddCommand to add the specified {@code Person} @@ -49,19 +66,47 @@ public AddCommand(Person person) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + logger.fine("Adding contact into NUSearch."); - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } + modelContains(toAdd, model); + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); model.addPerson(toAdd); return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); } + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Addition of contact.", false, false); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddCommand // instanceof handles nulls && toAdd.equals(((AddCommand) other).toAdd)); } + + /** + * The check to check if model contains duplicate contacts. Catches duplicate errors sequentially + * @param toAdd the person to check + * @param model the model to check against + * @throws CommandException exception to throw + */ + public void modelContains(Person toAdd, Model model) throws CommandException { + requireAllNonNull(toAdd, model); + if (model.hasPerson(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + if (model.hasEmail(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_EMAIL); + } + + if (model.hasPhone(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PHONE); + } + } } diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..907e3c4fd46 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,22 +2,33 @@ import static java.util.Objects.requireNonNull; +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.ModelMemento; /** * Clears the address book. */ public class ClearCommand extends Command { - public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + private ModelMemento modelMemento; @Override public CommandResult execute(Model model) { requireNonNull(model); + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); model.setAddressBook(new AddressBook()); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Clearing of contact list.", false, false); + } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..c8ac4917ed1 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -7,7 +7,6 @@ * Represents a command with hidden internal logic and the ability to be executed. */ public abstract class Command { - /** * Executes the command and returns the result message. * @@ -16,5 +15,12 @@ public abstract class Command { * @throws CommandException If an error occurs during command execution. */ public abstract CommandResult execute(Model model) throws CommandException; + public abstract CommandResult unExecute(Model model) throws CommandException; + public boolean isUndo() { + return false; + }; + public boolean isRedo() { + return false; + } } diff --git a/src/main/java/seedu/address/logic/commands/CommandManageable.java b/src/main/java/seedu/address/logic/commands/CommandManageable.java new file mode 100644 index 00000000000..5e4f9520512 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CommandManageable.java @@ -0,0 +1,10 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; + +public interface CommandManageable { + CommandResult insertCommand(Command currentCommand) throws CommandException; + void refreshFutureCommands(int undoRedoPointer); + CommandResult undo() throws CommandException; + CommandResult redo() throws CommandException; +} diff --git a/src/main/java/seedu/address/logic/commands/CommandManager.java b/src/main/java/seedu/address/logic/commands/CommandManager.java new file mode 100644 index 00000000000..d2c127411e7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CommandManager.java @@ -0,0 +1,98 @@ +package seedu.address.logic.commands; +import java.util.Stack; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +public class CommandManager implements CommandManageable { + private static final Logger logger = LogsCenter.getLogger(CommandManager.class); + private int commandStackPointer = -1; + private final Stack commandStack = new Stack<>(); + private final Model model; + + public CommandManager(Model modelManager) { + this.model = modelManager; + } + + public int getCommandStackPointer() { + return commandStackPointer; + } + + public Stack getCommandStack() { + return commandStack; + } + + public Model getModel() { + return model; + } + + @Override + public CommandResult insertCommand(Command currentCommand) throws CommandException { + CommandResult toReturn; + try { + //if undo + if (currentCommand.isUndo()) { + CommandResult undidCommand = undo(); + String undidMessage = undidCommand.getFeedbackToUser(); + boolean isShowHelp = undidCommand.isShowHelp(); + return new CommandResult("Undid command: " + undidMessage, isShowHelp, false); + } + //if redo + if (currentCommand.isRedo()) { + CommandResult redidCommand = redo(); + String redidMessage = redidCommand.getFeedbackToUser(); + boolean isShowHelp = redidCommand.isShowHelp(); + return new CommandResult("Redid command: " + redidMessage, isShowHelp, false); + } + } catch (Exception err) { + //handle error + } + + // else + refreshFutureCommands(commandStackPointer); + commandStack.push(currentCommand); + toReturn = currentCommand.execute(model); + commandStackPointer++; + return toReturn; + } + + @Override + public void refreshFutureCommands(int undoRedoPointer) { + logger.fine("Attempting to insert new command"); + if (commandStack.size() < 1) { + return; + } + if (commandStack.size() > undoRedoPointer + 1) { + commandStack.subList(undoRedoPointer + 1, commandStack.size()).clear(); + } + } + + @Override + public CommandResult undo() throws CommandException { + logger.fine("Attempting to undo command"); + if (commandStackPointer < 0) { + //i.e no past commands to execute + return new CommandResult("There are no commands to undo!", false, false); + } + + Command command = commandStack.get(commandStackPointer); + commandStackPointer--; + return command.unExecute(model); + } + + @Override + public CommandResult redo() throws CommandException { + logger.fine("Attempting to redo command"); + if (commandStackPointer == commandStack.size() - 1) { + //i.e no future commands to execute + return new CommandResult("There are no commands to redo!", false, false); + } + + commandStackPointer++; + Command command = commandStack.get(commandStackPointer); + System.out.println(command); + return command.execute(model); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..58d31d8c754 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -46,6 +46,10 @@ public boolean isExit() { return exit; } + public boolean isCloseHelp() { + return feedbackToUser.equals("Undid command: Show help"); + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/logic/commands/CopyEmailCommand.java b/src/main/java/seedu/address/logic/commands/CopyEmailCommand.java new file mode 100644 index 00000000000..2716ef22e9d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CopyEmailCommand.java @@ -0,0 +1,82 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.Person; + +/** + * Copies the email address of person identified using it's displayed index from the address book. + */ +public class CopyEmailCommand extends Command { + public static final String COMMAND_WORD = "copy-email"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Copies the email address of person identified by the\n" + + " index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer greater than\n" + + " 0 and less than 2147483648)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_COPY_PERSON_SUCCESS = "Copied email address: %1$s"; + private static final Logger logger = LogsCenter.getLogger(CopyEmailCommand.class); + + + private final Index targetIndex; + private ModelMemento modelMemento; + private Person copyPerson; + + public CopyEmailCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.fine("Copying contact."); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + logger.log(Level.WARNING, "processing error"); + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToCopy = lastShownList.get(targetIndex.getZeroBased()); + this.copyPerson = personToCopy; + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + + String emailToCopy = copyPerson.getEmail().value; + StringSelection stringSelection = new StringSelection(emailToCopy); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(stringSelection, null); + + return new CommandResult(String.format(MESSAGE_COPY_PERSON_SUCCESS, emailToCopy)); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Copy of contact cannot be done.", false, false); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CopyEmailCommand // instanceof handles nulls + && targetIndex.equals(((CopyEmailCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/CopyPhoneCommand.java b/src/main/java/seedu/address/logic/commands/CopyPhoneCommand.java new file mode 100644 index 00000000000..4b61d1cde28 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CopyPhoneCommand.java @@ -0,0 +1,81 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.Person; +/** + * Copies the phone number of person identified using it's displayed index from the address book. + */ +public class CopyPhoneCommand extends Command { + public static final String COMMAND_WORD = "copy-phone"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Copies the phone number of person identified by the\n" + + " index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer greater than\n" + + " 0 and less than 2147483648)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_COPY_PERSON_SUCCESS = "Copied phone number: %1$s"; + private static final Logger logger = LogsCenter.getLogger(CopyPhoneCommand.class); + + + private final Index targetIndex; + private ModelMemento modelMemento; + private Person copyPerson; + + public CopyPhoneCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.fine("Copying contact."); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + logger.log(Level.WARNING, "processing error"); + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToCopy = lastShownList.get(targetIndex.getZeroBased()); + this.copyPerson = personToCopy; + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + + String phoneToCopy = copyPerson.getPhone().value; + StringSelection stringSelection = new StringSelection(phoneToCopy); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(stringSelection, null); + + return new CommandResult(String.format(MESSAGE_COPY_PERSON_SUCCESS, phoneToCopy)); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Copy of contact cannot be done.", false, false); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CopyPhoneCommand // instanceof handles nulls + && targetIndex.equals(((CopyPhoneCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..4caf877fa16 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -8,6 +8,8 @@ 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.ModelMemento; import seedu.address.model.person.Person; /** @@ -18,13 +20,16 @@ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" + + ": Deletes the person identified by the index number used\n" + + " in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer greater than\n" + + " 0 and less than 2147483648)\n" + "Example: " + COMMAND_WORD + " 1"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; private final Index targetIndex; + private ModelMemento modelMemento; public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; @@ -40,10 +45,19 @@ public CommandResult execute(Model model) throws CommandException { } Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + model.deletePerson(personToDelete); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); } + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Deletion of contact.", false, false); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..c0913cd0ebc 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,11 +1,13 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FACULTY; 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_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -19,28 +21,36 @@ import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; +import seedu.address.model.person.Favourite; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Edits the details of an existing person in the address book. */ public class EditCommand extends Command { - public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits the details of the person identified by the\n" + + " index number used in the displayed person list.\n" + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " + + "Parameters: INDEX (must be a positive integer greater than\n" + + " 0 and less than 2147483648)\n" + + " [" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_FACULTY + "FACULTY]\n" + + " [" + PREFIX_ROLE + "ROLE] " + + "[" + PREFIX_TELEGRAM + "TELEGRAM] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " @@ -52,6 +62,8 @@ public class EditCommand extends Command { private final Index index; private final EditPersonDescriptor editPersonDescriptor; + private ModelMemento modelMemento; + private Person editedPerson; /** * @param index of the person in the filtered person list to edit @@ -81,11 +93,21 @@ public CommandResult execute(Model model) throws CommandException { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + this.editedPerson = personToEdit; + + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Editing of contact.", false, false); + } + /** * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. @@ -96,10 +118,15 @@ 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()); + Faculty updatedFaculty = editPersonDescriptor.getFaculty().orElse(personToEdit.getFaculty()); + Role updatedRole = editPersonDescriptor.getRole().orElse(personToEdit.getRole()); + Telegram updatedTelegram = editPersonDescriptor.getTelegram().orElse(personToEdit.getTelegram()); + Favourite updatedFavourite = personToEdit.getFavourite(); // does not allow editing favourite Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + + return new Person(updatedName, updatedPhone, updatedEmail, updatedFaculty, updatedRole, updatedTelegram, + updatedFavourite, updatedTags); } @Override @@ -128,7 +155,10 @@ public static class EditPersonDescriptor { private Name name; private Phone phone; private Email email; - private Address address; + private Faculty faculty; + private Role role; + private Telegram telegram; + private Favourite favourite; private Set tags; public EditPersonDescriptor() {} @@ -141,7 +171,10 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setFaculty(toCopy.faculty); + setRole(toCopy.role); + setTelegram(toCopy.telegram); + setFavourite(toCopy.favourite); setTags(toCopy.tags); } @@ -149,7 +182,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, faculty, role, telegram, tags); } public void setName(Name name) { @@ -176,12 +209,36 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setFaculty(Faculty faculty) { + this.faculty = faculty; + } + + public Optional getFaculty() { + return Optional.ofNullable(faculty); + } + + public void setRole(Role role) { + this.role = role; + } + + public Optional getRole() { + return Optional.ofNullable(role); + } + + public void setTelegram(Telegram telegram) { + this.telegram = telegram; + } + + public Optional getTelegram() { + return Optional.ofNullable(telegram); + } + + public void setFavourite(Favourite favourite) { + this.favourite = favourite; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getFavourite() { + return Optional.ofNullable(favourite); } /** @@ -219,7 +276,9 @@ public boolean equals(Object other) { return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) + && getFaculty().equals(e.getFaculty()) + && getRole().equals(e.getRole()) + && getTelegram().equals(e.getTelegram()) && 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..0cc1b81254b 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -1,5 +1,6 @@ package seedu.address.logic.commands; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; /** @@ -16,4 +17,9 @@ public CommandResult execute(Model model) { return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); } + @Override + public CommandResult unExecute(Model model) throws CommandException { + throw new CommandException("It is impossible to un-execute an exit command."); + } + } diff --git a/src/main/java/seedu/address/logic/commands/FavouriteCommand.java b/src/main/java/seedu/address/logic/commands/FavouriteCommand.java new file mode 100644 index 00000000000..1c7633fa8c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FavouriteCommand.java @@ -0,0 +1,103 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; +import seedu.address.model.person.Favourite; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; +import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; + +/** + * Changes the favourite status of an existing person in the address book. + */ +public class FavouriteCommand extends Command { + + public static final String COMMAND_WORD = "fav"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Changes the favourite status of a person identified by\n" + + " the index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer greater than\n" + + " 0 and less than 2147483648)\n" + + "Example: " + COMMAND_WORD + " 1"; + + + public static final String MESSAGE_FAVOURITE_PERSON_SUCCESS = "Added to Favourites Person: %1$s"; + + public static final String MESSAGE_DUPLICATE_PERSON = "This person is already saved to favourites."; + + private final Index targetIndex; + + private ModelMemento modelMemento; + private Person personToFavourite; + + public FavouriteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + this.personToFavourite = lastShownList.get(targetIndex.getZeroBased()); + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + Person favouritedPerson = createFavouritedPerson(personToFavourite); + + if (personToFavourite.isSameFavouritePerson(favouritedPerson) && model.hasFavouritePerson(favouritedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(personToFavourite, favouritedPerson); + return new CommandResult(String.format(MESSAGE_FAVOURITE_PERSON_SUCCESS, favouritedPerson)); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Favourite contact."); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FavouriteCommand // instanceof handles nulls + && targetIndex.equals(((FavouriteCommand) other).targetIndex)); // state check + } + + private static Person createFavouritedPerson(Person personToFavourite) { + assert personToFavourite != null; + + Name updatedName = personToFavourite.getName(); + Phone updatedPhone = personToFavourite.getPhone(); + Email updatedEmail = personToFavourite.getEmail(); + Faculty updatedFaculty = personToFavourite.getFaculty(); + Role updatedRole = personToFavourite.getRole(); + Telegram updatedTelegram = personToFavourite.getTelegram(); + Favourite updatedFavourite = new Favourite(true); // edit command does not allow editing favourite status + Set updatedTags = personToFavourite.getTags(); + + + return new Person(updatedName, updatedPhone, updatedEmail, updatedFaculty, updatedRole, + updatedTelegram, updatedFavourite, updatedTags); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..2ff4f78502d 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -3,36 +3,56 @@ import static java.util.Objects.requireNonNull; import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.NameFacultyRoleContainsAllKeywordsPredicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { - public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all persons whose name, role or faculty contain all of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Example: " + COMMAND_WORD + " alice computing professor"; - private final NameContainsKeywordsPredicate predicate; + private final NameFacultyRoleContainsAllKeywordsPredicate predicate; + private ModelMemento modelMemento; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(NameFacultyRoleContainsAllKeywordsPredicate predicate) { this.predicate = predicate; } @Override public CommandResult execute(Model model) { requireNonNull(model); + + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + modelMemento.setPredicate(model.getModelPredicate()); + model.updateFilteredPersonList(predicate); return new CommandResult( String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); } + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.updateFilteredPersonList(modelMemento.getPredicate()); + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + //bug above: 2 possibilities: + //1. not saving model in execute command + //2. Not drawing the right model in unexecute command + //3. Not updating filtered person list with the right predicate -> Needs to be pervious predicate + return new CommandResult("Find contacts with specified keywords."); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/FindWideCommand.java b/src/main/java/seedu/address/logic/commands/FindWideCommand.java new file mode 100644 index 00000000000..297617f9a87 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindWideCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindWideCommand extends Command { + public static final String COMMAND_WORD = "find-wide"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all persons whose name, role or faculty contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob computing"; + + private final NameFacultyRoleContainsAnyKeywordsPredicate predicate; + private ModelMemento modelMemento; + + public FindWideCommand(NameFacultyRoleContainsAnyKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + modelMemento.setPredicate(model.getModelPredicate()); + + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + model.updateFilteredPersonList(modelMemento.getPredicate()); + return new CommandResult("Find contacts with specified keywords."); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindWideCommand // instanceof handles nulls + && predicate.equals(((FindWideCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..5e1610f088c 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -1,12 +1,12 @@ package seedu.address.logic.commands; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; /** * Format full help instructions for every command for display. */ public class HelpCommand extends Command { - public static final String COMMAND_WORD = "help"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" @@ -18,4 +18,9 @@ public class HelpCommand extends Command { public CommandResult execute(Model model) { return new CommandResult(SHOWING_HELP_MESSAGE, true, false); } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + return new CommandResult("Show help", false, false); + } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..49a618b85fd 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -3,22 +3,37 @@ import static java.util.Objects.requireNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; /** * Lists all persons in the address book to the user. */ public class ListCommand extends Command { - public static final String COMMAND_WORD = "list"; public static final String MESSAGE_SUCCESS = "Listed all persons"; + private ModelMemento modelMemento; @Override public CommandResult execute(Model model) { requireNonNull(model); + + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + modelMemento.setPredicate(model.getModelPredicate()); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + model.updateFilteredPersonList(modelMemento.getPredicate()); + return new CommandResult("List all contacts.", false, false); + } } diff --git a/src/main/java/seedu/address/logic/commands/ListFavouritesCommand.java b/src/main/java/seedu/address/logic/commands/ListFavouritesCommand.java new file mode 100644 index 00000000000..a684957d67a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListFavouritesCommand.java @@ -0,0 +1,39 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_FAVOURITES; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; + +/** + * Lists all persons in the address book to the user. + */ +public class ListFavouritesCommand extends Command { + + public static final String COMMAND_WORD = "list-fav"; + + public static final String MESSAGE_SUCCESS = "Listed all favourites"; + private ModelMemento modelMemento; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + modelMemento.setPredicate(model.getModelPredicate()); + + model.updateFilteredPersonList(PREDICATE_SHOW_FAVOURITES); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + model.updateFilteredPersonList(modelMemento.getPredicate()); + return new CommandResult("List all favourite contacts", false, false); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java new file mode 100644 index 00000000000..4a481409922 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + + +public class RedoCommand extends Command { + public static final String COMMAND_WORD = "redo"; + + public RedoCommand() { + + } + + @Override + public CommandResult execute(Model model) throws CommandException { + return new CommandResult("Redid command", false, false); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + throw new CommandException("Redo command should not be un-executed."); + } + + @Override + public boolean isRedo() { + return true; + } +} diff --git a/src/main/java/seedu/address/logic/commands/TagCommand.java b/src/main/java/seedu/address/logic/commands/TagCommand.java new file mode 100644 index 00000000000..56510fa39de --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TagCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.TagContainsKeywordsPredicate; + +/** + * Finds and lists all persons in address book whose tags contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class TagCommand extends Command { + + public static final String COMMAND_WORD = "tag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose tags contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " CS2103T CS2100 KentRidge bestie"; + + private final TagContainsKeywordsPredicate predicate; + private ModelMemento modelMemento; + + public TagCommand(TagContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + modelMemento.setPredicate(model.getModelPredicate()); + + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.updateFilteredPersonList(modelMemento.getPredicate()); + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Find contacts with specified tag."); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagCommand // instanceof handles nulls + && predicate.equals(((TagCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..448514a47e5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +public class UndoCommand extends Command { + public static final String COMMAND_WORD = "undo"; + + public UndoCommand() { + + } + + public CommandResult execute(Model model) throws CommandException { + return new CommandResult("Undid command", false, false); + } + + //not used + @Override + public CommandResult unExecute(Model model) throws CommandException { + throw new CommandException("Undo command should not be un-executed."); + } + + @Override + public boolean isUndo() { + return true; + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnfavouriteCommand.java b/src/main/java/seedu/address/logic/commands/UnfavouriteCommand.java new file mode 100644 index 00000000000..c06099c4b12 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnfavouriteCommand.java @@ -0,0 +1,102 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ModelMemento; +import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; +import seedu.address.model.person.Favourite; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; +import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; + +/** + * Changes the favourite status of an existing person in the address book. + */ +public class UnfavouriteCommand extends Command { + + public static final String COMMAND_WORD = "unfav"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Changes the favourite status of a person identified by\n" + + " the index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer greater than\n" + + " 0 and less than 2147483648)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNFAVOURITE_PERSON_SUCCESS = "Removed From Favourites Person: %1$s"; + + public static final String MESSAGE_DUPLICATE_PERSON = "This person is already removed from favourites."; + + private final Index targetIndex; + private ModelMemento modelMemento; + private Person personToUnfavourite; + + public UnfavouriteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + this.personToUnfavourite = lastShownList.get(targetIndex.getZeroBased()); + this.modelMemento = new ModelMemento(); + modelMemento.setModel(new ModelManager(model.makeCopy())); + Person unfavouritedPerson = createUnfavouritedPerson(personToUnfavourite); + + if (personToUnfavourite.isSameFavouritePerson(unfavouritedPerson) + && model.hasFavouritePerson(unfavouritedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(personToUnfavourite, unfavouritedPerson); + return new CommandResult(String.format(MESSAGE_UNFAVOURITE_PERSON_SUCCESS, unfavouritedPerson)); + } + + @Override + public CommandResult unExecute(Model model) throws CommandException { + model.setAddressBook(this.modelMemento.getModel().getAddressBook()); + return new CommandResult("Un-favourite contact."); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnfavouriteCommand // instanceof handles nulls + && targetIndex.equals(((UnfavouriteCommand) other).targetIndex)); // state check + } + + private static Person createUnfavouritedPerson(Person personToUnfavourite) { + assert personToUnfavourite != null; + + Name updatedName = personToUnfavourite.getName(); + Phone updatedPhone = personToUnfavourite.getPhone(); + Email updatedEmail = personToUnfavourite.getEmail(); + Faculty updatedFaculty = personToUnfavourite.getFaculty(); + Role updatedRole = personToUnfavourite.getRole(); + Telegram updatedTelegram = personToUnfavourite.getTelegram(); + Favourite updatedFavourite = new Favourite(false); // edit command does not allow editing favourite status + Set updatedTags = personToUnfavourite.getTags(); + + + return new Person(updatedName, updatedPhone, updatedEmail, updatedFaculty, updatedRole, + updatedTelegram, updatedFavourite, updatedTags); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..ff30d9172c4 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,23 +1,28 @@ 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_FACULTY; 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_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; 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.Faculty; +import seedu.address.model.person.Favourite; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Parses input arguments and creates a new AddCommand object @@ -31,21 +36,24 @@ public class AddCommandParser implements Parser { */ 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_FACULTY, PREFIX_ROLE, + PREFIX_TELEGRAM, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_FACULTY, PREFIX_ROLE) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Faculty faculty = ParserUtil.parseFaculty(argMultimap.getValue(PREFIX_FACULTY).orElse(null)); + Role role = ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()); + //may have some problems here because of how getValue works + Telegram telegram = ParserUtil.parseTelegram(argMultimap.getValue(PREFIX_TELEGRAM)); + Favourite favourite = new Favourite(false); // default value is false Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - + Person person = new Person(name, phone, email, faculty, role, telegram, favourite, tagList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..56abbfbc399 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,12 +9,21 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CopyEmailCommand; +import seedu.address.logic.commands.CopyPhoneCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FavouriteCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindWideCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListFavouritesCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.TagCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.UnfavouriteCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -53,24 +62,50 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case FavouriteCommand.COMMAND_WORD: + return new FavouriteCommandParser().parse(arguments); + + case UnfavouriteCommand.COMMAND_WORD: + return new UnfavouriteCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FindWideCommand.COMMAND_WORD: + return new FindWideCommandParser().parse(arguments); + + case TagCommand.COMMAND_WORD: + return new TagCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); + case ListFavouritesCommand.COMMAND_WORD: + return new ListFavouritesCommand(); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case CopyEmailCommand.COMMAND_WORD: + return new CopyEmailCommandParser().parse(arguments); + + case CopyPhoneCommand.COMMAND_WORD: + return new CopyPhoneCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } - } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..3c6b73ee3be 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -117,7 +117,6 @@ private static String extractArgumentValue(String argsString, PrefixPosition currentPrefixPosition, PrefixPosition nextPrefixPosition) { Prefix prefix = currentPrefixPosition.getPrefix(); - int valueStartPos = currentPrefixPosition.getStartPosition() + prefix.getPrefix().length(); String value = argsString.substring(valueStartPos, nextPrefixPosition.getStartPosition()); diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..b2a9f5c60a4 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -9,7 +9,9 @@ public class CliSyntax { public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + public static final Prefix PREFIX_FACULTY = new Prefix("f/"); + public static final Prefix PREFIX_ROLE = new Prefix("r/"); + public static final Prefix PREFIX_TELEGRAM = new Prefix("tele/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); } diff --git a/src/main/java/seedu/address/logic/parser/CopyEmailCommandParser.java b/src/main/java/seedu/address/logic/parser/CopyEmailCommandParser.java new file mode 100644 index 00000000000..d31f887522e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CopyEmailCommandParser.java @@ -0,0 +1,25 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CopyEmailCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class CopyEmailCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CopyEmailCommand + * and returns a CopyEmailCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CopyEmailCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new CopyEmailCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CopyEmailCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/CopyPhoneCommandParser.java b/src/main/java/seedu/address/logic/parser/CopyPhoneCommandParser.java new file mode 100644 index 00000000000..e9fd661dc24 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CopyPhoneCommandParser.java @@ -0,0 +1,25 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CopyPhoneCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class CopyPhoneCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CopyPhoneCommand + * and returns a CopyPhoneCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CopyPhoneCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new CopyPhoneCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CopyPhoneCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..9dc98f89424 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,11 +2,13 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FACULTY; 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_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; import java.util.Collection; import java.util.Collections; @@ -18,6 +20,7 @@ import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Parses input arguments and creates a new EditCommand object @@ -32,7 +35,8 @@ 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_FACULTY, PREFIX_ROLE, + PREFIX_TELEGRAM, PREFIX_TAG); Index index; @@ -52,9 +56,13 @@ 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_FACULTY).isPresent()) { + editPersonDescriptor.setFaculty(ParserUtil.parseFaculty(argMultimap.getValue(PREFIX_FACULTY).get())); } + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + editPersonDescriptor.setRole(ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get())); + } + parseTelegramForEdit(argMultimap.getValue(PREFIX_TELEGRAM)).ifPresent(editPersonDescriptor::setTelegram); parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { @@ -79,4 +87,13 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + private Optional parseTelegramForEdit(Optional telegram) throws ParseException { + assert telegram != null; + + if (telegram.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(ParserUtil.parseTelegram(telegram)); + } } diff --git a/src/main/java/seedu/address/logic/parser/FavouriteCommandParser.java b/src/main/java/seedu/address/logic/parser/FavouriteCommandParser.java new file mode 100644 index 00000000000..8e631018551 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FavouriteCommandParser.java @@ -0,0 +1,24 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.FavouriteCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class FavouriteCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code FavouriteCommand} + * and returns a {@code FavouriteCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FavouriteCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new FavouriteCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FavouriteCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..2306595c74f 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -6,7 +6,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAllKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object @@ -27,7 +27,7 @@ public FindCommand parse(String args) throws ParseException { String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindCommand(new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList(nameKeywords))); } } diff --git a/src/main/java/seedu/address/logic/parser/FindWideCommandParser.java b/src/main/java/seedu/address/logic/parser/FindWideCommandParser.java new file mode 100644 index 00000000000..b51c8109b6a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindWideCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindWideCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindWideCommandParser 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 FindWideCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindWideCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindWideCommand(new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..daa414e6330 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -4,16 +4,19 @@ import java.util.Collection; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -66,18 +69,23 @@ public static Phone parsePhone(String phone) throws ParseException { } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String telegram} into an {@code Telegram}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the given {@code telegram} 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); + public static Telegram parseTelegram(Optional telegram) throws ParseException { + //Empty string handled in telegram constructor + String trimmedTelegram = null; + if (telegram.isPresent()) { + trimmedTelegram = telegram.get().trim(); } - return new Address(trimmedAddress); + + if (telegram.isPresent() && !Telegram.isValidHandle(trimmedTelegram)) { + throw new ParseException(Telegram.MESSAGE_CONSTRAINTS); + } + + return new Telegram(Optional.ofNullable(trimmedTelegram)); } /** @@ -95,6 +103,36 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code String faculty} into an {@code Faculty}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code faculty} is invalid. + */ + public static Faculty parseFaculty(String faculty) throws ParseException { + requireNonNull(faculty); + String trimmedFaculty = faculty.trim(); + if (!Faculty.isValidFaculty(trimmedFaculty)) { + throw new ParseException(Faculty.MESSAGE_CONSTRAINTS); + } + return new Faculty(trimmedFaculty); + } + + /** + * Parses a {@code String role} into an {@code Role}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code role} is invalid. + */ + public static Role parseRole(String role) throws ParseException { + requireNonNull(role); + String trimmedRole = role.trim(); + if (!Role.isValidRole(trimmedRole)) { + throw new ParseException(Role.MESSAGE_CONSTRAINTS); + } + return new Role(trimmedRole); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. diff --git a/src/main/java/seedu/address/logic/parser/TagCommandParser.java b/src/main/java/seedu/address/logic/parser/TagCommandParser.java new file mode 100644 index 00000000000..10a9544de5e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TagCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.TagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.TagContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new TagCommand object + */ +public class TagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TagCommand + * and returns a TagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TagCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new TagCommand(new TagContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/UnfavouriteCommandParser.java b/src/main/java/seedu/address/logic/parser/UnfavouriteCommandParser.java new file mode 100644 index 00000000000..88ce0c2ef41 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnfavouriteCommandParser.java @@ -0,0 +1,24 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnfavouriteCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class UnfavouriteCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code FavouriteCommand} + * and returns a {@code FavouriteCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnfavouriteCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnfavouriteCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnfavouriteCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..56d44954236 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -23,11 +23,13 @@ public class AddressBook implements ReadOnlyAddressBook { * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication * among constructors. */ - { + public AddressBook() { persons = new UniquePersonList(); } - public AddressBook() {} + public AddressBook(UniquePersonList list) { + this.persons = list; + } /** * Creates an AddressBook using the Persons in the {@code toBeCopied} @@ -37,7 +39,9 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { resetData(toBeCopied); } - //// list overwrite operations + public UniquePersonList makeCopy() { + return new UniquePersonList(this.persons.makeCopy()); + } /** * Replaces the contents of the person list with {@code persons}. @@ -63,7 +67,31 @@ public void resetData(ReadOnlyAddressBook newData) { */ public boolean hasPerson(Person person) { requireNonNull(person); - return persons.contains(person); + return persons.containsName(person); + } + + /** + * Returns true if a person with the same identity and favourite as {@code person} exists in the address book. + */ + public boolean hasFavouritePerson(Person person) { + requireNonNull(person); + return persons.containsFavouriteName(person); + } + + /** + * Returns true if a person with the same email as {@code person} exists in the address book. + */ + public boolean hasEmail(Person person) { + requireNonNull(person); + return persons.containsEmail(person); + } + + /** + * Returns true if a person with the same phone as {@code person} exists in the address book. + */ + public boolean hasPhone(Person person) { + requireNonNull(person); + return persons.containsPhone(person); } /** diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..8d35682c661 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -14,6 +14,12 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that evaluate to true if person is a favourite */ + Predicate PREDICATE_SHOW_FAVOURITES = person -> + person.getFavourite().isFavourite; + + AddressBook makeCopy(); + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -57,6 +63,20 @@ public interface Model { */ boolean hasPerson(Person person); + /** + * Returns true if a person with the same identity and favourite as {@code person} exists in the address book. + */ + boolean hasFavouritePerson(Person person); + /** + * Returns true if a person with the same email as {@code person} exists in the address book. + */ + boolean hasEmail(Person person); + + /** + * Returns true if a person with the same phone as {@code person} exists in the address book. + */ + boolean hasPhone(Person person); + /** * Deletes the given person. * The person must exist in the address book. @@ -79,6 +99,8 @@ public interface Model { /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + Predicate getModelPredicate(); + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..81d60488ea3 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -22,9 +22,11 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private Predicate predicate; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * + * */ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { requireAllNonNull(addressBook, userPrefs); @@ -36,10 +38,27 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); } + /** + * empty constructor + */ public ModelManager() { this(new AddressBook(), new UserPrefs()); } + /** + * Constructor from given address book + * @param newAddressBook new one + */ + public ModelManager(AddressBook newAddressBook) { + this.addressBook = newAddressBook; + this.userPrefs = new UserPrefs(); + this.filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + } + + public AddressBook makeCopy() { + return new AddressBook(this.addressBook.makeCopy()); + } + //=========== UserPrefs ================================================================================== @Override @@ -93,6 +112,24 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public boolean hasFavouritePerson(Person person) { + requireNonNull(person); + return addressBook.hasFavouritePerson(person); + } + + @Override + public boolean hasEmail(Person person) { + requireNonNull(person); + return addressBook.hasEmail(person); + } + + @Override + public boolean hasPhone(Person person) { + requireNonNull(person); + return addressBook.hasPhone(person); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); @@ -122,9 +159,15 @@ public ObservableList getFilteredPersonList() { return filteredPersons; } + @Override + public Predicate getModelPredicate() { + return this.predicate; + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); + this.predicate = predicate; filteredPersons.setPredicate(predicate); } diff --git a/src/main/java/seedu/address/model/ModelMemento.java b/src/main/java/seedu/address/model/ModelMemento.java new file mode 100644 index 00000000000..6ef63f89d41 --- /dev/null +++ b/src/main/java/seedu/address/model/ModelMemento.java @@ -0,0 +1,36 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Optional; +import java.util.function.Predicate; + +import seedu.address.model.person.Person; + + +public class ModelMemento { + private Model modelState; + private Optional> modelPredicate; + + public Model getModel() { + return this.modelState; + } + + public Predicate getPredicate() { + return this.modelPredicate.orElse(PREDICATE_SHOW_ALL_PERSONS); + } + + public void setModel(Model newModelState) { + requireNonNull(newModelState); + if (this.modelState == null) { + this.modelState = newModelState; + } + } + + public void setPredicate(Predicate newPredicate) { + if (this.modelPredicate == null) { + this.modelPredicate = Optional.ofNullable(newPredicate); + } + } +} 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..b464dc60607 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -20,7 +20,8 @@ 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 " + + "or underscores, if any."; // alphanumeric and special characters private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" diff --git a/src/main/java/seedu/address/model/person/Faculty.java b/src/main/java/seedu/address/model/person/Faculty.java new file mode 100644 index 00000000000..9492bf77500 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Faculty.java @@ -0,0 +1,79 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Contact's faculty in the contact list. + * Guarantees: immutable; is valid as declared in {@link #isValidFaculty(String)} + */ +public class Faculty { + + + public static final String MESSAGE_CONSTRAINTS = "Faculty should not be blank " + + "and should only have the following values\n" + + "1. CHS\n" + + "2. Business\n" + + "3. Computing\n" + + "4. Dentistry\n" + + "5. CDE\n" + + "6. Law\n" + + "7. Medicine\n" + + "8. Nursing\n" + + "9. Pharmacy\n" + + "10. Music\n" + + "11. Others\n"; + /* + * 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 = "^[A-Za-z]+$"; + public final String value; + + /** + * Constructs a {@code Faculty}. + * + * @param faculty A valid faculty. + */ + public Faculty(String faculty) { + requireNonNull(faculty); + checkArgument(isValidFaculty(faculty), MESSAGE_CONSTRAINTS); + value = faculty.substring(0, 1).toUpperCase() + faculty.substring(1).toLowerCase(); + } + + /** + * Returns true if a given string is a valid faculty. + */ + public static boolean isValidFaculty(String test) { + return test.matches(VALIDATION_REGEX) + && (test.equalsIgnoreCase("CHS") + || test.equalsIgnoreCase("Business") + || test.equalsIgnoreCase("Computing") + || test.equalsIgnoreCase("CDE") + || test.equalsIgnoreCase("Dentistry") + || test.equalsIgnoreCase("Law") + || test.equalsIgnoreCase("Medicine") + || test.equalsIgnoreCase("Nursing") + || test.equalsIgnoreCase("Pharmacy") + || test.equalsIgnoreCase("Music") + || test.equalsIgnoreCase("Others")); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Faculty // instanceof handles nulls + && value.equals(((Faculty) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Favourite.java b/src/main/java/seedu/address/model/person/Favourite.java new file mode 100644 index 00000000000..f5ffee2cf10 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Favourite.java @@ -0,0 +1,36 @@ +package seedu.address.model.person; + +public class Favourite { + + public static final String MESSAGE_IS_FAVOURTIE = "true"; + public static final String MESSAGE_NOT_FAVOURTIE = "false"; + public static final String MESSAGE_CONSTRAINTS = "Favourite status should either be true or false"; + + public final boolean isFavourite; + + public Favourite(boolean isFavourite) { + this.isFavourite = isFavourite; + } + + @Override + public String toString() { + if (isFavourite) { + return MESSAGE_IS_FAVOURTIE; + } else { + return MESSAGE_NOT_FAVOURTIE; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Favourite // instanceof handles nulls + && isFavourite == ((Favourite) other).isFavourite); // state check + } + + @Override + public int hashCode() { + return this.toString().hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/NameFacultyRoleContainsAllKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameFacultyRoleContainsAllKeywordsPredicate.java new file mode 100644 index 00000000000..a4a163311bc --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameFacultyRoleContainsAllKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameFacultyRoleContainsAllKeywordsPredicate implements Predicate { + private final List keywords; + + public NameFacultyRoleContainsAllKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .allMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword) + || StringUtil.containsWordIgnoreCase(person.getFaculty().value, keyword) + || StringUtil.containsWordIgnoreCase(person.getRole().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameFacultyRoleContainsAllKeywordsPredicate // instanceof handles nulls + && keywords.equals(((NameFacultyRoleContainsAllKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/NameFacultyRoleContainsAnyKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameFacultyRoleContainsAnyKeywordsPredicate.java new file mode 100644 index 00000000000..5c5e7318d02 --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameFacultyRoleContainsAnyKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameFacultyRoleContainsAnyKeywordsPredicate implements Predicate { + private final List keywords; + + public NameFacultyRoleContainsAnyKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword) + || StringUtil.containsWordIgnoreCase(person.getFaculty().value, keyword) + || StringUtil.containsWordIgnoreCase(person.getRole().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameFacultyRoleContainsAnyKeywordsPredicate // instanceof handles nulls + && keywords.equals(((NameFacultyRoleContainsAnyKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..4dfd3600ffe 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -8,6 +8,7 @@ import java.util.Set; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Represents a Person in the address book. @@ -19,20 +20,27 @@ public class Person { private final Name name; private final Phone phone; private final Email email; + private final Faculty faculty; + private final Role role; // Data fields - private final Address address; + private final Telegram telegram; private final Set tags = new HashSet<>(); + private final Favourite favourite; /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Faculty faculty, Role role, Telegram telegram, + Favourite favourite, Set tags) { + requireAllNonNull(name, phone, email, role, telegram, tags); this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.faculty = faculty; + this.role = role; + this.telegram = telegram; + this.favourite = favourite; this.tags.addAll(tags); } @@ -48,8 +56,20 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + public Faculty getFaculty() { + return faculty; + } + + public Role getRole() { + return role; + } + + public Telegram getTelegram() { + return telegram; + } + + public Favourite getFavourite() { + return favourite; } /** @@ -73,6 +93,48 @@ public boolean isSamePerson(Person otherPerson) { && otherPerson.getName().equals(getName()); } + /** + * Returns true if both persons have the same name, and same favourite + * This defines a weaker notion of equality between two persons, but stronger than just name alone. + */ + public boolean isSameFavouritePerson(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + boolean result = otherPerson != null + && otherPerson.getName().equals(getName()) + && otherPerson.getFavourite().equals(getFavourite()); + + return result; + } + + /** + * Returns true if both persons have the same email. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSameEmail(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getEmail().equals(getEmail()); + } + + /** + * Returns true if both persons have the same email. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSamePhone(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getPhone().equals(getPhone()); + } + /** * Returns true if both persons have the same identity and data fields. * This defines a stronger notion of equality between two persons. @@ -91,32 +153,44 @@ public boolean equals(Object other) { return otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) + && otherPerson.getFaculty().equals(getFaculty()) + && otherPerson.getRole().equals(getRole()) + && otherPerson.getTelegram().equals(getTelegram()) + && otherPerson.getFavourite().equals(getFavourite()) && 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, faculty, role, telegram, favourite, tags); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append(getName()) - .append("; Phone: ") + .append("; \nPhone: ") .append(getPhone()) - .append("; Email: ") + .append("; \nEmail: ") .append(getEmail()) - .append("; Address: ") - .append(getAddress()); + .append("; \nFaculty: ") + .append(getFaculty()) + .append("; \nRole: ") + .append(getRole()) + .append("; \nTelegram: ") + .append(getTelegram()) + .append("; \nFavourite: ") + .append(getFavourite()); + Set tags = getTags(); if (!tags.isEmpty()) { - builder.append("; Tags: "); + builder.append("; \nTags: "); tags.forEach(builder::append); } + return builder.toString(); } diff --git a/src/main/java/seedu/address/model/person/Role.java b/src/main/java/seedu/address/model/person/Role.java new file mode 100644 index 00000000000..3b0c28e4b7c --- /dev/null +++ b/src/main/java/seedu/address/model/person/Role.java @@ -0,0 +1,78 @@ +package seedu.address.model.person; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Contact's role in the contact list. + * Guarantees: immutable; is valid as declared in {@link #isValidRole(String)} + */ +public class Role { + + + public static final String MESSAGE_CONSTRAINTS = "Role should not be blank " + + "and should only have the following values\n" + + "1. Professor\n" + + "2. Tutor\n" + + "3. Lecturer\n" + + "4. TA\n" + + "3. Researcher\n" + + "5. Admin\n" + + "6. Other"; + + /* + * 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 = "^[A-Za-z]+$"; + public final String value; + + /** + * Constructs a {@code Role}. + * + * @param role A valid role. + */ + public Role(String role) { + requireNonNull(role); + checkArgument(isValidRole(role), MESSAGE_CONSTRAINTS); + + if (role.equalsIgnoreCase("TA")) { + value = role.toUpperCase(); + } else { + value = role.substring(0, 1).toUpperCase() + role.substring(1).toLowerCase(); + } + } + + /** + * Returns true if a given string is a valid role. + */ + public static boolean isValidRole(String test) { + return test.matches(VALIDATION_REGEX) + && (test.equalsIgnoreCase("Professor") + || test.equalsIgnoreCase("Tutor") + || test.equalsIgnoreCase("Lecturer") + || test.equalsIgnoreCase("TA") + || test.equalsIgnoreCase("Researcher") + || test.equalsIgnoreCase("Admin") + || test.equalsIgnoreCase("Others")); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Role // instanceof handles nulls + && value.equals(((Role) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java new file mode 100644 index 00000000000..aed364b3df2 --- /dev/null +++ b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.person; + +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 Person}'s {@code Name} matches any of the keywords given. + */ +public class TagContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + for (String keyword : keywords) { + for (Tag tag : person.getTags()) { + if (StringUtil.containsWordIgnoreCase(tag.tagName, keyword)) { + return true; + } + } + } + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..80cf5ae35a0 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -24,25 +24,73 @@ */ public class UniquePersonList implements Iterable { - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); + private final ObservableList internalList; + private final ObservableList internalUnmodifiableList; /** - * Returns true if the list contains an equivalent person as the given argument. + * Empty constructor + */ + public UniquePersonList() { + this.internalList = FXCollections.observableArrayList(); + this.internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); + } + + /** + * Constructor + * @param persons list of persons */ - public boolean contains(Person toCheck) { + public UniquePersonList(ObservableList persons) { + this.internalList = persons; + this.internalUnmodifiableList = FXCollections + .unmodifiableObservableList(persons); + } + + public ObservableList makeCopy() { + return FXCollections.observableArrayList(internalList); + } + + /** + * Returns true i + * f the list contains an equivalent person as the given argument. + */ + public boolean containsName(Person toCheck) { requireNonNull(toCheck); return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true i + * f the list contains an equivalent person as the given argument. + */ + public boolean containsFavouriteName(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameFavouritePerson); + } + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean containsPhone(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePhone); + } + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean containsEmail(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameEmail); + } + /** * Adds a person to the list. * The person must not already exist in the list. */ public void add(Person toAdd) { requireNonNull(toAdd); - if (contains(toAdd)) { + if (containsName(toAdd)) { throw new DuplicatePersonException(); } internalList.add(toAdd); @@ -61,7 +109,7 @@ public void setPerson(Person target, Person editedPerson) { throw new PersonNotFoundException(); } - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { + if (!target.isSamePerson(editedPerson) && containsName(editedPerson)) { throw new DuplicatePersonException(); } @@ -134,4 +182,5 @@ private boolean personsAreUnique(List persons) { } return true; } + } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7..5e6474839e3 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -9,7 +9,8 @@ */ public class Tag { - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_CONSTRAINTS = "Tag names should: " + + "\n1. Be alphanumeric \n2. Not contain spaces\n3. Not be empty"; public static final String VALIDATION_REGEX = "\\p{Alnum}+"; public final String tagName; @@ -22,7 +23,7 @@ public class Tag { public Tag(String tagName) { requireNonNull(tagName); checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; + this.tagName = tagName.toLowerCase(); } /** diff --git a/src/main/java/seedu/address/model/telegram/Telegram.java b/src/main/java/seedu/address/model/telegram/Telegram.java new file mode 100644 index 00000000000..1f12bd98d2f --- /dev/null +++ b/src/main/java/seedu/address/model/telegram/Telegram.java @@ -0,0 +1,66 @@ +package seedu.address.model.telegram; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Optional; + + +/** + * Represents a Person's address in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidHandle(String)} + */ +public class Telegram { + + public static final String MESSAGE_CONSTRAINTS = "Telegram usernames should be of the format @name " + + "and adhere to the following constraints:\n" + + "- usernames should start with a '@' and followed by a name\n" + + "- name should be 5 to 32 characters long.\n" + + "- name should be alphanumeric.\n" + + "- name should not contain special characters except underscore.\n" + + "- name should not end with an underscore.\n"; + /* + * 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 = "@\\w{5,32}(? value; + + /** + * Constructs an {@code Telegram}. + * + * @param telegram A valid address. + */ + public Telegram(Optional telegram) { + if (telegram.isPresent()) { + checkArgument(isValidHandle(telegram.get()), MESSAGE_CONSTRAINTS); + this.value = telegram; + } else { + this.value = Optional.empty(); + } + } + + /** + * Returns true if a given string is a valid address. + */ + public static boolean isValidHandle(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value.orElse("--NIL--"); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Telegram // instanceof handles nulls + && value.orElse("--NIL--").equals(((Telegram) other).value.orElse("--NIL--"))); // state check + } + + @Override + public int hashCode() { + return value.orElse("--NIL--").hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..db1c464ef88 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,17 +1,21 @@ package seedu.address.model.util; import java.util.Arrays; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; +import seedu.address.model.person.Favourite; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -20,23 +24,23 @@ 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 Faculty("Computing"), new Role("Professor"), new Telegram(Optional.of("@LXyo191")), + new Favourite(false), 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 Faculty("Computing"), new Role("TA"), new Telegram(Optional.of("@berniceYu78")), + new Favourite(false), 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 Faculty("Computing"), new Role("Professor"), new Telegram(Optional.of("@COsir93")), + new Favourite(true), 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 Faculty("Computing"), new Role("TA"), new Telegram(Optional.of("@Splendidli39")), + new Favourite(false), 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 Faculty("Computing"), new Role("Professor"), new Telegram(Optional.of("@IIejeu39")), + new Favourite(true), 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 Faculty("Computing"), new Role("TA"), new Telegram(Optional.of("@Balarpy74")), + new Favourite(true), getTagSet("colleagues")) }; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..0ba158a1177 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -10,12 +11,15 @@ 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.Faculty; +import seedu.address.model.person.Favourite; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * Jackson-friendly version of {@link Person}. @@ -27,7 +31,10 @@ class JsonAdaptedPerson { private final String name; private final String phone; private final String email; - private final String address; + private final String faculty; + private final String role; + private final String telegram; + private final boolean favourite; private final List tagged = new ArrayList<>(); /** @@ -35,25 +42,34 @@ class JsonAdaptedPerson { */ @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + @JsonProperty("email") String email, @JsonProperty("faculty") String faculty, + @JsonProperty("role") String role, @JsonProperty("telegram") String telegram, + @JsonProperty("favourite") boolean favourite, + @JsonProperty("tagged") List tagged) { this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.faculty = faculty; + this.role = role; + this.telegram = telegram; + this.favourite = favourite; if (tagged != null) { this.tagged.addAll(tagged); } } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Person} into this class for Json use. + * From person to json */ public JsonAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; + faculty = source.getFaculty().value; + role = source.getRole().value; + telegram = source.getTelegram().value.orElse(null); + favourite = source.getFavourite().isFavourite; tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); @@ -94,16 +110,37 @@ 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 (faculty == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Faculty.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + if (!Faculty.isValidFaculty(faculty)) { + throw new IllegalValueException(Faculty.MESSAGE_CONSTRAINTS); } - final Address modelAddress = new Address(address); + final Faculty modelFaculty = new Faculty(faculty); + + if (role == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Role.class.getSimpleName())); + } + if (!Role.isValidRole(role)) { + throw new IllegalValueException(Role.MESSAGE_CONSTRAINTS); + } + final Role modelRole = new Role(role); + + + final Telegram modelTelegram; + + if (telegram != null && !Telegram.isValidHandle(telegram)) { + throw new IllegalValueException(Telegram.MESSAGE_CONSTRAINTS); + } else { + modelTelegram = new Telegram(Optional.ofNullable(telegram)); + } + + final Favourite modelFavourite = new Favourite(favourite); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + + return new Person(modelName, modelPhone, modelEmail, modelFaculty, modelRole, modelTelegram, + modelFavourite, modelTags); } } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 6cfa0162164..97a43a72cf3 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -17,8 +17,8 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; + private final AddressBookStorage addressBookStorage; + private final UserPrefsStorage userPrefsStorage; /** * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..63f095a9068 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -14,9 +14,63 @@ * Controller for a help page */ public class HelpWindow extends UiPart { + public static final String USERGUIDE_URL = + "https://ay2122s2-cs2103t-w11-4.github.io/tp/UserGuide.html"; + private static final String HELP_ADD_MESSAGE = + "\nTo add contact:\n add n/NAME p/PHONE_NUMBER e/EMAIL f/FACULTY r/ROLE " + + "[tele/TELEGRAM] [t/TAG]...\n"; + private static final String HELP_LIST_MESSAGE = + "\nTo display all contacts:\n list (IMPT: use 'list' to view index of contact when required)\n"; + private static final String HELP_EDIT_MESSAGE = + "\nTo edit:\n edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [f/FACULTY] [r/ROLE] " + + "[tele/TELEGRAM] [t/TAG]...\n"; + private static final String HELP_FAV_MESSAGE = + "\nTo add a contact to favourites:\n fav INDEX\n"; + private static final String HELP_UNFAV_MESSAGE = + "\nTo remove a contact from favourites:\n unfav INDEX (IMPT: use 'list-fav' to view index)\n"; + private static final String HELP_LISTFAV_MESSAGE = + "\nTo display all favourite contacts:\n list-fav\n"; + private static final String HELP_FIND_MESSAGE = + "\nTo find contacts that contains all of the given keywords:\n find KEYWORD [MORE_KEYWORDS]\n"; + private static final String HELP_FIND_WIDE_MESSAGE = + "\nTo find contacts that contains any of the given keywords:\n find-wide KEYWORD [MORE_KEYWORDS]\n"; + private static final String HELP_TAG_MESSAGE = + "\nTo find contacts that contains any of the given tags:\n tag KEYWORD [MORE_KEYWORDS]\n"; + private static final String HELP_COPY_EMAIL_MESSAGE = + "\nTo copy email of a contact:\n copy-email INDEX\n"; + private static final String HELP_COPY_PHONE_MESSAGE = + "\nTo copy phone number of a contact:\n copy-phone INDEX\n"; + private static final String HELP_DELETE_MESSAGE = + "\nTo delete a contact:\n delete INDEX\n"; + private static final String HELP_CLEAR_MESSAGE = + "\nTo delete all contacts:\n clear\n"; + private static final String HELP_UNDO_MESSAGE = + "\nTo undo command:\n undo\n"; + private static final String HELP_REDO_MESSAGE = + "\nTo redo an undo command:\n redo\n"; + private static final String HELP_EXIT_MESSAGE = + "\nTo exit:\n exit\n"; + public static final String HELP_MESSAGE = + "Refer to the user guide for more details: " + USERGUIDE_URL + "\n" + + HELP_ADD_MESSAGE + + HELP_LIST_MESSAGE + + HELP_EDIT_MESSAGE + + HELP_FAV_MESSAGE + + HELP_UNFAV_MESSAGE + + HELP_LISTFAV_MESSAGE + + HELP_FIND_MESSAGE + + HELP_FIND_WIDE_MESSAGE + + HELP_TAG_MESSAGE + + HELP_COPY_EMAIL_MESSAGE + + HELP_COPY_PHONE_MESSAGE + + HELP_DELETE_MESSAGE + + HELP_CLEAR_MESSAGE + + HELP_UNDO_MESSAGE + + HELP_REDO_MESSAGE + + HELP_EXIT_MESSAGE; + + - 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; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..0e6629ec1c4 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -163,6 +163,13 @@ private void handleExit() { primaryStage.hide(); } + @FXML + private void closeHelp() { + if (helpWindow.isShowing()) { + helpWindow.hide(); + } + } + public PersonListPanel getPersonListPanel() { return personListPanel; } @@ -186,6 +193,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + if (commandResult.isCloseHelp()) { + closeHelp(); + } + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..bff7d2d62c6 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -6,6 +6,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import seedu.address.model.person.Person; @@ -15,6 +16,14 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; + private static final String heart = new String(Character.toChars(10084)); + private static final String empty = ""; + private static final String telegramField = "Telegram: "; + private static final String phoneField = "Phone : "; + private static final String emailField = "Email : "; + private static final String facultyField = "Faculty : "; + private static final String roleField = "Role : "; + /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -35,10 +44,16 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; + private Label favourite; + @FXML + private Pane telegram; @FXML private Label email; @FXML + private Label faculty; + @FXML + private Label role; + @FXML private FlowPane tags; /** @@ -47,11 +62,14 @@ public class PersonCard extends UiPart { public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; - id.setText(displayedIndex + ". "); + 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(phoneField + person.getPhone().value); + person.getTelegram().value.ifPresent(handle -> telegram.getChildren().add(new Label(telegramField + handle))); + favourite.setText(person.getFavourite().isFavourite ? heart : empty); + email.setText(emailField + person.getEmail().value); + faculty.setText(facultyField + person.getFaculty().value); + role.setText(roleField + person.getRole().value); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); diff --git a/src/main/resources/images/magnifying_glass.png b/src/main/resources/images/magnifying_glass.png new file mode 100644 index 00000000000..83c4c8f4eb2 Binary files /dev/null and b/src/main/resources/images/magnifying_glass.png differ diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css index 17e8a8722cd..ab13fec6a30 100644 --- a/src/main/resources/view/HelpWindow.css +++ b/src/main/resources/view/HelpWindow.css @@ -1,19 +1,29 @@ #copyButton, #helpMessage { -fx-text-fill: white; + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; } #copyButton { - -fx-background-color: dimgray; + -fx-background-color: #fe7b0f ; + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; } #copyButton:hover { - -fx-background-color: gray; + -fx-background-color: #f5a856; + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; } #copyButton:armed { -fx-background-color: darkgray; + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; } #helpMessageContainer { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#004e88, 20%); + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index 5dea0adef70..032fc49972b 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -9,7 +9,12 @@ - + + + + + @@ -18,27 +23,28 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..637cec08dd8 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -12,14 +12,14 @@ + title="NUSearch" minWidth="600" minHeight="600" onCloseRequest="#handleExit"> - + - + diff --git a/src/main/resources/view/NusTheme.css b/src/main/resources/view/NusTheme.css new file mode 100644 index 00000000000..f668cc869c7 --- /dev/null +++ b/src/main/resources/view/NusTheme.css @@ -0,0 +1,352 @@ +.background { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #383838; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Courier New"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Courier New"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Courier New"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Courier New"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #4975A5; +} + +.list-cell:filled:odd { + -fx-background-color: #004e88; +} + +.list-cell:filled:selected { + -fx-background-color: #006496; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: white; +} + +.cell_big_label { + -fx-font-family: "Courier New"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Courier New"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.stack-pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.pane-with-border { + -fx-background-color: derive(white, 20%); + -fx-border-color: derive(white, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Courier New"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Courier New"; + -fx-text-fill: white; + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 30%); + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.context-menu { + -fx-background-color: derive(#1d1d1d, 50%); +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: derive(#004e88, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Courier New"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Courier New", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #d64b8a; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#1d1d1d, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar { + -fx-background-color: derive(#cacbcf, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#fe7b0f, 25%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: white white white white; + -fx-background-insets: 0; + -fx-border-color: #fe7b0f #fe7b0f #fe7b0f #fe7b0f; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Courier New"; + -fx-font-size: 13pt; + -fx-text-fill: #383838; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #004e88, transparent, #fe7b0f; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: #d64b8a; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..88466e06950 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -6,19 +6,21 @@ + + - + - + - + - + + + + diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json index 6a4d2b7181c..c634631729c 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json @@ -3,11 +3,15 @@ "name": "Valid Person", "phone": "9482424", "email": "hans@example.com", + "faculty": "Computing", + "role": "Professor", "address": "4th street" }, { "name": "Person With Invalid Phone Field", "phone": "948asdf2424", "email": "hans@example.com", + "faculty": "Computing", + "role": "Professor", "address": "4th street" } ] } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json index ccd21f7d1a9..c28ef83a98b 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json @@ -3,6 +3,8 @@ "name": "Person with invalid name field: Ha!ns Mu@ster", "phone": "9482424", "email": "hans@example.com", + "faculty": "Computing", + "role": "Professor", "address": "4th street" } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json index 48831cc7674..7361797b96a 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json @@ -3,12 +3,16 @@ "name": "Alice Pauline", "phone": "94351253", "email": "alice@example.com", + "faculty": "Computing", + "role": "Professor", "address": "123, Jurong West Ave 6, #08-111", "tagged": [ "friends" ] }, { "name": "Alice Pauline", "phone": "94351253", "email": "pauline@example.com", + "faculty": "Computing", + "role": "Professor", "address": "4th street" } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json index ad3f135ae42..9365308fe3b 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json @@ -3,6 +3,8 @@ "name": "Hans Muster", "phone": "9482424", "email": "invalid@email!3e", + "faculty": "gsdfjkgn", + "role": "asdfghjkl", "address": "4th street" } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json index f10eddee12e..f6931547b9e 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json @@ -4,43 +4,71 @@ "name" : "Alice Pauline", "phone" : "94351253", "email" : "alice@example.com", + "faculty" : "Business", + "role" : "Professor", + "telegram": "@AliceInBorderland9393", "address" : "123, Jurong West Ave 6, #08-111", + "favourite" : false, "tagged" : [ "friends" ] }, { "name" : "Benson Meier", "phone" : "98765432", "email" : "johnd@example.com", + "faculty" : "Computing", + "role" : "TA", + "telegram": "@bendsons332", "address" : "311, Clementi Ave 2, #02-25", + "favourite" : true, "tagged" : [ "owesMoney", "friends" ] }, { "name" : "Carl Kurz", "phone" : "95352563", "email" : "heinz@example.com", + "faculty" : "Dentistry", + "role" : "Professor", + "telegram": "@ketchupMaster59", "address" : "wall street", + "favourite" : false, "tagged" : [ ] }, { "name" : "Daniel Meier", "phone" : "87652533", "email" : "cornelia@example.com", + "faculty" : "Law", + "role" : "TA", + "telegram": "@theDamnDaniel391", "address" : "10th street", + "favourite" : false, "tagged" : [ "friends" ] }, { "name" : "Elle Meyer", "phone" : "9482224", "email" : "werner@example.com", + "faculty" : "Nursing", + "role" : "Professor", + "telegram": null, "address" : "michegan ave", + "favourite" : false, "tagged" : [ ] }, { "name" : "Fiona Kunz", "phone" : "9482427", "email" : "lydia@example.com", + "faculty" : "Music", + "role" : "TA", + "telegram": "@100ladybugs", "address" : "little tokyo", + "favourite" : false, "tagged" : [ ] }, { "name" : "George Best", "phone" : "9482442", "email" : "anna@example.com", + "faculty" : "Pharmacy", + "role" : "Professor", + "telegram": null, "address" : "4th street", + "favourite" : false, "tagged" : [ ] } ] } diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..18c677c4704 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -3,10 +3,12 @@ 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.FACULTY_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.ROLE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_AMY; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.AMY; @@ -80,7 +82,7 @@ public void execute_storageThrowsIoException_throwsCommandException() { // Execute add command String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY - + ADDRESS_DESC_AMY; + + FACULTY_DESC_AMY + ROLE_DESC_AMY + TELEGRAM_DESC_AMY; Person expectedPerson = new PersonBuilder(AMY).withTags().build(); ModelManager expectedModel = new ModelManager(); expectedModel.addPerson(expectedPerson); diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 5865713d5dd..4985c2f7c95 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -4,7 +4,9 @@ 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.assertCommandUnExecuteSuccess; import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.nio.file.Path; import java.util.ArrayList; @@ -18,8 +20,10 @@ 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.ReadOnlyUserPrefs; +import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; import seedu.address.testutil.PersonBuilder; @@ -34,13 +38,41 @@ public void constructor_nullPerson_throwsNullPointerException() { public void execute_personAcceptedByModel_addSuccessful() throws Exception { ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); Person validPerson = new PersonBuilder().build(); - CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); } + @Test + public void execute_duplicateEmail_throwsCommandException() { + PersonBuilder validPersonBuilder = new PersonBuilder(); + PersonBuilder personBuilderWithDiffName = new PersonBuilder().withName("somethingElse"); + Person validPerson = validPersonBuilder.build(); + Person diffName = personBuilderWithDiffName.build(); + + AddCommand addCommand = new AddCommand(validPerson); + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + modelStub.addPerson(diffName); + + assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_EMAIL, () -> addCommand.execute(modelStub)); + } + + @Test + public void execute_duplicatePhone_throwsCommandException() { + PersonBuilder validPersonBuilder = new PersonBuilder(); + PersonBuilder personBuilderWithDiffName = new PersonBuilder().withName("somethingElse") + .withEmail("se@gmail.com"); + Person validPerson = validPersonBuilder.build(); + Person diffName = personBuilderWithDiffName.build(); + + AddCommand addCommand = new AddCommand(validPerson); + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + modelStub.addPerson(diffName); + + assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PHONE, () -> addCommand.execute(modelStub)); + } + @Test public void execute_duplicatePerson_throwsCommandException() { Person validPerson = new PersonBuilder().build(); @@ -50,6 +82,15 @@ public void execute_duplicatePerson_throwsCommandException() { assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); } + @Test + public void unExecute_personUndid_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Person bob = new PersonBuilder().withName("Bob").build(); + + assertCommandUnExecuteSuccess(new AddCommand(bob), model, expectedModel); + } + @Test public void equals() { Person alice = new PersonBuilder().withName("Alice").build(); @@ -78,6 +119,11 @@ public void equals() { * A default model stub that have all of the methods failing. */ private class ModelStub implements Model { + @Override + public AddressBook makeCopy() { + return new AddressBook(); + } + @Override public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { throw new AssertionError("This method should not be called."); @@ -128,6 +174,21 @@ public boolean hasPerson(Person person) { throw new AssertionError("This method should not be called."); } + @Override + public boolean hasFavouritePerson(Person person) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasEmail(Person person) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPhone(Person person) { + throw new AssertionError("This method should not be called."); + } + @Override public void deletePerson(Person target) { throw new AssertionError("This method should not be called."); @@ -143,6 +204,11 @@ public ObservableList getFilteredPersonList() { throw new AssertionError("This method should not be called."); } + @Override + public Predicate getModelPredicate() { + throw new AssertionError("This method should not be called."); + } + @Override public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); @@ -179,6 +245,19 @@ public boolean hasPerson(Person person) { return personsAdded.stream().anyMatch(person::isSamePerson); } + @Override + public boolean hasEmail(Person person) { + requireNonNull(person); + return personsAdded.stream().anyMatch(person::isSameEmail); + } + + @Override + public boolean hasPhone(Person person) { + requireNonNull(person); + return personsAdded.stream().anyMatch(person::isSamePhone); + } + + @Override public void addPerson(Person person) { requireNonNull(person); diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java index 80d9110c03a..4627f25ad5c 100644 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.jupiter.api.Test; @@ -11,6 +12,13 @@ import seedu.address.model.UserPrefs; public class ClearCommandTest { + @Test + public void unExecute_addressBook_success() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + assertCommandUnExecuteSuccess(new ClearCommand(), model, expectedModel); + } @Test public void execute_emptyAddressBook_success() { diff --git a/src/test/java/seedu/address/logic/commands/CommandManagerTest.java b/src/test/java/seedu/address/logic/commands/CommandManagerTest.java new file mode 100644 index 00000000000..369e47c5b29 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CommandManagerTest.java @@ -0,0 +1,114 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Stack; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.ModelManager; +import seedu.address.model.person.Person; +import seedu.address.testutil.PersonBuilder; + + +class CommandManagerTest { + private final ModelManager modelManager = new ModelManager(); + private final CommandManager commandManager = new CommandManager(modelManager); + + @Test + public void constructor() { + assertEquals(-1, commandManager.getCommandStackPointer()); + assertEquals(new Stack(), commandManager.getCommandStack()); + assertEquals(new ModelManager(), commandManager.getModel()); + } + + //todo: write better test cases + @Test + void insertCommand() throws CommandException { + //insert all sorts of command + //check if stack is the same + Stack checkStack = new Stack<>(); + ModelManager modelManager = new ModelManager(); + CommandManager commandManager = new CommandManager(modelManager); + + //insert addCommand + Person bob = new PersonBuilder().withName("Bob").build(); + commandManager.insertCommand(new AddCommand(bob)); + checkStack.push(new AddCommand(bob)); + assertEquals(checkStack, commandManager.getCommandStack()); + assertEquals(0, commandManager.getCommandStackPointer()); + } + + @Test + void insertCommand_undo() throws CommandException { + //insert undo command + //check if stack is the same + Stack checkStack = new Stack<>(); + ModelManager modelManager = new ModelManager(); + CommandManager commandManager = new CommandManager(modelManager); + + //insert undo Command + commandManager.insertCommand(new UndoCommand()); + assertEquals(checkStack, commandManager.getCommandStack()); + assertEquals(-1, commandManager.getCommandStackPointer()); + } + + @Test + void insertCommand_redo() throws CommandException { + //insert undo command + //check if stack is the same + Stack checkStack = new Stack<>(); + ModelManager modelManager = new ModelManager(); + CommandManager commandManager = new CommandManager(modelManager); + + //insert undo Command + commandManager.insertCommand(new RedoCommand()); + assertEquals(checkStack, commandManager.getCommandStack()); + assertEquals(-1, commandManager.getCommandStackPointer()); + } + + //todo: this seems odd + @Test + void refreshFutureCommands() throws CommandException { + ModelManager modelManager = new ModelManager(); + CommandManager commandManager = new CommandManager(modelManager); + + Person bob = new PersonBuilder().withName("Bob").build(); + commandManager.insertCommand(new AddCommand(bob)); + + commandManager.insertCommand(new HelpCommand()); + + commandManager.refreshFutureCommands(0); + + assertEquals(1, commandManager.getCommandStackPointer()); + } + + //todo: do this test + @Test + void undo() throws CommandException { + ModelManager modelManager = new ModelManager(); + CommandManager commandManager = new CommandManager(modelManager); + + //insert addCommand + Person bob = new PersonBuilder().withName("Bob").build(); + AddCommand command = new AddCommand(bob); + commandManager.insertCommand(command); + + commandManager.undo(); + } + + @Test + void redo() throws CommandException { + ModelManager modelManager = new ModelManager(); + CommandManager commandManager = new CommandManager(modelManager); + + //insert addCommand + Person bob = new PersonBuilder().withName("Bob").build(); + AddCommand command = new AddCommand(bob); + commandManager.insertCommand(command); + + commandManager.undo(); + commandManager.redo(); + } +} diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..f2459abf804 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -2,11 +2,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FACULTY; 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_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; import static seedu.address.testutil.Assert.assertThrows; import java.util.ArrayList; @@ -17,7 +19,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; @@ -32,8 +34,14 @@ 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_FACULTY_AMY = "Business"; + public static final String VALID_FACULTY_BOB = "Computing"; + public static final String VALID_ROLE_AMY = "Professor"; + public static final String VALID_ROLE_BOB = "TA"; + public static final String VALID_TELEGRAM_AMY = "@Amy23"; + public static final String VALID_TELEGRAM_BOB = "@bobTheBuilder"; + public static final boolean VALID_FAVOURITE_AMY = true; + public static final boolean VALID_FAVOURITE_BOB = false; public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; @@ -43,16 +51,22 @@ 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 FACULTY_DESC_AMY = " " + PREFIX_FACULTY + VALID_FACULTY_AMY; + public static final String FACULTY_DESC_BOB = " " + PREFIX_FACULTY + VALID_FACULTY_BOB; + public static final String ROLE_DESC_AMY = " " + PREFIX_ROLE + VALID_ROLE_AMY; + public static final String ROLE_DESC_BOB = " " + PREFIX_ROLE + VALID_ROLE_BOB; + public static final String TELEGRAM_DESC_AMY = " " + PREFIX_TELEGRAM + VALID_TELEGRAM_AMY; + public static final String TELEGRAM_DESC_BOB = " " + PREFIX_TELEGRAM + VALID_TELEGRAM_BOB; public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol - public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses - public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String INVALID_FACULTY_DESC = " " + PREFIX_FACULTY; // empty string not allowed for addresses + public static final String INVALID_ROLE_DESC = " " + PREFIX_ROLE; // empty string not allowed for addresses + public static final String INVALID_TELEGRAM_DESC = " " + PREFIX_TELEGRAM; // empty string not allowed for addresses + public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hu bby*"; // '*' not allowed in tags public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; @@ -62,10 +76,14 @@ public class CommandTestUtil { 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) + .withFaculty(VALID_FACULTY_AMY).withRole(VALID_ROLE_AMY) + .withTelegram(VALID_TELEGRAM_AMY).withFavourite(VALID_FAVOURITE_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) + .withFaculty(VALID_FACULTY_BOB).withRole(VALID_ROLE_BOB) + .withTelegram(VALID_TELEGRAM_BOB).withFavourite(VALID_FAVOURITE_BOB) .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); } @@ -83,6 +101,7 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm } catch (CommandException ce) { throw new AssertionError("Execution of command should not fail.", ce); } + } /** @@ -95,6 +114,23 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); } + /** + * Stuff + * @param command command + * @param actualModel am + * @param expectedModel em + */ + public static void assertCommandUnExecuteSuccess(Command command, Model actualModel, Model expectedModel) { + try { + command.execute(actualModel); + command.unExecute(actualModel); + + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + /** * Executes the given {@code command}, confirms that
* - a {@code CommandException} is thrown
@@ -120,8 +156,7 @@ public static void showPersonAtIndex(Model model, Index targetIndex) { Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); - + model.updateFilteredPersonList(new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList(splitName[0]))); assertEquals(1, model.getFilteredPersonList().size()); } diff --git a/src/test/java/seedu/address/logic/commands/CopyEmailCommandTest.java b/src/test/java/seedu/address/logic/commands/CopyEmailCommandTest.java new file mode 100644 index 00000000000..916463844dc --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CopyEmailCommandTest.java @@ -0,0 +1,133 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.awt.HeadlessException; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code CopyEmailCommand}. + */ +public class CopyEmailCommandTest { + + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + try { + Person personToCopy = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + CopyEmailCommand copyEmailCommand = new CopyEmailCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(CopyEmailCommand.MESSAGE_COPY_PERSON_SUCCESS, + personToCopy.getEmail()); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + assertCommandSuccess(copyEmailCommand, model, expectedMessage, expectedModel); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + try { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + CopyEmailCommand copyEmailCommand = new CopyEmailCommand(outOfBoundIndex); + + assertCommandFailure(copyEmailCommand, model, + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + } + + @Test + public void execute_validIndexFilteredList_success() { + try { + Person personToCopy = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + CopyEmailCommand copyEmailCommand = new CopyEmailCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(CopyEmailCommand.MESSAGE_COPY_PERSON_SUCCESS, + personToCopy.getEmail()); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + assertCommandSuccess(copyEmailCommand, model, expectedMessage, expectedModel); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + try { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + + CopyEmailCommand copyEmailCommand = new CopyEmailCommand(outOfBoundIndex); + + assertCommandFailure(copyEmailCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + } + + @Test + public void unExecute_copy_successful() { + try { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + CopyEmailCommand copyFirstCommand = new CopyEmailCommand(INDEX_FIRST_PERSON); + + assertCommandUnExecuteSuccess(copyFirstCommand, model, expectedModel); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + + } + + @Test + public void equals() { + CopyEmailCommand copyFirstCommand = new CopyEmailCommand(INDEX_FIRST_PERSON); + CopyEmailCommand copySecondCommand = new CopyEmailCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(copyFirstCommand.equals(copyFirstCommand)); + + // same values -> returns true + CopyEmailCommand copyFirstCommandCopy = new CopyEmailCommand(INDEX_FIRST_PERSON); + assertTrue(copyFirstCommand.equals(copyFirstCommandCopy)); + + // different types -> returns false + assertFalse(copyFirstCommand.equals(1)); + + // null -> returns false + assertFalse(copyFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(copyFirstCommand.equals(copySecondCommand)); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/CopyPhoneCommandTest.java b/src/test/java/seedu/address/logic/commands/CopyPhoneCommandTest.java new file mode 100644 index 00000000000..fbbe1423470 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CopyPhoneCommandTest.java @@ -0,0 +1,132 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.awt.HeadlessException; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code CopyPhoneCommand}. + */ +public class CopyPhoneCommandTest { + + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + try { + Person personToCopy = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + CopyPhoneCommand copyPhoneCommand = new CopyPhoneCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(CopyPhoneCommand.MESSAGE_COPY_PERSON_SUCCESS, + personToCopy.getPhone()); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + assertCommandSuccess(copyPhoneCommand, model, expectedMessage, expectedModel); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + try { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + CopyPhoneCommand copyPhoneCommand = new CopyPhoneCommand(outOfBoundIndex); + + assertCommandFailure(copyPhoneCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + } + + @Test + public void execute_validIndexFilteredList_success() { + try { + Person personToCopy = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + CopyPhoneCommand copyPhoneCommand = new CopyPhoneCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(CopyPhoneCommand.MESSAGE_COPY_PERSON_SUCCESS, + personToCopy.getPhone()); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + assertCommandSuccess(copyPhoneCommand, model, expectedMessage, expectedModel); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + try { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + + CopyPhoneCommand copyPhoneCommand = new CopyPhoneCommand(outOfBoundIndex); + + assertCommandFailure(copyPhoneCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + } + + @Test + public void unExecute_copy_successful() { + try { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + CopyPhoneCommand copyFirstCommand = new CopyPhoneCommand(INDEX_FIRST_PERSON); + + assertCommandUnExecuteSuccess(copyFirstCommand, model, expectedModel); + } catch (HeadlessException error) { + assertTrue(error instanceof HeadlessException); + } + + } + + @Test + public void equals() { + CopyPhoneCommand copyFirstCommand = new CopyPhoneCommand(INDEX_FIRST_PERSON); + CopyPhoneCommand copySecondCommand = new CopyPhoneCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(copyFirstCommand.equals(copyFirstCommand)); + + // same values -> returns true + CopyPhoneCommand copyFirstCommandCopy = new CopyPhoneCommand(INDEX_FIRST_PERSON); + assertTrue(copyFirstCommand.equals(copyFirstCommandCopy)); + + // different types -> returns false + assertFalse(copyFirstCommand.equals(1)); + + // null -> returns false + assertFalse(copyFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(copyFirstCommand.equals(copySecondCommand)); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 45a8c910ba1..9e985e671b3 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; 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; @@ -24,7 +25,7 @@ */ public class DeleteCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); @Test public void execute_validIndexUnfilteredList_success() { @@ -76,6 +77,15 @@ public void execute_invalidIndexFilteredList_throwsCommandException() { assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } + @Test + public void unExecute_delete_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); + + assertCommandUnExecuteSuccess(deleteFirstCommand, model, expectedModel); + } + @Test public void equals() { DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index 214c6c2507b..13c6ca2f1c5 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -9,6 +9,7 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; 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; @@ -48,6 +49,17 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() { assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); } + @Test + public void unExecute_edit_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Person editedPerson = new PersonBuilder().build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); + EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); + + assertCommandUnExecuteSuccess(editCommand, model, expectedModel); + } + @Test public void execute_someFieldsSpecifiedUnfilteredList_success() { Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java index e0288792e72..0e92f70b5ed 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java @@ -4,11 +4,13 @@ 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_FACULTY_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_ROLE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_BOB; import org.junit.jupiter.api.Test; @@ -47,8 +49,16 @@ public void equals() { editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); + // different faculty -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withFaculty(VALID_FACULTY_BOB).build(); + assertFalse(DESC_AMY.equals(editedAmy)); + + // different role -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withRole(VALID_ROLE_BOB).build(); + assertFalse(DESC_AMY.equals(editedAmy)); + // different address -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTelegram(VALID_TELEGRAM_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); // different tags -> returns false diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java index 9533c473875..84cf846a9f9 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java @@ -1,20 +1,34 @@ package seedu.address.logic.commands; +import static org.junit.jupiter.api.Assertions.assertThrows; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; 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.UserPrefs; public class ExitCommandTest { - private Model model = new ModelManager(); - private Model expectedModel = new ModelManager(); + private final Model model = new ModelManager(); + private final Model expectedModel = new ModelManager(); @Test public void execute_exit_success() { CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel); } + + @Test + public void unExecute_exit_successful() throws CommandException { + ExitCommand command = new ExitCommand(); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + assertThrows(CommandException.class, () -> { + command.unExecute(model); + }); + } } diff --git a/src/test/java/seedu/address/logic/commands/FavouriteCommandTest.java b/src/test/java/seedu/address/logic/commands/FavouriteCommandTest.java new file mode 100644 index 00000000000..f6d5898f4c0 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FavouriteCommandTest.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.testutil.PersonBuilder; + +public class FavouriteCommandTest { + + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + + Person alice = new PersonBuilder().withName("Alice Pauline") + .withTelegram("@AliceInBorderland9393").withEmail("alice@example.com") + .withFaculty("Business").withRole("Professor").withFavourite(true).withPhone("94351253") + .withTags("friends").build(); + + + FavouriteCommand favouriteCommand = new FavouriteCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(FavouriteCommand.MESSAGE_FAVOURITE_PERSON_SUCCESS, alice); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.setPerson(model.getFilteredPersonList().get(0), alice); + + assertCommandSuccess(favouriteCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_personAlreadyFavourite_failure() { + Person person = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + + assertTrue(person.getFavourite().isFavourite == true); + + FavouriteCommand favouriteCommand = new FavouriteCommand(INDEX_SECOND_PERSON); + assertCommandFailure(favouriteCommand, model, favouriteCommand.MESSAGE_DUPLICATE_PERSON); + } + + @Test + public void unExecute_favourite_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + FavouriteCommand command = new FavouriteCommand(INDEX_FIRST_PERSON); + + assertCommandUnExecuteSuccess(command, model, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 9b15db28bbb..7c34df32c25 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -5,9 +5,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BENSON; import static seedu.address.testutil.TypicalPersons.CARL; import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; +import static seedu.address.testutil.TypicalPersons.GEORGE; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.Arrays; @@ -18,21 +21,21 @@ import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAllKeywordsPredicate; /** * 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 final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); @Test public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); + NameFacultyRoleContainsAllKeywordsPredicate firstPredicate = + new NameFacultyRoleContainsAllKeywordsPredicate(Collections.singletonList("first")); + NameFacultyRoleContainsAllKeywordsPredicate secondPredicate = + new NameFacultyRoleContainsAllKeywordsPredicate(Collections.singletonList("second")); FindCommand findFirstCommand = new FindCommand(firstPredicate); FindCommand findSecondCommand = new FindCommand(secondPredicate); @@ -55,29 +58,59 @@ public void equals() { } @Test - public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); + public void execute_oneKeyword_onePersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + NameFacultyRoleContainsAllKeywordsPredicate predicate = preparePredicateAll("Benson"); FindCommand command = new FindCommand(predicate); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredPersonList()); + assertEquals(Arrays.asList(BENSON), model.getFilteredPersonList()); } @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); + public void execute_oneKeyword_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 4); + NameFacultyRoleContainsAllKeywordsPredicate predicate = preparePredicateAll("Professor"); FindCommand command = new FindCommand(predicate); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); + assertEquals(Arrays.asList(ALICE, CARL, ELLE, GEORGE), model.getFilteredPersonList()); + } + + @Test + public void execute_multipleKeywords_onePersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + NameFacultyRoleContainsAllKeywordsPredicate predicate = preparePredicateAll("TA Benson"); + FindCommand command = new FindCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(BENSON), model.getFilteredPersonList()); + } + + @Test + public void execute_multipleKeywords_zeroPersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + NameFacultyRoleContainsAllKeywordsPredicate predicate = preparePredicateAll("Business Benson "); + FindCommand command = new FindCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredPersonList()); + } + + @Test + public void unExecute_find_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + NameFacultyRoleContainsAllKeywordsPredicate predicate = preparePredicateAll("TA"); + FindCommand command = new FindCommand(predicate); + + assertCommandUnExecuteSuccess(command, model, expectedModel); } /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + * Parses {@code userInput} into a {@code NameContainsKeywordsAllPredicate}. */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + private NameFacultyRoleContainsAllKeywordsPredicate preparePredicateAll(String userInput) { + return new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); } } diff --git a/src/test/java/seedu/address/logic/commands/FindWideCommandTest.java b/src/test/java/seedu/address/logic/commands/FindWideCommandTest.java new file mode 100644 index 00000000000..a7eda4f8cf8 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindWideCommandTest.java @@ -0,0 +1,95 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.CARL; +import static seedu.address.testutil.TypicalPersons.DANIEL; +import static seedu.address.testutil.TypicalPersons.FIONA; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindCommand}. + */ +public class FindWideCommandTest { + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + NameFacultyRoleContainsAnyKeywordsPredicate firstPredicate = + new NameFacultyRoleContainsAnyKeywordsPredicate(Collections.singletonList("first")); + NameFacultyRoleContainsAnyKeywordsPredicate secondPredicate = + new NameFacultyRoleContainsAnyKeywordsPredicate(Collections.singletonList("second")); + + FindWideCommand findFirstCommand = new FindWideCommand(firstPredicate); + FindWideCommand findSecondCommand = new FindWideCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindWideCommand findFirstCommandCopy = new FindWideCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_zeroKeywords_noPersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + NameFacultyRoleContainsAnyKeywordsPredicate predicate = preparePredicate(" "); + FindWideCommand command = new FindWideCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredPersonList()); + } + + @Test + public void execute_multipleKeywords_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 4); + NameFacultyRoleContainsAnyKeywordsPredicate predicate = preparePredicate("TA Kurz"); + FindWideCommand command = new FindWideCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(BENSON, CARL, DANIEL, FIONA), model.getFilteredPersonList()); + } + + @Test + public void unExecute_find_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + NameFacultyRoleContainsAnyKeywordsPredicate predicate = preparePredicate("TA Kurz"); + FindWideCommand command = new FindWideCommand(predicate); + + assertCommandUnExecuteSuccess(command, model, expectedModel); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsAnyPredicate}. + */ + private NameFacultyRoleContainsAnyKeywordsPredicate preparePredicate(String userInput) { + return new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 4904fc4352e..5f88f98e522 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -1,20 +1,33 @@ package seedu.address.logic.commands; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; 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.UserPrefs; public class HelpCommandTest { - private Model model = new ModelManager(); - private Model expectedModel = new ModelManager(); + private final Model model = new ModelManager(); + private final Model expectedModel = new ModelManager(); @Test public void execute_help_success() { CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false); assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel); } + + @Test + public void unExecute_help_successful() throws CommandException { + HelpCommand command = new HelpCommand(); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + assertCommandUnExecuteSuccess(command, model, expectedModel); + } } diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java index 435ff1f7275..0dff198441e 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; @@ -36,4 +37,12 @@ public void execute_listIsFiltered_showsEverything() { showPersonAtIndex(model, INDEX_FIRST_PERSON); assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); } + + @Test + public void unExecute_list_successful() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(expectedModel, INDEX_FIRST_PERSON); + + assertCommandUnExecuteSuccess(new ListCommand(), model, expectedModel); + } } diff --git a/src/test/java/seedu/address/logic/commands/ListFavouritesCommandTest.java b/src/test/java/seedu/address/logic/commands/ListFavouritesCommandTest.java new file mode 100644 index 00000000000..e9c456ad08a --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListFavouritesCommandTest.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.model.Model.PREDICATE_SHOW_FAVOURITES; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListCommand. + */ +public class ListFavouritesCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_FAVOURITES); + assertCommandSuccess(new ListFavouritesCommand(), model, ListFavouritesCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void unExecute_find_successful() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(expectedModel, INDEX_FIRST_PERSON); + + assertCommandUnExecuteSuccess(new ListFavouritesCommand(), model, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java new file mode 100644 index 00000000000..fb7273b619f --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +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.UserPrefs; + +class RedoCommandTest { + + @Test + void execute() throws CommandException { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + CommandResult result = (new RedoCommand()).execute(model); + + assertEquals(result.getFeedbackToUser(), "Redid command"); + } + + @Test + void unExecute() throws CommandException { + RedoCommand command = new RedoCommand(); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + assertThrows(CommandException.class, () -> { + command.unExecute(model); + }); + } + + @Test + void isRedo() { + boolean isRedo = (new RedoCommand()).isRedo(); + + assertTrue(isRedo); + } +} diff --git a/src/test/java/seedu/address/logic/commands/TagCommandTest.java b/src/test/java/seedu/address/logic/commands/TagCommandTest.java new file mode 100644 index 00000000000..6a0a3f44c33 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/TagCommandTest.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.DANIEL; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.TagContainsKeywordsPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code TagCommand}. + */ +public class TagCommandTest { + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + TagContainsKeywordsPredicate firstPredicate = + new TagContainsKeywordsPredicate(Collections.singletonList("first")); + TagContainsKeywordsPredicate secondPredicate = + new TagContainsKeywordsPredicate(Collections.singletonList("second")); + + TagCommand findFirstCommand = new TagCommand(firstPredicate); + TagCommand findSecondCommand = new TagCommand(secondPredicate); + + // same object -> returns true + assertEquals(findFirstCommand, findFirstCommand); + + // same values -> returns true + TagCommand findFirstCommandCopy = new TagCommand(firstPredicate); + assertEquals(findFirstCommandCopy, findFirstCommand); + + // different types -> returns false + assertNotEquals(findFirstCommand, 1); + + // null -> returns false + assertNotEquals(findFirstCommand, null); + + // different person -> returns false + assertNotEquals(findSecondCommand, findFirstCommand); + } + + @Test + public void execute_zeroKeywords_noPersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + TagContainsKeywordsPredicate predicate = preparePredicate(" "); + TagCommand command = new TagCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredPersonList()); + } + + @Test + public void execute_multipleKeywords_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + TagContainsKeywordsPredicate predicate = preparePredicate("friends colleagues"); + TagCommand command = new TagCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON, DANIEL), model.getFilteredPersonList()); + } + + @Test + public void unExecute_tag_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + TagContainsKeywordsPredicate predicate = preparePredicate("friends colleagues"); + TagCommand command = new TagCommand(predicate); + //todo: figure out why this test works... + assertCommandUnExecuteSuccess(command, model, expectedModel); + } + + /** + * Parses {@code userInput} into a {@code TagContainsKeywordsPredicate}. + */ + private TagContainsKeywordsPredicate preparePredicate(String userInput) { + return new TagContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java new file mode 100644 index 00000000000..93ba3403333 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +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.UserPrefs; + +class UndoCommandTest { + + @Test + void execute() throws CommandException { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + CommandResult result = (new UndoCommand()).execute(model); + + assertEquals(result.getFeedbackToUser(), "Undid command"); + } + + @Test + void unExecute() throws CommandException { + UndoCommand command = new UndoCommand(); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + assertThrows(CommandException.class, () -> { + command.unExecute(model); + }); + } + + @Test + void isRedo() { + boolean isUndo = (new UndoCommand()).isUndo(); + + assertTrue(isUndo); + } +} diff --git a/src/test/java/seedu/address/logic/commands/UnfavouriteCommandTest.java b/src/test/java/seedu/address/logic/commands/UnfavouriteCommandTest.java new file mode 100644 index 00000000000..6765072058c --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/UnfavouriteCommandTest.java @@ -0,0 +1,63 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandUnExecuteSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.testutil.PersonBuilder; + +public class UnfavouriteCommandTest { + + private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + Person benson = new PersonBuilder().withName("Benson Meier") + .withEmail("johnd@example.com").withTelegram("@bendsons332") + .withPhone("98765432").withFaculty("Computing").withRole("TA") + .withFavourite(false).withTags("owesMoney", "friends").build(); + + UnfavouriteCommand unfavouriteCommand = + new UnfavouriteCommand(INDEX_SECOND_PERSON); + + String expectedMessage = + String.format(UnfavouriteCommand.MESSAGE_UNFAVOURITE_PERSON_SUCCESS, + benson); + + ModelManager expectedModel = + new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.setPerson(model.getFilteredPersonList().get(1), benson); + + assertCommandSuccess(unfavouriteCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_personAlreadyUnfavourite_failure() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + + assertTrue(person.getFavourite().isFavourite == false); + + UnfavouriteCommand unfavouriteCommand = new UnfavouriteCommand(INDEX_FIRST_PERSON); + assertCommandFailure(unfavouriteCommand, model, UnfavouriteCommand.MESSAGE_DUPLICATE_PERSON); + } + + @Test + public void unExecute_unfavourite_successful() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + UnfavouriteCommand command = + new UnfavouriteCommand(INDEX_SECOND_PERSON); + + assertCommandUnExecuteSuccess(command, model, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index 5cf487d7ebb..70f1d39ce57 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -1,29 +1,37 @@ 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.FACULTY_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.FACULTY_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FACULTY_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_ROLE_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TELEGRAM_DESC; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.ROLE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.ROLE_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_BOB; +import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_FACULTY_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_ROLE_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_TELEGRAM_BOB; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; import static seedu.address.testutil.TypicalPersons.AMY; @@ -32,16 +40,18 @@ 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.Faculty; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; import seedu.address.testutil.PersonBuilder; public class AddCommandParserTest { - private AddCommandParser parser = new AddCommandParser(); + private final AddCommandParser parser = new AddCommandParser(); @Test public void parse_allFieldsPresent_success() { @@ -49,37 +59,53 @@ public void parse_allFieldsPresent_success() { // 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)); + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_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)); + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_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)); + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_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)); + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_DESC_BOB + + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); + + // multiple faculties - last faculty accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + FACULTY_DESC_AMY + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_DESC_BOB + + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); + + // multiple roles - last role accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + FACULTY_DESC_BOB + + ROLE_DESC_AMY + ROLE_DESC_BOB + TELEGRAM_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 + TELEGRAM_DESC_AMY + + TELEGRAM_DESC_BOB + FACULTY_DESC_BOB + ROLE_DESC_BOB + + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); // multiple tags - all accepted Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) .build(); - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + FACULTY_DESC_BOB + + ROLE_DESC_BOB + TELEGRAM_DESC_BOB + TAG_DESC_HUSBAND + + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); } @Test public void parse_optionalFieldsMissing_success() { // zero tags Person expectedPerson = new PersonBuilder(AMY).withTags().build(); - assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, - new AddCommand(expectedPerson)); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + FACULTY_DESC_AMY + + ROLE_DESC_AMY + TELEGRAM_DESC_AMY, new AddCommand(expectedPerson)); } @Test @@ -87,55 +113,80 @@ 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); + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + FACULTY_DESC_BOB + + ROLE_DESC_BOB + TELEGRAM_DESC_BOB, expectedMessage); // missing phone prefix - assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + FACULTY_DESC_BOB + + ROLE_DESC_BOB + TELEGRAM_DESC_BOB, expectedMessage); // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + FACULTY_DESC_BOB + + ROLE_DESC_BOB + TELEGRAM_DESC_BOB, expectedMessage); + + // missing faculty prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_FACULTY_BOB + + ROLE_DESC_BOB + TELEGRAM_DESC_BOB, expectedMessage); - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); + // missing role prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + FACULTY_DESC_BOB + + VALID_ROLE_BOB + TELEGRAM_DESC_BOB, expectedMessage); + + // missing telegram prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + FACULTY_DESC_BOB + + VALID_TELEGRAM_BOB, expectedMessage); // all prefixes missing - assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, - expectedMessage); + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_FACULTY_BOB + + ROLE_DESC_BOB + VALID_TELEGRAM_BOB, expectedMessage); } @Test public void parse_invalidValue_failure() { // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_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); + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_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); + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_DESC_BOB + TAG_DESC_HUSBAND + + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS); + + // invalid faculty + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + INVALID_FACULTY_DESC + ROLE_DESC_BOB + TELEGRAM_DESC_BOB + TAG_DESC_HUSBAND + + TAG_DESC_FRIEND, Faculty.MESSAGE_CONSTRAINTS); + + // invalid role + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + FACULTY_DESC_BOB + INVALID_ROLE_DESC + TELEGRAM_DESC_BOB + TAG_DESC_HUSBAND + + TAG_DESC_FRIEND, Role.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 TELEGRAM + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + FACULTY_DESC_BOB + ROLE_DESC_BOB + INVALID_TELEGRAM_DESC + TAG_DESC_HUSBAND + + TAG_DESC_FRIEND, Telegram.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); + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_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); + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + + FACULTY_DESC_BOB + ROLE_DESC_BOB + + INVALID_TELEGRAM_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, + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + + EMAIL_DESC_BOB + FACULTY_DESC_BOB + ROLE_DESC_BOB + TELEGRAM_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index d9659205b57..e6e34b30cdb 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -15,16 +15,24 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.CopyEmailCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FavouriteCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindWideCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListFavouritesCommand; +import seedu.address.logic.commands.TagCommand; +import seedu.address.logic.commands.UnfavouriteCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAllKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.model.person.TagContainsKeywordsPredicate; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; @@ -37,6 +45,8 @@ public class AddressBookParserTest { public void parseCommand_add() throws Exception { Person person = new PersonBuilder().build(); AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); + // assertTrue(person.isSamePerson(command.getToAdd())); + // the 2 persons are the same, but the commands are not equal what the fk assertEquals(new AddCommand(person), command); } @@ -46,6 +56,13 @@ public void parseCommand_clear() throws Exception { assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); } + @Test + public void parseCommand_copy() throws Exception { + CopyEmailCommand command = (CopyEmailCommand) parser.parseCommand( + CopyEmailCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + assertEquals(new CopyEmailCommand(INDEX_FIRST_PERSON), command); + } + @Test public void parseCommand_delete() throws Exception { DeleteCommand command = (DeleteCommand) parser.parseCommand( @@ -62,18 +79,48 @@ public void parseCommand_edit() throws Exception { assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); } + @Test + public void parseCommand_favourite() throws Exception { + FavouriteCommand command = (FavouriteCommand) parser.parseCommand( + FavouriteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + assertEquals(new FavouriteCommand(INDEX_FIRST_PERSON), command); + } + + @Test + public void parseCommand_unfavourite() throws Exception { + UnfavouriteCommand command = (UnfavouriteCommand) parser.parseCommand( + UnfavouriteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + assertEquals(new UnfavouriteCommand(INDEX_FIRST_PERSON), command); + } + @Test public void parseCommand_exit() throws Exception { assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); } + @Test + public void parseCommand_find_wide() throws Exception { + List keywords = Arrays.asList("foo", "bar", "baz"); + FindWideCommand command = (FindWideCommand) parser.parseCommand( + FindWideCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); + assertEquals(new FindWideCommand(new NameFacultyRoleContainsAnyKeywordsPredicate(keywords)), command); + } + @Test public void parseCommand_find() throws Exception { List keywords = Arrays.asList("foo", "bar", "baz"); FindCommand command = (FindCommand) parser.parseCommand( FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + assertEquals(new FindCommand(new NameFacultyRoleContainsAllKeywordsPredicate(keywords)), command); + } + + @Test + public void parseCommand_tag() throws Exception { + List keywords = Arrays.asList("foo", "bar", "baz"); + TagCommand command = (TagCommand) parser.parseCommand( + TagCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); + assertEquals(new TagCommand(new TagContainsKeywordsPredicate(keywords)), command); } @Test @@ -88,6 +135,12 @@ public void parseCommand_list() throws Exception { assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); } + @Test + public void parseCommand_list_favourites() throws Exception { + assertTrue(parser.parseCommand(ListFavouritesCommand.COMMAND_WORD) instanceof ListFavouritesCommand); + assertTrue(parser.parseCommand(ListFavouritesCommand.COMMAND_WORD + " 3") instanceof ListFavouritesCommand); + } + @Test public void parseCommand_unrecognisedInput_throwsParseException() { assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () diff --git a/src/test/java/seedu/address/logic/parser/CopyEmailCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CopyEmailCommandParserTest.java new file mode 100644 index 00000000000..d42ee090c05 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/CopyEmailCommandParserTest.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.CopyEmailCommand; + +public class CopyEmailCommandParserTest { + private CopyEmailCommandParser parser = new CopyEmailCommandParser(); + + @Test + public void parse_validArgs_returnsCopyCommand() { + assertParseSuccess(parser, "1", new CopyEmailCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CopyEmailCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/CopyPhoneCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CopyPhoneCommandParserTest.java new file mode 100644 index 00000000000..dd7e1d7cdec --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/CopyPhoneCommandParserTest.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.CopyPhoneCommand; + +public class CopyPhoneCommandParserTest { + private CopyPhoneCommandParser parser = new CopyPhoneCommandParser(); + + @Test + public void parse_validArgs_returnsCopyCommand() { + assertParseSuccess(parser, "1", new CopyPhoneCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CopyPhoneCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index 2ff31522486..617a5449be3 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -1,29 +1,39 @@ 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.FACULTY_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.FACULTY_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_FACULTY_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_ROLE_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TELEGRAM_DESC; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.ROLE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.ROLE_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.TELEGRAM_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.TELEGRAM_DESC_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_FACULTY_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_FACULTY_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ROLE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ROLE_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_TELEGRAM_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_BOB; 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; @@ -36,11 +46,13 @@ 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.Faculty; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; import seedu.address.testutil.EditPersonDescriptorBuilder; public class EditCommandParserTest { @@ -50,7 +62,7 @@ public class EditCommandParserTest { private static final String MESSAGE_INVALID_FORMAT = String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); - private EditCommandParser parser = new EditCommandParser(); + private final EditCommandParser parser = new EditCommandParser(); @Test public void parse_missingParts_failure() { @@ -84,7 +96,9 @@ 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_FACULTY_DESC, Faculty.MESSAGE_CONSTRAINTS); // invalid faculty + assertParseFailure(parser, "1" + INVALID_ROLE_DESC, Role.MESSAGE_CONSTRAINTS); // invalid role + assertParseFailure(parser, "1" + INVALID_TELEGRAM_DESC, Telegram.MESSAGE_CONSTRAINTS); // invalid address assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag // invalid phone followed by valid email @@ -101,18 +115,20 @@ public void parse_invalidValue_failure() { 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); + assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_FACULTY_AMY + + VALID_ROLE_AMY + VALID_TELEGRAM_AMY + VALID_PHONE_AMY, Name.MESSAGE_CONSTRAINTS); } @Test public void parse_allFieldsSpecified_success() { Index targetIndex = INDEX_SECOND_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND - + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; + String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + TAG_DESC_HUSBAND + + EMAIL_DESC_AMY + FACULTY_DESC_AMY + ROLE_DESC_AMY + TELEGRAM_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) + .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withFaculty(VALID_FACULTY_AMY) + .withRole(VALID_ROLE_AMY).withTelegram(VALID_TELEGRAM_AMY) .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -152,9 +168,21 @@ public void parse_oneFieldSpecified_success() { expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); + // faculty + userInput = targetIndex.getOneBased() + FACULTY_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withFaculty(VALID_FACULTY_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // role + userInput = targetIndex.getOneBased() + ROLE_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withRole(VALID_ROLE_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(); + userInput = targetIndex.getOneBased() + TELEGRAM_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withTelegram(VALID_TELEGRAM_AMY).build(); expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); @@ -168,13 +196,15 @@ public void parse_oneFieldSpecified_success() { @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; + String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + TELEGRAM_DESC_AMY + + EMAIL_DESC_AMY + FACULTY_DESC_AMY + ROLE_DESC_AMY + TAG_DESC_FRIEND + + PHONE_DESC_AMY + TELEGRAM_DESC_AMY + EMAIL_DESC_AMY + FACULTY_DESC_AMY + + ROLE_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_BOB + TELEGRAM_DESC_BOB + + EMAIL_DESC_BOB + FACULTY_DESC_BOB + ROLE_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(); + .withEmail(VALID_EMAIL_BOB).withFaculty(VALID_FACULTY_BOB).withRole(VALID_ROLE_BOB) + .withTelegram(VALID_TELEGRAM_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); @@ -190,10 +220,10 @@ public void parse_invalidValueFollowedByValidValue_success() { assertParseSuccess(parser, userInput, expectedCommand); // other valid values specified - userInput = targetIndex.getOneBased() + EMAIL_DESC_BOB + INVALID_PHONE_DESC + ADDRESS_DESC_BOB - + PHONE_DESC_BOB; + userInput = targetIndex.getOneBased() + EMAIL_DESC_BOB + FACULTY_DESC_BOB + ROLE_DESC_BOB + + INVALID_PHONE_DESC + PHONE_DESC_BOB; descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).build(); + .withFaculty(VALID_FACULTY_BOB).withRole(VALID_ROLE_BOB).build(); expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } diff --git a/src/test/java/seedu/address/logic/parser/FavouriteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FavouriteCommandParserTest.java new file mode 100644 index 00000000000..a4669b21628 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FavouriteCommandParserTest.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.FavouriteCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the FavouriteCommand code. For example, inputs "1" and "1 abc" take the + * same path through the FavouriteCommand, 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 FavouriteCommandParserTest { + + private FavouriteCommandParser parser = new FavouriteCommandParser(); + + @Test + public void parse_validArgs_returnsFavouriteCommand() { + assertParseSuccess(parser, "1", new FavouriteCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FavouriteCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java index 70f4f0e79c4..38208dc3164 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAllKeywordsPredicate; public class FindCommandParserTest { @@ -24,11 +24,11 @@ public void parse_emptyArg_throwsParseException() { public void parse_validArgs_returnsFindCommand() { // no leading and trailing whitespaces FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); + new FindCommand(new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList("Alice", "Business"))); + assertParseSuccess(parser, "Alice Business", expectedFindCommand); // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); + assertParseSuccess(parser, " \n Alice \n \t Business \t", expectedFindCommand); } } diff --git a/src/test/java/seedu/address/logic/parser/FindWideCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindWideCommandParserTest.java new file mode 100644 index 00000000000..c38ace9813b --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindWideCommandParserTest.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.FindWideCommand; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; + +public class FindWideCommandParserTest { + + private FindWideCommandParser parser = new FindWideCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FindWideCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + FindWideCommand expectedFindWideCommand = + new FindWideCommand(new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Alice", "Bob"))); + assertParseSuccess(parser, "Alice Bob", expectedFindWideCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindWideCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..562f95c5ae8 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -9,28 +9,36 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Optional; 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.Faculty; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; 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 Optional INVALID_TELEGRAM = Optional.of(" "); private static final String INVALID_EMAIL = "example.com"; + private static final String INVALID_FACULTY = " "; + private static final String INVALID_ROLE = " "; 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 Optional VALID_TELEGRAM = Optional.of("@walkRachel33"); private static final String VALID_EMAIL = "rachel@example.com"; + private static final String VALID_FACULTY = "Computing"; + private static final String VALID_ROLE = "Professor"; private static final String VALID_TAG_1 = "friend"; private static final String VALID_TAG_2 = "neighbour"; @@ -103,26 +111,26 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc } @Test - public void parseAddress_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null)); + public void parseTelegram_null_throwsNoSuchElementException() { + assertThrows(NoSuchElementException.class, () -> ParserUtil.parseTelegram((Optional.empty())).value.get()); } @Test public void parseAddress_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS)); + assertThrows(ParseException.class, () -> ParserUtil.parseTelegram(INVALID_TELEGRAM)); } @Test - public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception { - Address expectedAddress = new Address(VALID_ADDRESS); - assertEquals(expectedAddress, ParserUtil.parseAddress(VALID_ADDRESS)); + public void parseAddress_validValueWithoutWhitespace_returnsTelegram() throws Exception { + Telegram expectedAddress = new Telegram(VALID_TELEGRAM); + assertEquals(expectedAddress, ParserUtil.parseTelegram(VALID_TELEGRAM)); } @Test - public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws Exception { - String addressWithWhitespace = WHITESPACE + VALID_ADDRESS + WHITESPACE; - Address expectedAddress = new Address(VALID_ADDRESS); - assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace)); + public void parseAddress_validValueWithWhitespace_returnsTrimmedTelegram() throws Exception { + String telegramWithWhitespace = WHITESPACE + VALID_TELEGRAM.get() + WHITESPACE; + Telegram expectedTelegram = new Telegram(VALID_TELEGRAM); + assertEquals(expectedTelegram, ParserUtil.parseTelegram(Optional.of(telegramWithWhitespace))); } @Test @@ -148,6 +156,52 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace)); } + @Test + public void parseFaculty_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseFaculty((String) null)); + } + + @Test + public void parseFaculty_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseFaculty(INVALID_FACULTY)); + } + + @Test + public void parseFaculty_validValueWithoutWhitespace_returnsFaculty() throws Exception { + Faculty expectedFaculty = new Faculty(VALID_FACULTY); + assertEquals(expectedFaculty, ParserUtil.parseFaculty(VALID_FACULTY)); + } + + @Test + public void parseFaculty_validValueWithWhitespace_returnsTrimmedAddress() throws Exception { + String facultyWithWhitespace = WHITESPACE + VALID_FACULTY + WHITESPACE; + Faculty expectedFaculty = new Faculty(VALID_FACULTY); + assertEquals(expectedFaculty, ParserUtil.parseFaculty(facultyWithWhitespace)); + } + + @Test + public void parseRole_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseRole((String) null)); + } + + @Test + public void parseRole_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseRole(INVALID_ROLE)); + } + + @Test + public void parseRole_validValueWithoutWhitespace_returnsRole() throws Exception { + Role expectedRole = new Role(VALID_ROLE); + assertEquals(expectedRole, ParserUtil.parseRole(VALID_ROLE)); + } + + @Test + public void parseRole_validValueWithWhitespace_returnsTrimmedAddress() throws Exception { + String roleWithWhitespace = WHITESPACE + VALID_ROLE + WHITESPACE; + Role expectedRole = new Role(VALID_ROLE); + assertEquals(expectedRole, ParserUtil.parseRole(roleWithWhitespace)); + } + @Test public void parseTag_null_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null)); diff --git a/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java new file mode 100644 index 00000000000..78a12280b20 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.TagCommand; +import seedu.address.model.person.TagContainsKeywordsPredicate; + +public class TagCommandParserTest { + + private TagCommandParser parser = new TagCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsTagCommand() { + // no leading and trailing whitespaces + TagCommand expectedTagCommand = + new TagCommand(new TagContainsKeywordsPredicate(Arrays.asList("CS2103T", "CS2100"))); + assertParseSuccess(parser, "CS2103T CS2100", expectedTagCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n CS2103T \n \t CS2100 \t", expectedTagCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/UnfavouriteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnfavouriteCommandParserTest.java new file mode 100644 index 00000000000..e0eb1002ad5 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/UnfavouriteCommandParserTest.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.UnfavouriteCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the UnfavouriteCommand code. For example, inputs "1" and "1 abc" take the + * same path through the UnfavouriteCommand, 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 UnfavouriteCommandParserTest { + + private UnfavouriteCommandParser parser = new UnfavouriteCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "1", new UnfavouriteCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnfavouriteCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 87782528ecd..7ae14e18fe4 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -3,11 +3,9 @@ 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_TELEGRAM_BOB; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.Arrays; import java.util.Collection; @@ -21,6 +19,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.TypicalPersons; public class AddressBookTest { @@ -38,7 +37,7 @@ public void resetData_null_throwsNullPointerException() { @Test public void resetData_withValidReadOnlyAddressBook_replacesData() { - AddressBook newData = getTypicalAddressBook(); + AddressBook newData = TypicalPersons.getTypicalAddressBook(); addressBook.resetData(newData); assertEquals(newData, addressBook); } @@ -46,9 +45,10 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() { @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) + Person editedAlice = new PersonBuilder(TypicalPersons.ALICE) + .withTelegram(VALID_TELEGRAM_BOB).withTags(VALID_TAG_HUSBAND) .build(); - List newPersons = Arrays.asList(ALICE, editedAlice); + List newPersons = Arrays.asList(TypicalPersons.ALICE, editedAlice); AddressBookStub newData = new AddressBookStub(newPersons); assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); @@ -61,23 +61,64 @@ public void hasPerson_nullPerson_throwsNullPointerException() { @Test public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(addressBook.hasPerson(ALICE)); + assertFalse(addressBook.hasPerson(TypicalPersons.ALICE)); } @Test public void hasPerson_personInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - assertTrue(addressBook.hasPerson(ALICE)); + addressBook.addPerson(TypicalPersons.ALICE); + assertTrue(addressBook.hasPerson(TypicalPersons.ALICE)); } @Test public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + addressBook.addPerson(TypicalPersons.ALICE); + Person editedAlice = new PersonBuilder(TypicalPersons.ALICE) + .withTelegram(VALID_TELEGRAM_BOB).withTags(VALID_TAG_HUSBAND) .build(); assertTrue(addressBook.hasPerson(editedAlice)); } + @Test + public void hasFavouritePerson_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.hasFavouritePerson(null)); + } + + @Test + public void hasFavouritePerson_personNotInAddressBook_returnsFalse() { + assertFalse(addressBook.hasFavouritePerson(TypicalPersons.ALICE)); + } + + @Test + public void hasFavouritePerson_personInAddressBook_returnsTrue() { + addressBook.addPerson(TypicalPersons.ALICE); + assertTrue(addressBook.hasFavouritePerson(TypicalPersons.ALICE)); + } + + @Test + public void hasFavouritePerson_personWithDifferentFavouriteTrue_returnsFalse() { + addressBook.addPerson(TypicalPersons.ALICE); + Person editedAlice = new PersonBuilder(TypicalPersons.ALICE).withFavourite(true) + .build(); + assertFalse(addressBook.hasFavouritePerson(editedAlice)); + } + + @Test + public void hasFavouritePerson_personWithDifferentFavouriteFalse_returnsFalse() { + addressBook.addPerson(TypicalPersons.BENSON); + Person editedBenson = new PersonBuilder(TypicalPersons.BENSON).withFavourite(false) + .build(); + assertFalse(addressBook.hasFavouritePerson(editedBenson)); + } + + @Test + public void hasFavouritePerson_personWithSameFavourite_returnsTrue() { + addressBook.addPerson(TypicalPersons.ALICE); + Person editedAlice = new PersonBuilder(TypicalPersons.ALICE).withFavourite(false) + .build(); + assertTrue(addressBook.hasFavouritePerson(editedAlice)); + } + @Test public void getPersonList_modifyList_throwsUnsupportedOperationException() { assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..511676fd326 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -15,7 +15,7 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { @@ -88,6 +88,22 @@ public void hasPerson_personInAddressBook_returnsTrue() { assertTrue(modelManager.hasPerson(ALICE)); } + @Test + public void hasFavouritePerson_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasFavouritePerson(null)); + } + + @Test + public void hasFavouritePerson_personNotInAddressBook_returnsFalse() { + assertFalse(modelManager.hasFavouritePerson(ALICE)); + } + + @Test + public void hasFavouritePerson_personInAddressBook_returnsTrue() { + modelManager.addPerson(ALICE); + assertTrue(modelManager.hasFavouritePerson(ALICE)); + } + @Test public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); @@ -118,7 +134,7 @@ public void equals() { // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + modelManager.updateFilteredPersonList(new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList(keywords))); assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); // resets modelManager to initial state for upcoming tests diff --git a/src/test/java/seedu/address/model/ModelMementoTest.java b/src/test/java/seedu/address/model/ModelMementoTest.java new file mode 100644 index 00000000000..63bacab32c9 --- /dev/null +++ b/src/test/java/seedu/address/model/ModelMementoTest.java @@ -0,0 +1,55 @@ +package seedu.address.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.person.NameFacultyRoleContainsAnyKeywordsPredicate; +import seedu.address.model.person.Person; + +class ModelMementoTest { + + @Test + void getModel() { + Model expectedModel = new ModelManager(); + ModelMemento modelMemento = new ModelMemento(); + modelMemento.setModel(expectedModel); + Model actualModel = modelMemento.getModel(); + assertEquals(expectedModel, actualModel); + } + + @Test + void getPredicate() { + Predicate expectedPredicate = preparePredicate("TA"); + ModelMemento modelMemento = new ModelMemento(); + modelMemento.setPredicate(expectedPredicate); + Predicate actualPredicate = modelMemento.getPredicate(); + assertEquals(expectedPredicate, actualPredicate); + } + + @Test + void setModel() { + Model model = new ModelManager(); + ModelMemento actualMem = new ModelMemento(); + actualMem.setModel(model); + assertEquals(model, actualMem.getModel()); + } + + @Test + void setPredicate() { + Predicate predicate = preparePredicate("TA"); + ModelMemento actualMem = new ModelMemento(); + actualMem.setPredicate(predicate); + assertEquals(predicate, actualMem.getPredicate()); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsAnyPredicate}. + */ + private NameFacultyRoleContainsAnyKeywordsPredicate preparePredicate(String userInput) { + return new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} 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/FacultyTest.java b/src/test/java/seedu/address/model/person/FacultyTest.java new file mode 100644 index 00000000000..7218b8e1bdd --- /dev/null +++ b/src/test/java/seedu/address/model/person/FacultyTest.java @@ -0,0 +1,52 @@ +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 FacultyTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Faculty(null)); + } + + @Test + public void constructor_invalidFaculty_throwsIllegalArgumentException() { + String invalidFaculty = ""; + assertThrows(IllegalArgumentException.class, () -> new Faculty(invalidFaculty)); + } + + @Test + public void isValidFaculty() { + // null address + assertThrows(NullPointerException.class, () -> Faculty.isValidFaculty(null)); + + // invalid faculties + assertFalse(Faculty.isValidFaculty("")); // empty string + assertFalse(Faculty.isValidFaculty(" ")); // spaces only + assertFalse(Faculty.isValidFaculty("Sociology")); // invalid faculty + assertFalse(Faculty.isValidFaculty(" Computing")); // whitespace before faculty string + assertFalse(Faculty.isValidFaculty("C HS")); // whitespace between characters in string + assertFalse(Faculty.isValidFaculty("Computing Business")); // two separate valid faculties + assertFalse(Faculty.isValidFaculty("TA")); // role instead of faculty + + + // valid faculties + assertTrue(Faculty.isValidFaculty("Computing")); + assertTrue(Faculty.isValidFaculty("cOMputing")); // different casing + assertTrue(Faculty.isValidFaculty("CHS")); // short faculty + assertTrue(Faculty.isValidFaculty("Others")); // test for others as a valid faculty + + } + + @Test + public void testEqualsSymmetric() { + Faculty x = new Faculty("Computing"); // equals and hashCode check name field value + Faculty y = new Faculty("Computing"); + assertTrue(x.equals(y) && y.equals(x)); + assertTrue(x.hashCode() == y.hashCode()); + } +} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java deleted file mode 100644 index f136664e017..00000000000 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import seedu.address.testutil.PersonBuilder; - -public class NameContainsKeywordsPredicateTest { - - @Test - public void equals() { - List firstPredicateKeywordList = Collections.singletonList("first"); - List secondPredicateKeywordList = Arrays.asList("first", "second"); - - NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList); - - // same object -> returns true - assertTrue(firstPredicate.equals(firstPredicate)); - - // same values -> returns true - NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - assertTrue(firstPredicate.equals(firstPredicateCopy)); - - // different types -> returns false - assertFalse(firstPredicate.equals(1)); - - // null -> returns false - assertFalse(firstPredicate.equals(null)); - - // different person -> returns false - assertFalse(firstPredicate.equals(secondPredicate)); - } - - @Test - public void test_nameContainsKeywords_returnsTrue() { - // One keyword - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Multiple keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); - - // Mixed-case keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - } - - @Test - public void test_nameDoesNotContainKeywords_returnsFalse() { - // Zero keywords - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); - - // Non-matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Keywords match phone, email and address, but does not match name - predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") - .withEmail("alice@email.com").withAddress("Main Street").build())); - } -} diff --git a/src/test/java/seedu/address/model/person/NameFacultyRoleContainsAllKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameFacultyRoleContainsAllKeywordsPredicateTest.java new file mode 100644 index 00000000000..1c91e116afc --- /dev/null +++ b/src/test/java/seedu/address/model/person/NameFacultyRoleContainsAllKeywordsPredicateTest.java @@ -0,0 +1,170 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PersonBuilder; + +public class NameFacultyRoleContainsAllKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + NameFacultyRoleContainsAllKeywordsPredicate firstPredicate = new NameFacultyRoleContainsAllKeywordsPredicate( + firstPredicateKeywordList); + NameFacultyRoleContainsAllKeywordsPredicate secondPredicate = new NameFacultyRoleContainsAllKeywordsPredicate( + secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + NameFacultyRoleContainsAllKeywordsPredicate firstPredicateCopy = + new NameFacultyRoleContainsAllKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_nameContainsKeywords_returnsTrue() { + // One keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Collections.singletonList("Alice")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Multiple keywords + predicate = new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList("Alice", "Bob")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Mixed-case keywords + predicate = new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList("aLIce", "bOB")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + } + + @Test + public void test_nameContainsKeywords_returnsFalse() { + // Only one matching keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Bob", "Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice Carol").build())); + } + + @Test + public void test_nameDoesNotMatchKeyword_returnsFalse() { + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Collections.emptyList()); + // Non-matching keyword + predicate = new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + } + + @Test + public void test_facultyContainsKeywords_returnsTrue() { + // One keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Collections.singletonList("Computing")); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Computing").build())); + + // Mixed-case keywords + predicate = new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList("cOmpUtinG")); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Computing").build())); + + } + + @Test + public void test_facultyContainsKeywords_returnsFalse() { + // Only one matching keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Business", "Computing")); + assertFalse(predicate.test(new PersonBuilder().withFaculty("Computing").build())); + } + + @Test + public void test_facultyDoesNotContainKeywords_returnsFalse() { + // Zero matching keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Computing")); + assertFalse(predicate.test(new PersonBuilder().withFaculty("Business").build())); + + } + + @Test + public void test_roleContainsKeywords_returnsTrue() { + // One keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Collections.singletonList("Professor")); + assertTrue(predicate.test(new PersonBuilder().withRole("Professor").build())); + + // Mixed-case keywords + predicate = new NameFacultyRoleContainsAllKeywordsPredicate(Arrays.asList("prOfesSor")); + assertTrue(predicate.test(new PersonBuilder().withRole("Professor").build())); + } + + @Test + public void test_roleContainsKeywords_returnsFalse() { + // Only one matching keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("TA", "Admin")); + assertFalse(predicate.test(new PersonBuilder().withRole("Admin").build())); + } + + @Test + public void test_roleDoesNotContainKeywords_returnsFalse() { + // Non-matching keyword + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Admin")); + assertFalse(predicate.test(new PersonBuilder().withRole("Professor").build())); + + } + + @Test + public void test_nameFacultyRoleContainAllKeywords_returnsTrue() { + // All Keywords match name, faculty and role + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Alice", "Computing", "Professor")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice").withPhone("67891") + .withEmail("bryce@gmail.com").withFaculty("Computing").withRole("Professor") + .withTelegram("@AliceInBorderland9393").build())); + } + + @Test + public void test_nameFacultyRoleDoNotContainAllKeywords_returnsFalse() { + // Keywords match name and faculty but not role + NameFacultyRoleContainsAllKeywordsPredicate predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Alice", "Business", "TA")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withFaculty("Business").withRole("Professor") + .withTelegram("@AliceInBorderland9393").build())); + + // Keywords match name and role but not faculty + predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Alice", "Computing", "Professor")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withFaculty("Business").withRole("Professor") + .withTelegram("@AliceInBorderland9393").build())); + + // Keywords match faculty and role but not name + predicate = new NameFacultyRoleContainsAllKeywordsPredicate( + Arrays.asList("Benson", "Business", "Professor")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withFaculty("Business").withRole("Professor") + .withTelegram("@AliceInBorderland9393").build())); + } +} diff --git a/src/test/java/seedu/address/model/person/NameFacultyRoleContainsAnyKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameFacultyRoleContainsAnyKeywordsPredicateTest.java new file mode 100644 index 00000000000..6fc9a0bfa51 --- /dev/null +++ b/src/test/java/seedu/address/model/person/NameFacultyRoleContainsAnyKeywordsPredicateTest.java @@ -0,0 +1,183 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PersonBuilder; + +public class NameFacultyRoleContainsAnyKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + NameFacultyRoleContainsAnyKeywordsPredicate firstPredicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + firstPredicateKeywordList); + NameFacultyRoleContainsAnyKeywordsPredicate secondPredicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + NameFacultyRoleContainsAnyKeywordsPredicate firstPredicateCopy = + new NameFacultyRoleContainsAnyKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_nameContainsKeywords_returnsTrue() { + // One keyword + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Collections.singletonList("Alice")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Multiple keywords + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Alice", "Bob")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Only one matching keyword + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Bob", "Carol")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); + + // Mixed-case keywords + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("aLIce", "bOB")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + } + + @Test + public void test_nameDoesNotContainKeywords_returnsFalse() { + // Zero keywords + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); + + // Non-matching keyword + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + } + + @Test + public void test_facultyContainsKeywords_returnsTrue() { + // One keyword + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Collections.singletonList("Computing")); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Computing").build())); + + // Multiple keywords + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Medicine", "Others")); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Medicine").build())); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Others").build())); + + // Only one matching keyword, other non-faculty + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Business", "Professor")); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Business").build())); + + // Mixed-case keywords + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("cOmpUtinG", "bUsinEsS")); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Computing").build())); + assertTrue(predicate.test(new PersonBuilder().withFaculty("Business").build())); + + } + + @Test + public void test_facultyDoesNotContainKeywords_returnsFalse() { + // Zero keywords + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withFaculty("Computing").build())); + + // Non-matching keyword + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("CHS")); + assertFalse(predicate.test(new PersonBuilder().withFaculty("Business").build())); + + } + + @Test + public void test_roleContainsKeywords_returnsTrue() { + // One keyword + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Collections.singletonList("Professor")); + assertTrue(predicate.test(new PersonBuilder().withRole("Professor").build())); + + // Only one matching keyword + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("Tutor", "TA")); + assertTrue(predicate.test(new PersonBuilder().withRole("Tutor").build())); + + // Mixed-case keywords + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("oTHErS", "lEctureR")); + assertTrue(predicate.test(new PersonBuilder().withRole("Others").build())); + assertTrue(predicate.test(new PersonBuilder().withRole("Lecturer").build())); + } + + @Test + public void test_roleDoesNotContainKeywords_returnsFalse() { + // Zero keywords + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withRole("Professor").build())); + + // Non-matching keyword + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate(Arrays.asList("TA")); + assertFalse(predicate.test(new PersonBuilder().withRole("Researcher").build())); + + } + + @Test + public void test_nameFacultyRoleContainKeywords_returnsTrue() { + // Keywords match faculty only + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Arrays.asList("CDE")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice").withPhone("67891") + .withEmail("doesNotMatch@gmail.com").withFaculty("CDE").withRole("Professor") + .withTelegram("@doesNotMatch").build())); + + // Keywords match name only + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Arrays.asList("TA")); + assertTrue(predicate.test(new PersonBuilder().withName("TA").withPhone("67891") + .withEmail("doesNotMatch@gmail.com").withFaculty("Computing").withRole("Professor") + .withTelegram("@doesNotMatch").build())); + + // Keywords match role only + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Arrays.asList("Admin")); + assertTrue(predicate.test(new PersonBuilder().withName("Bob").withPhone("67891") + .withEmail("doesNotMatch@gmail.com").withFaculty("Computing").withRole("Admin") + .withTelegram("@doesNotMatch").build())); + + // Keywords match name, faculty and role + predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Arrays.asList("Admin", "Bob", "Computing")); + assertTrue(predicate.test(new PersonBuilder().withName("Bob").withPhone("67891") + .withEmail("doesNotMatch@gmail.com").withFaculty("Computing").withRole("Admin") + .withTelegram("@doesNotMatch").build())); + } + + @Test + public void test_nameFacultyRoleDoNotContainKeywords_returnsFalse() { + // Keywords match phone, email and address, but does not match name, faculty and role + NameFacultyRoleContainsAnyKeywordsPredicate predicate = new NameFacultyRoleContainsAnyKeywordsPredicate( + Arrays.asList("12345", "alice@email.com", "Computing", "Professor", "Main", "Street")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withFaculty("Business").withRole("Admin") + .withTelegram("@MainStreet").build())); + } +} diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java index b29c097cfd4..3657f0ed119 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/address/model/person/PersonTest.java @@ -2,17 +2,23 @@ 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_FACULTY_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_FAVOURITE_AMY; 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_ROLE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_BOB; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; +import java.util.Set; + import org.junit.jupiter.api.Test; +import seedu.address.model.tag.Tag; import seedu.address.testutil.PersonBuilder; public class PersonTest { @@ -33,7 +39,12 @@ public void isSamePerson() { // 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(); + .withFaculty(VALID_FACULTY_BOB).withRole(VALID_ROLE_BOB).withTelegram(VALID_TELEGRAM_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + assertTrue(ALICE.isSamePerson(editedAlice)); + + // same name, different favourite -> returns true + editedAlice = new PersonBuilder(ALICE).withFavourite(VALID_FAVOURITE_AMY).build(); assertTrue(ALICE.isSamePerson(editedAlice)); // different name, all other attributes same -> returns false @@ -50,6 +61,38 @@ public void isSamePerson() { assertFalse(BOB.isSamePerson(editedBob)); } + @Test + public void isSameFavouritePerson() { + // same object -> returns true + assertTrue(ALICE.isSameFavouritePerson(ALICE)); + + // null -> returns false + assertFalse(ALICE.isSameFavouritePerson(null)); + + // same name and favourite, all other attributes different -> returns true + Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) + .withFaculty(VALID_FACULTY_BOB).withRole(VALID_ROLE_BOB).withTelegram(VALID_TELEGRAM_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + assertTrue(ALICE.isSameFavouritePerson(editedAlice)); + + // same name, different favourite -> returns false + editedAlice = new PersonBuilder(ALICE).withFavourite(VALID_FAVOURITE_AMY).build(); + assertFalse(ALICE.isSameFavouritePerson(editedAlice)); + + // different name, all other attributes same -> returns false + editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); + assertFalse(ALICE.isSameFavouritePerson(editedAlice)); + + // name differs in case, all other attributes same -> returns false + Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); + assertFalse(BOB.isSameFavouritePerson(editedBob)); + + // name has trailing spaces, all other attributes same -> returns false + String nameWithTrailingSpaces = VALID_NAME_BOB + " "; + editedBob = new PersonBuilder(BOB).withName(nameWithTrailingSpaces).build(); + assertFalse(BOB.isSameFavouritePerson(editedBob)); + } + @Test public void equals() { // same values -> returns true @@ -80,12 +123,62 @@ public void equals() { editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build(); assertFalse(ALICE.equals(editedAlice)); + // different faculty -> returns false + editedAlice = new PersonBuilder(ALICE).withFaculty(VALID_FACULTY_BOB).build(); + assertFalse(ALICE.equals(editedAlice)); + + // different role -> returns false + editedAlice = new PersonBuilder(ALICE).withRole(VALID_ROLE_BOB).build(); + assertFalse(ALICE.equals(editedAlice)); + // different address -> returns false - editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); + editedAlice = new PersonBuilder(ALICE).withTelegram(VALID_TELEGRAM_BOB).build(); + assertFalse(ALICE.equals(editedAlice)); + + // different favourite -> returns false + editedAlice = new PersonBuilder(ALICE).withFavourite(VALID_FAVOURITE_AMY).build(); assertFalse(ALICE.equals(editedAlice)); // different tags -> returns false editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); assertFalse(ALICE.equals(editedAlice)); } + + @Test + public void testEqualsSymmetric() { + Person x = ALICE; // equals and hashCode check name field value + Person y = ALICE; + assertTrue(x.equals(y) && y.equals(x)); + assertTrue(x.hashCode() == y.hashCode()); + } + + @Test + public void testToString() { + Person x = ALICE; + Person y = BOB; + final StringBuilder builder = new StringBuilder(); + builder.append(ALICE.getName()) + .append("; \nPhone: ") + .append(ALICE.getPhone()) + .append("; \nEmail: ") + .append(ALICE.getEmail()) + .append("; \nFaculty: ") + .append(ALICE.getFaculty()) + .append("; \nRole: ") + .append(ALICE.getRole()) + .append("; \nTelegram: ") + .append(ALICE.getTelegram()) + .append("; \nFavourite: ") + .append(ALICE.getFavourite()); + Set tags = ALICE.getTags(); + if (!tags.isEmpty()) { + builder.append("; \nTags: "); + tags.forEach(builder::append); + } + String alice = builder.toString(); + assertTrue(x.toString().equals(x.toString())); + assertTrue(x.toString().equals(alice)); + assertFalse(x.toString().equals(y.toString())); + + } } diff --git a/src/test/java/seedu/address/model/person/RoleTest.java b/src/test/java/seedu/address/model/person/RoleTest.java new file mode 100644 index 00000000000..cf2282d807d --- /dev/null +++ b/src/test/java/seedu/address/model/person/RoleTest.java @@ -0,0 +1,51 @@ +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 RoleTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Role(null)); + } + + @Test + public void constructor_invalidRole_throwsIllegalArgumentException() { + String invalidRole = ""; + assertThrows(IllegalArgumentException.class, () -> new Role(invalidRole)); + } + + @Test + public void isValidRole() { + // null address + assertThrows(NullPointerException.class, () -> Role.isValidRole(null)); + + // invalid faculties + assertFalse(Role.isValidRole("")); // empty string + assertFalse(Role.isValidRole(" ")); // spaces only + assertFalse(Role.isValidRole("Dean")); // not in list of roles + assertFalse(Role.isValidRole(" Professor")); // whitespace before role + assertFalse(Role.isValidRole("Prof essor")); // whitespace in the role + assertFalse(Role.isValidRole("TA Professor")); // two separate valid roles + assertFalse(Role.isValidRole("Computing")); // faculty instead of role + + // valid faculties + assertTrue(Role.isValidRole("Professor")); + assertTrue(Role.isValidRole("TA")); // short role + assertTrue(Role.isValidRole("pROFessor")); // role with different case + assertTrue(Role.isValidRole("Others")); // test for others as a valid role + } + + @Test + public void testEqualsSymmetric() { + Role x = new Role("Professor"); // equals and hashCode check name field value + Role y = new Role("Professor"); + assertTrue(x.equals(y)); + assertTrue(y.equals(x)); + assertTrue(x.hashCode() == y.hashCode()); + } +} diff --git a/src/test/java/seedu/address/model/person/TagContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/TagContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..9a818550b52 --- /dev/null +++ b/src/test/java/seedu/address/model/person/TagContainsKeywordsPredicateTest.java @@ -0,0 +1,75 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PersonBuilder; + +public class TagContainsKeywordsPredicateTest { + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + TagContainsKeywordsPredicate firstPredicate = new TagContainsKeywordsPredicate(firstPredicateKeywordList); + TagContainsKeywordsPredicate secondPredicate = new TagContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + TagContainsKeywordsPredicate firstPredicateCopy = new TagContainsKeywordsPredicate(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_tagContainsKeywords_returnsTrue() { + // One keyword + TagContainsKeywordsPredicate predicate = new TagContainsKeywordsPredicate(Collections.singletonList("CS2103T")); + assertTrue(predicate.test(new PersonBuilder().withTags("CS2103T").build())); + + // Multiple keywords + predicate = new TagContainsKeywordsPredicate(Arrays.asList("CS2103T", "CS2100")); + assertTrue(predicate.test(new PersonBuilder().withTags("CS2103T", "CS2100").build())); + + // Only one matching keyword + predicate = new TagContainsKeywordsPredicate(Arrays.asList("CS2100", "bestie")); + assertTrue(predicate.test(new PersonBuilder().withTags("CS2103T", "bestie").build())); + + // Mixed-case keywords + predicate = new TagContainsKeywordsPredicate(Arrays.asList("cS2103t", "cS2100")); + assertTrue(predicate.test(new PersonBuilder().withTags("CS2103T", "CS2100").build())); + } + + @Test + public void test_tagDoesNotContainKeywords_returnsFalse() { + // Zero keywords + TagContainsKeywordsPredicate predicate = new TagContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withTags("CS2103T").build())); + + // Non-matching keyword + predicate = new TagContainsKeywordsPredicate(Arrays.asList("bestie")); + assertFalse(predicate.test(new PersonBuilder().withTags("CS2103T", "CS2100").build())); + + // Keywords match phone, email and address, but does not match name + predicate = new TagContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withTelegram("@Main_Street").build())); + } +} + diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java index 1cc5fe9e0fe..f6be4a8cd4b 100644 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java @@ -3,8 +3,8 @@ 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_TELEGRAM_BOB; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; @@ -25,26 +25,26 @@ public class UniquePersonListTest { @Test public void contains_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.contains(null)); + assertThrows(NullPointerException.class, () -> uniquePersonList.containsName(null)); } @Test public void contains_personNotInList_returnsFalse() { - assertFalse(uniquePersonList.contains(ALICE)); + assertFalse(uniquePersonList.containsName(ALICE)); } @Test public void contains_personInList_returnsTrue() { uniquePersonList.add(ALICE); - assertTrue(uniquePersonList.contains(ALICE)); + assertTrue(uniquePersonList.containsName(ALICE)); } @Test public void contains_personWithSameIdentityFieldsInList_returnsTrue() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Person editedAlice = new PersonBuilder(ALICE).withTelegram(VALID_TELEGRAM_BOB).withTags(VALID_TAG_HUSBAND) .build(); - assertTrue(uniquePersonList.contains(editedAlice)); + assertTrue(uniquePersonList.containsName(editedAlice)); } @Test @@ -85,7 +85,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).withTelegram(VALID_TELEGRAM_BOB).withTags(VALID_TAG_HUSBAND) .build(); uniquePersonList.setPerson(ALICE, editedAlice); UniquePersonList expectedUniquePersonList = new UniquePersonList(); diff --git a/src/test/java/seedu/address/model/telegram/TelegramTest.java b/src/test/java/seedu/address/model/telegram/TelegramTest.java new file mode 100644 index 00000000000..b297c0d2916 --- /dev/null +++ b/src/test/java/seedu/address/model/telegram/TelegramTest.java @@ -0,0 +1,45 @@ +package seedu.address.model.telegram; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +public class TelegramTest { + + @Test + public void constructor_null_throwsNoSuchElementException() { + assertThrows(NoSuchElementException.class, () -> new Telegram(Optional.empty()).value.get()); + } + + @Test + public void constructor_invalidAddress_throwsIllegalArgumentException() { + String invalidHandle = ""; + assertThrows(IllegalArgumentException.class, () -> new Telegram(Optional.of(invalidHandle))); + } + + @Test + public void isValidHandle() { + // null address + assertThrows(NullPointerException.class, () -> Telegram.isValidHandle(null)); + + // invalid addresses + assertFalse(Telegram.isValidHandle("")); // empty string + assertFalse(Telegram.isValidHandle(" ")); // spaces only + assertFalse(Telegram.isValidHandle("@four")); // < 5 + assertFalse(Telegram.isValidHandle("@totalnumberofcharactersshereare32")); // > 32 + assertFalse(Telegram.isValidHandle("@endswith_")); // > 32 + assertFalse(Telegram.isValidHandle("@special*")); // > 32 + + // valid addresses + assertTrue(Telegram.isValidHandle("@validNoDigits")); + assertTrue(Telegram.isValidHandle("@1919191")); + assertTrue(Telegram.isValidHandle("@five1")); + assertTrue(Telegram.isValidHandle("@totalnumberofcharactershereare32")); + assertTrue(Telegram.isValidHandle("@hello_hehe")); // one character + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java index 83b11331cdb..5013f0d3e8a 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java @@ -12,22 +12,28 @@ 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.Faculty; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; 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_TELEGRAM = " "; private static final String INVALID_EMAIL = "example.com"; + private static final String INVALID_FACULTY = " "; + private static final String INVALID_ROLE = " "; private static final String INVALID_TAG = "#friend"; private static final String VALID_NAME = BENSON.getName().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); private static final String VALID_EMAIL = BENSON.getEmail().toString(); - private static final String VALID_ADDRESS = BENSON.getAddress().toString(); + private static final String VALID_FACULTY = BENSON.getFaculty().toString(); + private static final String VALID_ROLE = BENSON.getRole().toString(); + private static final String VALID_TELEGRAM = BENSON.getTelegram().toString(); + private static final boolean VALID_FAVOURITE = BENSON.getFavourite().isFavourite; private static final List VALID_TAGS = BENSON.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList()); @@ -41,14 +47,16 @@ 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_FACULTY, VALID_ROLE, VALID_TELEGRAM, + VALID_FAVOURITE, 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_FACULTY, + VALID_ROLE, VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -56,14 +64,16 @@ 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_FACULTY, VALID_ROLE, + VALID_TELEGRAM, VALID_FAVOURITE, 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_FACULTY, + VALID_ROLE, VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -71,30 +81,52 @@ 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_FACULTY, VALID_ROLE, + VALID_TELEGRAM, VALID_FAVOURITE, 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_FACULTY, + VALID_ROLE, VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test - public void toModelType_invalidAddress_throwsIllegalValueException() { + public void toModelType_invalidFaculty_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_FACULTY, VALID_ROLE, + VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); + + String expectedMessage = Faculty.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullFaculty_throwsIllegalValueException() { + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, + VALID_ROLE, VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Faculty.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidRole_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_FACULTY, INVALID_ROLE, + VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); + String expectedMessage = Role.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()); + public void toModelType_nullRole_throwsIllegalValueException() { + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_FACULTY, + null, VALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Role.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -103,8 +135,16 @@ 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_FACULTY, VALID_ROLE, + VALID_TELEGRAM, VALID_FAVOURITE, invalidTags); assertThrows(IllegalValueException.class, person::toModelType); } + @Test + public void toModelType_invalidTelegram_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_FACULTY, VALID_ROLE, + INVALID_TELEGRAM, VALID_FAVOURITE, VALID_TAGS); + assertThrows(IllegalValueException.class, person::toModelType); + } } diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e..7e0b4806013 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java @@ -1,16 +1,20 @@ package seedu.address.testutil; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; +import seedu.address.model.person.Favourite; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; /** * A utility class to help with building EditPersonDescriptor objects. @@ -35,7 +39,10 @@ public EditPersonDescriptorBuilder(Person person) { descriptor.setName(person.getName()); descriptor.setPhone(person.getPhone()); descriptor.setEmail(person.getEmail()); - descriptor.setAddress(person.getAddress()); + descriptor.setFaculty(person.getFaculty()); + descriptor.setRole(person.getRole()); + descriptor.setTelegram(person.getTelegram()); + descriptor.setFavourite(person.getFavourite()); descriptor.setTags(person.getTags()); } @@ -64,10 +71,34 @@ public EditPersonDescriptorBuilder withEmail(String email) { } /** - * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + * Sets the {@code Faculty} of the {@code EditPersonDescriptor} that we are building. */ - public EditPersonDescriptorBuilder withAddress(String address) { - descriptor.setAddress(new Address(address)); + public EditPersonDescriptorBuilder withFaculty(String faculty) { + descriptor.setFaculty(new Faculty(faculty)); + return this; + } + + /** + * Sets the {@code Role} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withRole(String role) { + descriptor.setRole(new Role(role)); + return this; + } + + /** + * Sets the {@code Telegram} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withTelegram(String telegram) { + descriptor.setTelegram(new Telegram(Optional.ofNullable(telegram))); + return this; + } + + /** + * Sets the {@code Favourite} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withFavourite(boolean isFavourite) { + descriptor.setFavourite(new Favourite(isFavourite)); return this; } diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index 6be381d39ba..1dc82de999b 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -1,14 +1,18 @@ package seedu.address.testutil; import java.util.HashSet; +import java.util.Optional; import java.util.Set; -import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Faculty; +import seedu.address.model.person.Favourite; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; +import seedu.address.model.telegram.Telegram; import seedu.address.model.util.SampleDataUtil; /** @@ -18,13 +22,19 @@ 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_EMAIL = "amy2@gmail.com"; + public static final String DEFAULT_FACULTY = "Computing"; + public static final String DEFAULT_ROLE = "Professor"; + public static final String DEFAULT_TELEGRAM = "@Amy23"; + public static final boolean DEFAULT_FAVOURITE = false; private Name name; private Phone phone; private Email email; - private Address address; + private Faculty faculty; + private Role role; + private Telegram telegram; + private Favourite favourite; private Set tags; /** @@ -34,7 +44,10 @@ public PersonBuilder() { name = new Name(DEFAULT_NAME); phone = new Phone(DEFAULT_PHONE); email = new Email(DEFAULT_EMAIL); - address = new Address(DEFAULT_ADDRESS); + faculty = new Faculty(DEFAULT_FACULTY); + role = new Role(DEFAULT_ROLE); + telegram = new Telegram(Optional.of(DEFAULT_TELEGRAM)); + favourite = new Favourite(DEFAULT_FAVOURITE); tags = new HashSet<>(); } @@ -45,7 +58,10 @@ public PersonBuilder(Person personToCopy) { name = personToCopy.getName(); phone = personToCopy.getPhone(); email = personToCopy.getEmail(); - address = personToCopy.getAddress(); + faculty = personToCopy.getFaculty(); + role = personToCopy.getRole(); + telegram = personToCopy.getTelegram(); + favourite = personToCopy.getFavourite(); tags = new HashSet<>(personToCopy.getTags()); } @@ -68,8 +84,16 @@ 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 withTelegram(String telegram) { + this.telegram = new Telegram(Optional.ofNullable(telegram)); + return this; + } + + /** + * Sets the {@code Favourite} of the {@code Person} that we are building. + */ + public PersonBuilder withFavourite(boolean isFavourite) { + this.favourite = new Favourite(isFavourite); return this; } @@ -89,8 +113,24 @@ public PersonBuilder withEmail(String email) { return this; } + /** + * Sets the {@code Faculty} of the {@code Person} that we are building. + */ + public PersonBuilder withFaculty(String faculty) { + this.faculty = new Faculty(faculty); + return this; + } + + /** + * Sets the {@code Role} of the {@code Person} that we are building. + */ + public PersonBuilder withRole(String role) { + this.role = new Role(role); + return this; + } + public Person build() { - return new Person(name, phone, email, address, tags); + return new Person(name, phone, email, faculty, role, telegram, favourite, tags); } } diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 90849945183..9739b41c6d1 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -1,10 +1,12 @@ 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_FACULTY; 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_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM; import java.util.Set; @@ -33,7 +35,9 @@ 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_FACULTY + person.getFaculty().value + " "); + sb.append(PREFIX_ROLE + person.getRole().value + " "); + sb.append(PREFIX_TELEGRAM + person.getTelegram().value.orElse(null) + " "); person.getTags().stream().forEach( s -> sb.append(PREFIX_TAG + s.tagName + " ") ); @@ -48,7 +52,10 @@ 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.getFaculty().ifPresent(faculty -> sb.append(PREFIX_FACULTY).append(faculty.value).append(" ")); + descriptor.getRole().ifPresent(role -> sb.append(PREFIX_ROLE).append(role.value).append(" ")); + descriptor.getTelegram().ifPresent(telegram -> sb.append(PREFIX_TELEGRAM) + .append(telegram.value.orElse(null)).append(" ")); if (descriptor.getTags().isPresent()) { Set tags = descriptor.getTags().get(); if (tags.isEmpty()) { diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index fec76fb7129..016af3e8f6d 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -1,15 +1,19 @@ package seedu.address.testutil; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_FACULTY_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_FACULTY_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_ROLE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ROLE_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_TELEGRAM_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TELEGRAM_BOB; import java.util.ArrayList; import java.util.Arrays; @@ -24,35 +28,42 @@ public class TypicalPersons { public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") - .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") - .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") + .withTelegram("@AliceInBorderland9393").withEmail("alice@example.com").withFaculty("Business") + .withRole("Professor").withPhone("94351253").withTags("friends").build(); + public static final Person BENSON = new PersonBuilder().withName("Benson Meier").withTelegram(null) + .withEmail("johnd@example.com").withPhone("98765432").withFaculty("Computing").withRole("TA") + .withFavourite(true).withTelegram("@bendsons332") .withTags("owesMoney", "friends").build(); public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); + .withEmail("heinz@example.com").withFaculty("Dentistry").withRole("Professor") + .withTelegram("@ketchupMaster59").build(); public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); + .withEmail("cornelia@example.com").withFaculty("Law").withRole("TA").withTelegram("@theDamnDaniel391") + .withTags("friends").build(); public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); + .withEmail("werner@example.com").withFaculty("Nursing").withTelegram(null).withRole("Professor").build(); public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); + .withEmail("lydia@example.com").withFaculty("Music").withRole("TA").withTelegram("@100ladybugs") + .withTelegram("@100ladybugs").build(); public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); + .withEmail("anna@example.com").withFaculty("Pharmacy").withTelegram(null) + .withRole("Professor").build(); // Manually added public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); + .withEmail("stefan@example.com").withFaculty("Business").withRole("Professor") + .withTelegram("@bee83").build(); public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); + .withEmail("hans@example.com").withFaculty("Computing").withRole("TA") + .withTelegram("@HoUSA").build(); // Manually added - Person's details found in {@code CommandTestUtil} public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + .withEmail(VALID_EMAIL_AMY).withFaculty(VALID_FACULTY_AMY).withRole(VALID_ROLE_AMY) + .withTelegram(VALID_TELEGRAM_AMY).withTags(VALID_TAG_FRIEND).build(); public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .withEmail(VALID_EMAIL_BOB).withFaculty(VALID_FACULTY_BOB).withRole(VALID_ROLE_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).withTelegram(VALID_TELEGRAM_BOB) .build(); public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER @@ -70,7 +81,39 @@ public static AddressBook getTypicalAddressBook() { return ab; } + public static AddressBook getMinusAddressBook() { + AddressBook ab = new AddressBook(); + for (Person person : getTypicalPersonsMinusAlice()) { + ab.addPerson(person); + } + return ab; + } + + public static AddressBook getEditedAddressBook() { + AddressBook ab = new AddressBook(); + for (Person person : getTypicalPersonsEditedAlice()) { + ab.addPerson(person); + } + return ab; + } + public static List getTypicalPersons() { return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); } + + public static List getTypicalPersonsMinusAlice() { + return new ArrayList<>(Arrays.asList(BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); + } + + public static List getTypicalPersonsEditedAlice() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); + } + + public static List getTypicalPersonsAddHoon() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE, HOON)); + } + + public static List getTypicalPersonsTaggedFriends() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, DANIEL)); + } }