diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 2385938a20c..75f4d15033e 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,10 +1,10 @@ --- - layout: default.md - title: "Developer Guide" - pageNav: 3 +layout: default.md +title: "Developer Guide" +pageNav: 3 --- -# AB-3 Developer Guide +# EduContacts Developer Guide @@ -13,7 +13,7 @@ ## **Acknowledgements** -_{ list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well }_ +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org) ([UG](https://se-education.org/addressbook-level3/UserGuide.html), [DG](https://se-education.org/addressbook-level3/DeveloperGuide.html), [GitHub Page](https://github.com/se-edu/addressbook-level3)). -------------------------------------------------------------------------------------------------------------------- @@ -90,7 +90,7 @@ Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 12345678")` API call as an example. @@ -130,7 +130,7 @@ The `Model` component, -**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.
+**Note:** An alternative (arguably, a more OOP) model is given below. It has a `Role` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Role` object per unique tag, instead of each `Person` needing their own `Role` objects.
@@ -176,11 +176,11 @@ Step 1. The user launches the application for the first time. The `VersionedAddr -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. +Step 2. The user executes `delete 12345678` command to delete the person with Student ID of `12345678` in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 12345678` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. -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`. +Step 3. The user executes `add id/12345678 …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. @@ -281,6 +281,7 @@ _{Explain here how the data archiving feature will be implemented}_ **Value proposition**: save important time through simplification of student-parent contact management, enhancement in communication tracking and integrated progress reports +
### User stories @@ -310,24 +311,26 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli | `*` | long-time teacher | import contact data | load data from a file to restore lost or missing data | | `*` | long-time teacher | access communication history | be well-prepared for upcoming meetings | +
+ ### 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 `EduContacts` and the **Actor** is the `user`, unless specified otherwise) **Use case: UC01 - Add a student** **MSS** 1. User adds a student to the list of contacts. -2. AddressBook updates the list of contacts. +2. EduContacts updates the list of contacts. Use case ends. **Extensions** -* 1a. AddressBook detects an error in the given data. +* 1a. EduContacts detects an error in the given data. - * 1a1. AddressBook shows an error message. + * 1a1. EduContacts shows an error message. Use case ends. @@ -337,36 +340,29 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **MSS** -1. User finds a student. -2. AddressBook displays the student. +1. User provides details of the student. +2. EduContacts displays the student and their details. Use case ends. **Extensions** -* 1a. AddressBook detects an error in the given data. +* 1a. EduContacts detects an error in the given data. - * 1a1. AddressBook shows an error message. + * 1a1. EduContacts shows an error message. Use case ends. -* 2a. AddressBook displays an empty list. +* 2a. EduContacts is unable to find the student. Use case ends. -* 2b. AddressBook displays a list of multiple students. - - * 2b1. User manually locates the student in the list. - - Use case ends. - **Use case: UC03 - Add a grade for a student** **MSS** - -1. User finds the student they wish to add a grade for (UC02). +1. User finds the student (UC02) they wish to add a grade for. 2. User adds a grade for the student. -3. AddressBook updates the list of contacts. +3. EduContacts updates the list of contacts. Use case ends. @@ -376,9 +372,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 2a. AddressBook detects an error in the given data. +* 2a. EduContacts detects an error in the given data. - * 2a1. AddressBook shows an error message. + * 2a1. EduContacts shows an error message. Use case ends. @@ -386,9 +382,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **MSS** -1. User finds the student they wish to delete from the list (UC02). +1. User finds the student (UC02) they wish to delete from the list. 2. User deletes the student in the list. -3. AddressBook updates the list of contacts. +3. EduContacts updates the list of contacts. Use case ends. @@ -398,9 +394,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 2a. AddressBook detects an error in the given data. +* 2a. EduContacts detects an error in the given data. - * 2a1. AddressBook shows an error message. + * 2a1. EduContacts shows an error message. Use case ends. @@ -408,9 +404,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **MSS** -1. User finds the student they wish to edit (UC02). +1. User finds the student (UC02) they wish to edit. 2. User edits the details of the student in the list. -3. AddressBook updates the list of contacts. +3. EduContacts updates the list of contacts. Use case ends. @@ -420,19 +416,19 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 2a. AddressBook detects an error in the given data. +* 2a. EduContacts detects an error in the given data. - * 2a1. AddressBook shows an error message. + * 2a1. EduContacts shows an error message. Use case ends. -**Use case: UC06 - Add a tag to a student** +**Use case: UC06 - Add a module to a student** **MSS** -1. User finds the student they wish to edit the details of (UC02). -2. User adds a tag to the student in the list. -3. AddressBook adds the tag to the student and updates the list of contacts. +1. User finds the student (UC02) they wish to add a module for. +2. User adds a module to the student in the list. +3. EduContacts updates the list of contacts. Use case ends. @@ -442,19 +438,53 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 2a. AddressBook detects an error in the given data. +* 2a. EduContacts detects an error in the given data. - * 2a1. AddressBook shows an error message. + * 2a1. EduContacts shows an error message. Use case ends. -**Use case: UC07 - Add contacts of next-of-kins of a student** +* 2b. Student already has the module. + + * 2b1. EduContacts shows an error message. + + Use case ends. + +**Use case: UC07 - Grade a student** **MSS** -1. User finds the student they wish to add contacts of next-of-kins for (UC02). +1. User finds the student (UC02) they wish to grade. +2. User grades a module the student is taking. +3. EduContacts updates the list of contacts. + + Use case ends. + +**Extensions** + +* 1a. User is unable to find the student. + + Use case ends. + +* 2a. EduContacts detects an error in the given data. + + * 2a1. EduContacts shows an error message. + + Use case ends. + +* 2b. The module is already graded. + + * 2b1. EduContacts overwrites the old grade with the new grade. + + Use case ends. + +**Use case: UC08 - Add contacts of next-of-kins of a student** + +**MSS** + +1. User finds the student (UC02) they wish to add contacts of next-of-kins for. 2. User adds contacts of next-of-kins of the student in the list. -3. AddressBook updates the list of contacts. +3. EduContacts updates the list of contacts. Use case ends. @@ -464,12 +494,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 2a. AddressBook detects an error in the given data. +* 2a. EduContacts detects an error in the given data. - * 2a1. AddressBook shows an error message. + * 2a1. EduContacts shows an error message. Use case ends. +
+ ### Non-Functional Requirements 1. **Data Requirements** - EduContacts must be capable of storing up to 1000 students’ contact details and academic data without significant performance degradation @@ -510,14 +542,12 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli - The system should support exporting and importing data in common file formats (e.g., CSV) for ease of use and integration. 13. **Disaster Recovery** - The system should support manual and automatic backups to prevent data loss. In case of a critical failure, the data should be easily recoverable. - - There should be clear steps for restoring data from a backup after a system failure, ensuring minimal downtime. + - There should be clear steps for restoring data from a backup after a system failure, ensuring minimal downtime.
14. **Fault Tolerance** - All critical errors should be logged, allowing developers to troubleshoot and resolve issues. Minor errors should not crash the system but allow users to continue their tasks. - In the event of a system fault, the system should continue operating in a degraded mode without losing functionality. - - - +
### Glossary @@ -545,44 +575,85 @@ testers are expected to do more *exploratory* testing.
+
+ ### Launch and shutdown 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. + 2. 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 +2. Saving window preferences 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.
+ 2. 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. One person in the list has Student ID `12345678`. - 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. + 2. Test case: `delete 12345678`
+ Expected: Person with Student ID `12345678` is deleted. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. - 1. Test case: `delete 0`
+ 3. Test case: `delete 1234 5678`
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)
+ 4. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is a Student ID that no student in the list has)
Expected: Similar to previous. -1. _{ more test cases …​ }_ +2. Deleting a person while only one person is being shown + + 1. Prerequisites: Filter persons using the `filter` command until only one person remains. Multiple persons in the list. Person that remains has Student ID `12345678`. One person in the list has Student ID `11111111` + + 2. Test case: `delete 12345678`
+ Expected: Person with Student ID `12345678` is deleted. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. List of persons shown is now blank. + + 3. Test case: `delete 11111111`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + +3. Deleting a person while no persons are in the list + + 1. Prerequisites: Delete all persons in the list using the `clear` command. + + 2. Test case: `delete 12345678`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + +
### Saving data -1. Dealing with missing/corrupted data files +1. Dealing with missing data files + + 1. To simulate a missing file, in the same folder as the jar file, navigate to the `data` folder and delete the `address.json` file in the folder. + + 2. Launch EduContacts by double-clicking the jar file.
+ Expected: EduContacts is populated by a set of default list of persons. A new `address.json` file will be created in the `data` folder after closing the app or executing a command. + +2. Dealing with corrupted data files + + 1. To simulate a corrupted file, navigate to the `data` folder and remove a curly brace at the end of the file. + + 2. Launch EduContacts by double-clicking the jar file.
+ Expected: EduContacts has a blank list of persons. A new `address.json` file will be created in the `data` folder after closing the app or executing a command. + +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix: Effort** + +_{to work on in the future}_ + +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix: Planned Enhancements** - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +_{to work on in the future}_ -1. _{ more test cases …​ }_ diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 9e30139d075..fbcdd2677cc 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,7 +1,7 @@ --- - layout: default.md - title: "Setting up and getting started" - pageNav: 3 +layout: default.md +title: "Setting up and getting started" +pageNav: 3 --- # Setting up and getting started @@ -52,7 +52,7 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [EduContacts’ architecture](DeveloperGuide.md#architecture). 1. **Do the tutorials** These tutorials will help you get acquainted with the codebase. diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..e96576b8023 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -33,6 +33,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns a Person object to display */ + Person getPersonToDisplay(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..147a2dc3c8b 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -71,6 +71,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public Person getPersonToDisplay() { + return model.getPersonToDisplay(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java new file mode 100644 index 00000000000..9598a8ed201 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.person.StudentId; + +/** + * Finds a person identified using their Student ID and displays their details. + */ +public class FindCommand extends Command { + + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds the student identified by the Student ID used and displays their details.\n" + + "Parameters: " + + "ID\n" + + "Example: " + COMMAND_WORD + " " + + "12345678"; + + public static final String MESSAGE_FIND_PERSON_SUCCESS = "Found Student: %1$s"; + public static final String MESSAGE_PERSON_NOT_FOUND = "No student is found with Student ID: %1$s"; + private final StudentId studentId; + + /** + * Creates a FindCommand to find the person identified by the specified {@code StudentId}. + * + * @param studentId The student ID of the person to find. + * @throws NullPointerException if the {@code studentId} is null. + */ + public FindCommand(StudentId studentId) { + requireNonNull(studentId); + this.studentId = studentId; + } + + /** + * Executes the find command and displays the student identified by the given studentID. + * + * @param model the model that contains the data of persons + * @return a CommandResult that shows the outcome of the command + * @throws CommandException if the studentID is invalid or not found + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + Person toFind = null; + + for (Person person : lastShownList) { + if (person.getStudentId().equals(studentId)) { + toFind = person; + break; + } + } + + if (toFind == null) { + throw new CommandException(String.format(MESSAGE_PERSON_NOT_FOUND, studentId)); + } + + model.setPersonToDisplay(toFind); + return new CommandResult(String.format(MESSAGE_FIND_PERSON_SUCCESS, Messages.format(toFind))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindCommand)) { + return false; + } + + FindCommand otherFindCommand = (FindCommand) other; + return studentId.equals(otherFindCommand.studentId); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetStudentId", studentId) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 08512519f23..4167b2c78bb 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -15,6 +15,7 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.GradeCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; @@ -85,6 +86,9 @@ public Command parseCommand(String userInput) throws ParseException { case ModuleCommand.COMMAND_WORD: return new ModuleCommandParser().parse(arguments); + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java new file mode 100644 index 00000000000..634d211c2cf --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.StudentId; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindCommand parse(String args) throws ParseException { + String trimmedArg = args.trim(); + if (trimmedArg.contains(" ")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + StudentId studentId = ParserUtil.parseStudentId(trimmedArg); + return new FindCommand(studentId); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..83f1a585c9f 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -84,4 +84,15 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Displays the given person {@code personToDisplay} and their details. + * {@code target} must exist in the address book. + */ + void setPersonToDisplay(Person personToDisplay); + + /** + * Returns the Person object to display. + */ + Person getPersonToDisplay(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..6aa4c8ba38c 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,7 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Objects; import java.util.function.Predicate; import java.util.logging.Logger; @@ -22,6 +23,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private Person personToDisplay; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -36,6 +38,20 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); } + /** + * Initializes a ModelManager with the given addressBook, userPrefs and personToDisplay. + */ + public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, Person personToDisplay) { + requireAllNonNull(addressBook, userPrefs); + + logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + + this.addressBook = new AddressBook(addressBook); + this.userPrefs = new UserPrefs(userPrefs); + filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + setPersonToDisplay(personToDisplay); + } + public ModelManager() { this(new AddressBook(), new UserPrefs()); } @@ -80,6 +96,7 @@ public void setAddressBookFilePath(Path addressBookFilePath) { @Override public void setAddressBook(ReadOnlyAddressBook addressBook) { this.addressBook.resetData(addressBook); + setPersonToDisplay(null); } @Override @@ -96,11 +113,16 @@ public boolean hasPerson(Person person) { @Override public void deletePerson(Person target) { addressBook.removePerson(target); + if (target.isSamePerson(personToDisplay)) { + setPersonToDisplay(null); + } } @Override public void addPerson(Person person) { addressBook.addPerson(person); + + setPersonToDisplay(person); updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } @@ -109,6 +131,7 @@ public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); addressBook.setPerson(target, editedPerson); + setPersonToDisplay(editedPerson); } //=========== Filtered Person List Accessors ============================================================= @@ -128,6 +151,20 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //=========== Person To Display ========================================================================= + + @Override + public void setPersonToDisplay(Person personToDisplay) { + if (personToDisplay == null || addressBook.hasPerson(personToDisplay)) { + this.personToDisplay = personToDisplay; + } + } + + @Override + public Person getPersonToDisplay() { + return personToDisplay; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -140,9 +177,11 @@ public boolean equals(Object other) { } ModelManager otherModelManager = (ModelManager) other; + return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredPersons.equals(otherModelManager.filteredPersons) + && Objects.equals(personToDisplay, otherModelManager.personToDisplay); } } diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index e17d4d4a580..1da2ffb7fc9 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -90,6 +90,22 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0)); } + @Test + public void getPersonToDisplay_validPerson_success() { + // store current model in a temporary variable + Model temp = model; + + // set up Logic object with personToDisplay set as AMY + model.addPerson(AMY); + model = new ModelManager(model.getAddressBook(), model.getUserPrefs(), AMY); + setUp(); + + assertEquals(AMY, logic.getPersonToDisplay()); + + // reset model to old model after testing + model = temp; + } + /** * Executes the command and confirms that * - no exceptions are thrown
@@ -173,6 +189,7 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) Person expectedPerson = new PersonBuilder(AMY).emptyModuleList().build(); ModelManager expectedModel = new ModelManager(); expectedModel.addPerson(expectedPerson); + expectedModel.setPersonToDisplay(expectedPerson); assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java index 162a0c86031..bd44f49c202 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java @@ -32,6 +32,7 @@ public void execute_newPerson_success() { Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); expectedModel.addPerson(validPerson); + expectedModel.setPersonToDisplay(validPerson); assertCommandSuccess(new AddCommand(validPerson), model, String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)), diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 90e8253f48e..cee9c867cc2 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -42,6 +42,7 @@ public void execute_personAcceptedByModel_addSuccessful() throws Exception { assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)), commandResult.getFeedbackToUser()); assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); + assertEquals(validPerson, modelStub.personDisplayed); } @Test @@ -157,6 +158,16 @@ public ObservableList getFilteredPersonList() { public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + + @Override + public void setPersonToDisplay(Person personToDisplay) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Person getPersonToDisplay() { + throw new AssertionError("This method should not be called."); + } } /** @@ -182,6 +193,7 @@ public boolean hasPerson(Person person) { */ private class ModelStubAcceptingPersonAdded extends ModelStub { final ArrayList personsAdded = new ArrayList<>(); + private Person personDisplayed; @Override public boolean hasPerson(Person person) { @@ -193,6 +205,7 @@ public boolean hasPerson(Person person) { public void addPerson(Person person) { requireNonNull(person); personsAdded.add(person); + personDisplayed = person; } @Override diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 7cb5b09ed3b..3cf436e7bf7 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -69,6 +69,39 @@ public void execute_validStudentIdFilteredList_success() { assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); } + @Test + public void execute_personDisplayedDeleted_success() { + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + model.setPersonToDisplay(personToDelete); + StudentId studentIdToDelete = personToDelete.getStudentId(); + DeleteCommand deleteCommand = new DeleteCommand(studentIdToDelete); + + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, + Messages.format(personToDelete)); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deletePerson(personToDelete); + + assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_personDisplayedNotDeleted_success() { + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personToDisplay = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + model.setPersonToDisplay(personToDisplay); + StudentId studentIdToDelete = personToDelete.getStudentId(); + DeleteCommand deleteCommand = new DeleteCommand(studentIdToDelete); + + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, + Messages.format(personToDelete)); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), personToDisplay); + expectedModel.deletePerson(personToDelete); + + assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + } + @Test public void equals() { Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index d91af00b729..6cf96a24892 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -79,7 +79,7 @@ public void execute_noFieldSpecifiedUnfilteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)); - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(), editedPerson); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); } diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java new file mode 100644 index 00000000000..3f54e86e897 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -0,0 +1,107 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.model.person.StudentId; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code FindCommand}. + */ +public class FindCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validStudentIdUnfilteredList_success() { + Person personToFind = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + StudentId studentIdToFind = personToFind.getStudentId(); + FindCommand findCommand = new FindCommand(studentIdToFind); + + String expectedMessage = String.format(FindCommand.MESSAGE_FIND_PERSON_SUCCESS, + Messages.format(personToFind)); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.setPersonToDisplay(personToFind); + + assertCommandSuccess(findCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidStudentIdUnfilteredList_throwsCommandException() { + StudentId invalidStudentId = new StudentId("12345679"); + FindCommand findCommand = new FindCommand(invalidStudentId); + assertCommandFailure( + findCommand, model, String.format(FindCommand.MESSAGE_PERSON_NOT_FOUND, invalidStudentId)); + } + + @Test + public void execute_validStudentIdFilteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person personToFind = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + StudentId studentIdToFind = personToFind.getStudentId(); + FindCommand findCommand = new FindCommand(studentIdToFind); + + String expectedMessage = String.format(FindCommand.MESSAGE_FIND_PERSON_SUCCESS, + Messages.format(personToFind)); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), personToFind); + showPersonAtIndex(expectedModel, INDEX_FIRST_PERSON); + + assertCommandSuccess(findCommand, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person secondPerson = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + + StudentId firstStudentId = firstPerson.getStudentId(); + StudentId secondStudentId = secondPerson.getStudentId(); + + FindCommand findFirstCommand = new FindCommand(firstStudentId); + FindCommand findSecondCommand = new FindCommand(secondStudentId); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindCommand findFirstCommandCopy = new FindCommand(firstStudentId); + 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 toStringMethod() { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + StudentId targetStudentId = firstPerson.getStudentId(); + + FindCommand findCommand = new FindCommand(targetStudentId); + String expected = FindCommand.class.getCanonicalName() + "{targetStudentId=" + targetStudentId + "}"; + assertEquals(expected, findCommand.toString()); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 1d08eaa860f..7189a5b8b62 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -23,6 +23,7 @@ import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FilterCommand; +import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.GradeCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; @@ -142,6 +143,14 @@ public void parseCommand_grade() throws Exception { assertEquals(new GradeCommand(validStudentId, validModule, validGrade), command); } + @Test + public void parseCommand_find() throws Exception { + StudentId validStudentId = new StudentId(CommandTestUtil.VALID_STUDENTID_BOB); + FindCommand command = (FindCommand) parser.parseCommand( + FindCommand.COMMAND_WORD + " " + validStudentId); + assertEquals(new FindCommand(validStudentId), command); + } + @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/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java new file mode 100644 index 00000000000..39d051d64f0 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.CommandTestUtil; +import seedu.address.logic.commands.FindCommand; +import seedu.address.model.person.StudentId; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside the FindCommand code. For example, inputs "1" and "1 abc" take the + * same path through the FindCommand, 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 FindCommandParserTest { + + private FindCommandParser parser = new FindCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + StudentId validStudentId = new StudentId(CommandTestUtil.VALID_STUDENTID_BOB); + assertParseSuccess(parser, validStudentId.toString(), new FindCommand(validStudentId)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // multiple arguments + assertParseFailure(parser, "11111111 abc", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + + // invalid studentId + assertParseFailure(parser, "1", StudentId.MESSAGE_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..453d0010a31 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BENSON; @@ -27,6 +27,7 @@ public void constructor() { assertEquals(new UserPrefs(), modelManager.getUserPrefs()); assertEquals(new GuiSettings(), modelManager.getGuiSettings()); assertEquals(new AddressBook(), new AddressBook(modelManager.getAddressBook())); + assertEquals(null, modelManager.getPersonToDisplay()); } @Test @@ -93,6 +94,41 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); } + @Test + public void setPersonToDisplay_validPerson_success() { + modelManager.addPerson(ALICE); + modelManager.setPersonToDisplay(ALICE); + + // same person -> returns true + assertEquals(new ModelManager(modelManager.getAddressBook(), modelManager.getUserPrefs(), ALICE), modelManager); + + // different person -> returns false + assertNotEquals(new ModelManager(modelManager.getAddressBook(), modelManager.getUserPrefs(), BENSON), + modelManager); + } + + @Test + public void setPersonToDisplay_invalidPerson_nothingHappens() { + modelManager.addPerson(ALICE); + modelManager.setPersonToDisplay(ALICE); + modelManager.setPersonToDisplay(BENSON); + + assertEquals(new ModelManager(modelManager.getAddressBook(), + modelManager.getUserPrefs(), + ALICE), modelManager); + } + + @Test + public void getPersonToDisplay_personToDisplayEqualToPersonSet_returnsTrue() { + modelManager.addPerson(ALICE); + modelManager = new ModelManager(modelManager.getAddressBook(), modelManager.getUserPrefs(), ALICE); + // same person -> returns true + assertEquals(ALICE, modelManager.getPersonToDisplay()); + + // different person -> returns false + assertNotEquals(BENSON, modelManager.getPersonToDisplay()); + } + @Test public void equals() { AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); @@ -104,6 +140,10 @@ public void equals() { ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs); assertTrue(modelManager.equals(modelManagerCopy)); + modelManager = new ModelManager(addressBook, userPrefs, ALICE); + modelManagerCopy = new ModelManager(addressBook, userPrefs, ALICE); + assertTrue(modelManager.equals(modelManagerCopy)); + // same object -> returns true assertTrue(modelManager.equals(modelManager)); @@ -121,8 +161,11 @@ public void equals() { modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + // different personToDisplay -> returns false + assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + // resets modelManager to initial state for upcoming tests - modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + modelManager = new ModelManager(addressBook, userPrefs); // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs();