From 35382690b61e17cf5d9d8ed879770c6392c4bfcc Mon Sep 17 00:00:00 2001 From: Han Bin Date: Thu, 3 Oct 2024 20:48:12 +0800 Subject: [PATCH 01/13] Fix formatting issues in DeveloperGuide.md Fix minor formatting issues. --- docs/DeveloperGuide.md | 222 ++++++++++++++++++++++------------------- 1 file changed, 122 insertions(+), 100 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index d42669b0107..fcb16fce70b 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -290,54 +290,54 @@ High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have Those without any stars are user stories that were considered but will not be implemented at this time. -| Priority | As a …​ | I want to …​ | So that…​ | -|:--------:|---------------------------|----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `* * *` | user | add tag(s) to each contact based on created tags such as florist, musician etc. | I can easily understand the group this person belongs to | -| `* * *` | user | add a phone number associated with each contact | I can easily find the contact information for each contact. | -| `* * *` | user | add the address associated with each contact | I can easily find the address of each contact. | -| `* * *` | user | filter contacts by tag | I can quickly see all the groups under the same tag, and find the right vendor based on the type of services provided | -| `* * *` | user | add new contacts into WedLinker | I can store the contact details of new contacts | -| `* * *` | user | delete contacts that are no longer needed | I can remove unnecessary contacts and have a more organised address book | -| `* * *` | user | search for contact by name | I can find specific contacts that I am looking for | -| `* * *` | user | create tags | I can have special categories for non traditional vendors | -| `* *` | user | edit information such as the contact number and address of each contact | all contacts have the most updated information | -| `* *` | user | clear all the contacts in the system | I can clear all my contacts quickly without having to individually delete them if I want to add in a completely new set of contacts | -| `* *` | careless user | receive a prompt that requires me to key in a confirmation that I want to delete a contact or clear the address book | I will not lose all my contacts when I accidentally type delete/ clear | -| `* *` | user | assign each guest contact its dietary requirements status | I can track the dietary requirement of each guest. | -| `* *` | user | sort contacts by alphabetical order | I can easily find the contacts required in a large address book. | -| `* *` | user | assign additional information for each contact | I can include important notes that may not fit into other categories, such as reminders for what the contact might need | -| `* *` | first-time user | see some sample contacts already available in the app | I can try out the different features without needing to add my own data (e.g allocating people to wedding, allocating task to contacts) | -| `* *` | careless, first-time user | reload the sample contacts into the app | I can continue trying out different features without needing to add my own data in case I accidentally cleared the contacts | -| `* *` | first-time user | see a help message showing all the commands/feature I can use | I can try out all the different features by referring to the message | -| `*` | user | assign tasks to contacts | I can track which tasks have been assigned to each contact. | -| `*` | user | update the status of tasks of contacts | I can track the status of completion of the tasks assigned to contacts | -| `*` | user | add a tag to each guest indicating their table number | track the table each guest is seated at | -| `*` | user | key in the table number and get the list of guests seated at that table | I can quickly identify all the groups seated at one table | -| `*` | user | assign a rating out of 5 to each vendor | I can track the experience with this vendor for future reference | -| `*` | busy user | add multiple wedding events | I can track contacts for multiple weddings at once | -| `*` | busy user | tag each contact to a wedding | I can easily see which contacts are relevant to which wedding | -| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled | -| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled | -| `*` | user | filter contacts by wedding | I can keep track of which contacts are relevant for each wedding | -| `*` | user | send out (standardised formatted) information (text/email) from the application | I can efficiently send out information without any mistakes | -| `*` | user | share the contact details to relevant third-parties for bookings (eg: venue bookings, suit/dress rental, etc.) | I can easily send out all relevant information (including dietary restriction, and other tags) to all the third-parties | -| `*` | user | exclude tags from search and filter | I can focus on contacts that are relevant to certain events or requirements without being overwhelmed by unnecessary information | -| `*` | busy user | autocomplete existing tags when user is inputting tag information | I can quickly assign roles for people that might be working with others I have already input into the system and not have to type the same roles in multiple times | -| `*` | user | assign availability to vendors | I can check who will be available for a particular wedding | -| `*` | user | filter availability of vendors | I can easily find vendors that can cater to a wedding | -| `*` | user | store multiple contact methods | I can contact the vendors through different means | -| `*` | user | re-assign tasks to another contact | I can account for vendors suddenly being unavailable | -| `*` | user | set reminders for tasks to different contacts | I can easily track and follow up with clients and vendors for deliverables | -| `*` | user | see a list of all tasks and reminders I have assigned to contacts in its own window | I can quickly and easily see what my earliest priorities are and act on them quickly | -| `*` | user | see a calendar view of tasks, reminders, and weddings I have assigned | I can see the whole timelines of my planned weddings and see how much time there is between tasks | -| `*` | user | set privacy setting for different contacts | I can keep personal and sensitive information private when sharing address book | -| `*` | forgetful user | create links between different contacts, such as assigning a vendor to a bride or groom in a wedding | I can easily navigate from key stakeholders in the wedding that I remember better to vendors who I might not remember as well | -| `*` | user | add certain vendors as favorites | I can remember which vendors performed well and see if they are favorites | -| `*` | user | access a list of all my favorite vendors | I can easily check who the best vendors were that I previously engaged with | -| | user | generate a checklist of all the contacts for a particular wedding, grouped by roles | I can keep track of who is meant to be present at the wedding | -| | user | assign a time for each contact for when they are meant to arrive | I can easily keep track of which people are on time and check who to contact in case they have not arrived yet | -| | user | attach extra documents as a file to various contacts | I can store all the information in one place, eg. Invoices from a vendor | -| | user | categorize tasks based on its nature (e.g. procurement, arrangement) | I can view tasks in a more organised manner | +| Priority | As a …​ | I want to …​ | So that…​ | +|:--------:|---------------------------|----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `* * *` | user | add tag(s) to each contact based on created tags such as florist, musician etc. | I can easily understand the group this person belongs to. | +| `* * *` | user | add a phone number associated with each contact | I can easily find the contact information for each contact. | +| `* * *` | user | add the address associated with each contact | I can easily find the address of each contact. | +| `* * *` | user | filter contacts by tag | I can quickly see all the groups under the same tag, and find the right vendor based on the type of services provided. | +| `* * *` | user | add new contacts into WedLinker | I can store the contact details of new contacts. | +| `* * *` | user | delete contacts that are no longer needed | I can remove unnecessary contacts and have a more organised address book. | +| `* * *` | user | search for contact by name | I can find specific contacts that I am looking for. | +| `* * *` | user | create tags | I can have special categories for non traditional vendors. | +| `* *` | user | edit information such as the contact number and address of each contact | all contacts have the most updated information. | +| `* *` | user | clear all the contacts in the system | I can clear all my contacts quickly without having to individually delete them if I want to add in a completely new set of contacts. | +| `* *` | careless user | receive a prompt that requires me to key in a confirmation that I want to delete a contact or clear the address book | I will not lose all my contacts when I accidentally type delete/ clear. | +| `* *` | user | assign each guest contact its dietary requirements status | I can track the dietary requirement of each guest. | +| `* *` | user | sort contacts by alphabetical order | I can easily find the contacts required in a large address book. | +| `* *` | user | assign additional information for each contact | I can include important notes that may not fit into other categories, such as reminders for what the contact might need. | +| `* *` | first-time user | see some sample contacts already available in the app | I can try out the different features without needing to add my own data (e.g allocating people to wedding, allocating task to contacts). | +| `* *` | careless, first-time user | reload the sample contacts into the app | I can continue trying out different features without needing to add my own data in case I accidentally cleared the contacts. | +| `* *` | first-time user | see a help message showing all the commands/feature I can use | I can try out all the different features by referring to the message. | +| `*` | user | assign tasks to contacts | I can track which tasks have been assigned to each contact. | +| `*` | user | update the status of tasks of contacts | I can track the status of completion of the tasks assigned to contacts. | +| `*` | user | add a tag to each guest indicating their table number | track the table each guest is seated at. | +| `*` | user | key in the table number and get the list of guests seated at that table | I can quickly identify all the groups seated at one table. | +| `*` | user | assign a rating out of 5 to each vendor | I can track the experience with this vendor for future reference. | +| `*` | busy user | add multiple wedding events | I can track contacts for multiple weddings at once. | +| `*` | busy user | tag each contact to a wedding | I can easily see which contacts are relevant to which wedding. | +| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled. | +| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled. | +| `*` | user | filter contacts by wedding | I can keep track of which contacts are relevant for each wedding. | +| `*` | user | send out (standardised formatted) information (text/email) from the application | I can efficiently send out information without any mistakes. | +| `*` | user | share the contact details to relevant third-parties for bookings (eg: venue bookings, suit/dress rental, etc.) | I can easily send out all relevant information (including dietary restriction, and other tags) to all the third-parties. | +| `*` | user | exclude tags from search and filter | I can focus on contacts that are relevant to certain events or requirements without being overwhelmed by unnecessary information. | +| `*` | busy user | autocomplete existing tags when user is inputting tag information | I can quickly assign roles for people that might be working with others I have already input into the system and not have to type the same roles in multiple times. | +| `*` | user | assign availability to vendors | I can check who will be available for a particular wedding. | +| `*` | user | filter availability of vendors | I can easily find vendors that can cater to a wedding. | +| `*` | user | store multiple contact methods | I can contact the vendors through different means. | +| `*` | user | re-assign tasks to another contact | I can account for vendors suddenly being unavailable. | +| `*` | user | set reminders for tasks to different contacts | I can easily track and follow up with clients and vendors for deliverables. | +| `*` | user | see a list of all tasks and reminders I have assigned to contacts in its own window | I can quickly and easily see what my earliest priorities are and act on them quickly. | +| `*` | user | see a calendar view of tasks, reminders, and weddings I have assigned | I can see the whole timelines of my planned weddings and see how much time there is between tasks. | +| `*` | user | set privacy setting for different contacts | I can keep personal and sensitive information private when sharing address book. | +| `*` | forgetful user | create links between different contacts, such as assigning a vendor to a bride or groom in a wedding | I can easily navigate from key stakeholders in the wedding that I remember better to vendors who I might not remember as well. | +| `*` | user | add certain vendors as favorites | I can remember which vendors performed well and see if they are favorites. | +| `*` | user | access a list of all my favorite vendors | I can easily check who the best vendors were that I previously engaged with. | +| | user | generate a checklist of all the contacts for a particular wedding, grouped by roles | I can keep track of who is meant to be present at the wedding. | +| | user | assign a time for each contact for when they are meant to arrive | I can easily keep track of which people are on time and check who to contact in case they have not arrived yet. | +| | user | attach extra documents as a file to various contacts | I can store all the information in one place, eg. Invoices from a vendor. | +| | user | categorize tasks based on its nature (e.g. procurement, arrangement) | I can view tasks in a more organised manner. | ### Use cases @@ -347,16 +347,18 @@ Those without any stars are user stories that were considered but will not be im > > Use Cases beginning with 'UCSH' cover non-core AddressBook functionality. -**Use case: UC01 List all contacts** +**Use case: UC01 List all Contacts** **MSS** 1. User issues the list command. 2. The system retrieves and displays the list of all contacts to the user. + Use case ends. -**Use case: UC02 Add a contact** + +**Use case: UC02 Add a Contact** **MSS** @@ -377,31 +379,31 @@ Those without any stars are user stories that were considered but will not be im * 1b. The system detects a phone number input error (invalid format). * 1b1. The system displays an error message stating the correct format. - Use case ends. + Use case ends. * 1c. The system detects an address input error (too long). * 1c1. The system displays an error message stating the maximum length. - Use case ends. + Use case ends. * 1d. The system detects an email input error (invalid format). * 1d1. The system displays an error message stating the correct format. - Use case ends. + Use case ends. * 1e. The system detects a duplicate phone number error. * 1e1. The system displays an error message mentioning the existence of a duplicate phone number. - Use case ends. + Use case ends. * 1f. The system detects an invalid tag input. * 1f1. The system displays an error message stating the tag is invalid. - Use case ends. + Use case ends. @@ -410,7 +412,6 @@ Those without any stars are user stories that were considered but will not be im **Guarantees:** * No duplicate phone numbers will be stored in two different contacts. - **MSS** 1. User lists all contacts (UC01). @@ -430,19 +431,19 @@ Those without any stars are user stories that were considered but will not be im * 2a. The system detects an invalid contact index. * 2a1. The system displays an error message stating the contact index is invalid. - Use case resumes at step 1. + Use case resumes at step 1. * 2b. The system detects a phone number input error (invalid format). * 2b1. The system displays an error message stating the correct format. - Use case resumes at step 1. + Use case resumes at step 1. * 2c. The system detects a duplicate phone number error. * 2c1. The system displays an error message mentioning the existence of a duplicate phone number. - Use case resumes at step 1. + Use case resumes at step 1. @@ -452,28 +453,28 @@ Those without any stars are user stories that were considered but will not be im 1. User lists all contacts (UC01). 2. User requests to add address for a contact with the corresponding details. -3. The system adds the address to the contact and displays a success message +3. The system adds the address to the contact and displays a success message. 4. The system displays the updated contact information in the address book. - Use case ends. + Use case ends. **Extensions** * 1a. The list is empty. - Use case ends. + Use case ends. * 2a. The system detects an invalid contact index. * 2a1. The system displays an error message stating the contact index is invalid. - Use case resumes at step 1. + Use case resumes at step 1. * 2b. The system detects an address input error (too long). - * 2b1. The system displays an error message stating the maximum length + * 2b1. The system displays an error message stating the maximum length. - Use case resumes at step 1. + Use case resumes at step 1. @@ -483,10 +484,10 @@ Those without any stars are user stories that were considered but will not be im 1. User lists all contacts (UC01). 2. User requests to add email address for a contact with the corresponding details. -3. The system adds the email address to the contact and displays a success message +3. The system adds the email address to the contact and displays a success message. 4. The system displays the updated contact information in the address book. - Use case ends. + Use case ends. **Extensions** @@ -498,22 +499,27 @@ Those without any stars are user stories that were considered but will not be im * 2a. The system detects an invalid contact index. * 2a1. The system displays an error message stating the contact index is invalid. - Use case resumes at step 1. + Use case resumes at step 1. * 2b. The system detects an email input error (invalid format). * 2b1. The system displays an error message stating the correct format. - Use case resumes at step 1. + Use case resumes at step 1. -**Use case: UC06 Search for contacts by Name** + +**Use case: UC06 Search for Contacts by Name** **MSS** 1. User searches for the contact by name. 2. System shows a list of contacts containing the name. + Use case ends. + + + **Use case: UC07 Filter by Tag** **MSS** @@ -530,6 +536,7 @@ Those without any stars are user stories that were considered but will not be im Use case ends. + **Use case: UC08 Create Tags** **MSS** @@ -548,6 +555,7 @@ Those without any stars are user stories that were considered but will not be im Use case ends. + **Use case: UC09 Tagging a contact with a specified tag** **MSS** @@ -567,7 +575,7 @@ Those without any stars are user stories that were considered but will not be im * 2a. System detects that the tag does not exist. - * 2a1. System creates a new tag (UC08) + * 2a1. System creates a new tag (UC08). Use case resumes at step 3. @@ -577,6 +585,8 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. + + **Use case: UC10 Delete Contact** **MSS** @@ -601,13 +611,14 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. + **Use case: UCSH01 Edit details for a contact** **MSS** -1. User lists all contacts (UC01) -2. User requests to edit the details of a person and specifies what they want to change the details to -3. AddressBook changes the existing details to the specified details and shows list of persons with new details +1. User lists all contacts (UC01). +2. User requests to edit the details of a person and specifies what they want to change the details to. +3. AddressBook changes the existing details to the specified details and shows list of persons with new details. **Extensions** @@ -644,6 +655,7 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. + **Use case: UCSH02 Clear all contacts from the system** **Guarantees:** @@ -651,8 +663,8 @@ Those without any stars are user stories that were considered but will not be im **MSS** -1. User requests to clear all contact -2. System deletes all contacts and shows a blank list of persons +1. User requests to clear all contact. +2. System deletes all contacts and shows a blank list of persons. Use case ends. @@ -660,11 +672,11 @@ Those without any stars are user stories that were considered but will not be im **MSS** -1. User lists all contacts (UC01) -2. User requests to delete a contact -3. System gives a prompt to confirm whether the user wants to delete the contact -4. User confirms they want to delete the contact -5. System deletes the contact and shows the updated list of persons +1. User lists all contacts (UC01). +2. User requests to delete a contact. +3. System gives a prompt to confirm whether the user wants to delete the contact. +4. User confirms they want to delete the contact. +5. System deletes the contact and shows the updated list of persons. Use case ends. @@ -689,14 +701,15 @@ Those without any stars are user stories that were considered but will not be im Use case ends. + **Use case: UCSH04 Receive a prompt when clearing the system** **MSS** -1. User requests to clear the system of all persons -2. System gives a prompt to confirm whether the user wants to clear all contacts -3. User confirms they want to clear all contacts -4. System deletes all contacts and shows a blank list of persons +1. User requests to clear the system of all persons. +2. System gives a prompt to confirm whether the user wants to clear all contacts. +3. User confirms they want to clear all contacts. +4. System deletes all contacts and shows a blank list of persons. Use case ends. @@ -709,13 +722,14 @@ Those without any stars are user stories that were considered but will not be im Use case ends. + **Use case: UCSH05 Assign dietary requirement to contact** **MSS** -1. User lists all contacts (UC01) -2. User requests to add a dietary status to the person -3. System adds the dietary status to the contact and shows list of persons with new details +1. User lists all contacts (UC01). +2. User requests to add a dietary status to the person. +3. System adds the dietary status to the contact and shows list of persons with new details. Use case ends. **Extensions** @@ -731,12 +745,13 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. + **Use case: UCSH06 Sort contacts in alphabetical order** **MSS** -1. User requests to show a list of persons sorted alphabetically -2. System shows the list of persons sorted in alphabetical order +1. User requests to show a list of persons sorted alphabetically. +2. System shows the list of persons sorted in alphabetical order. Use case ends. @@ -746,13 +761,15 @@ Those without any stars are user stories that were considered but will not be im Use case ends. + + **Use case: UCSH07 Add additional information for a person** **MSS** -1. User lists all contacts (UC01) -2. User requests to add additional information for a person -3. System adds the additional information to the contact and shows list of persons with new details +1. User lists all contacts (UC01). +2. User requests to add additional information for a person. +3. System adds the additional information to the contact and shows list of persons with new details. Use case ends. @@ -779,34 +796,39 @@ Those without any stars are user stories that were considered but will not be im **Use case: UCSH08 See sample contacts in the system before starting to modify it** -Preconditions: User has not added or edited contacts previously +Preconditions: User has not added or edited contacts previously. **MSS** -1. User opens the application -2. System shows a list of sample contacts +1. User opens the application. +2. System shows a list of sample contacts. Use case ends. + + **Use case: UCSH09 Reload sample contacts in the system** **MSS** -1. User requests to reload sample contacts into the system -2. System deletes all current persons in the system and shows a list of sample contacts +1. User requests to reload sample contacts into the system. +2. System deletes all current persons in the system and shows a list of sample contacts. Use case ends. + + **Use case: UCSH10 See a list of all possible commands** **MSS** -1. User requests to see a list of all possible commands they can use in the system -2. System shows a list of commands with their corresponding input format +1. User requests to see a list of all possible commands they can use in the system. +2. System shows a list of commands with their corresponding input format. Use case ends. + ### Non-Functional Requirements **Performance Requirements** From 26aee2c5044e3a3fb0056adaf22a102e2238dc1a Mon Sep 17 00:00:00 2001 From: Han Bin Date: Thu, 3 Oct 2024 20:48:46 +0800 Subject: [PATCH 02/13] Revert "Fix formatting issues in DeveloperGuide.md" This reverts commit 35382690b61e17cf5d9d8ed879770c6392c4bfcc. --- docs/DeveloperGuide.md | 222 +++++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 122 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index fcb16fce70b..d42669b0107 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -290,54 +290,54 @@ High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have Those without any stars are user stories that were considered but will not be implemented at this time. -| Priority | As a …​ | I want to …​ | So that…​ | -|:--------:|---------------------------|----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `* * *` | user | add tag(s) to each contact based on created tags such as florist, musician etc. | I can easily understand the group this person belongs to. | -| `* * *` | user | add a phone number associated with each contact | I can easily find the contact information for each contact. | -| `* * *` | user | add the address associated with each contact | I can easily find the address of each contact. | -| `* * *` | user | filter contacts by tag | I can quickly see all the groups under the same tag, and find the right vendor based on the type of services provided. | -| `* * *` | user | add new contacts into WedLinker | I can store the contact details of new contacts. | -| `* * *` | user | delete contacts that are no longer needed | I can remove unnecessary contacts and have a more organised address book. | -| `* * *` | user | search for contact by name | I can find specific contacts that I am looking for. | -| `* * *` | user | create tags | I can have special categories for non traditional vendors. | -| `* *` | user | edit information such as the contact number and address of each contact | all contacts have the most updated information. | -| `* *` | user | clear all the contacts in the system | I can clear all my contacts quickly without having to individually delete them if I want to add in a completely new set of contacts. | -| `* *` | careless user | receive a prompt that requires me to key in a confirmation that I want to delete a contact or clear the address book | I will not lose all my contacts when I accidentally type delete/ clear. | -| `* *` | user | assign each guest contact its dietary requirements status | I can track the dietary requirement of each guest. | -| `* *` | user | sort contacts by alphabetical order | I can easily find the contacts required in a large address book. | -| `* *` | user | assign additional information for each contact | I can include important notes that may not fit into other categories, such as reminders for what the contact might need. | -| `* *` | first-time user | see some sample contacts already available in the app | I can try out the different features without needing to add my own data (e.g allocating people to wedding, allocating task to contacts). | -| `* *` | careless, first-time user | reload the sample contacts into the app | I can continue trying out different features without needing to add my own data in case I accidentally cleared the contacts. | -| `* *` | first-time user | see a help message showing all the commands/feature I can use | I can try out all the different features by referring to the message. | -| `*` | user | assign tasks to contacts | I can track which tasks have been assigned to each contact. | -| `*` | user | update the status of tasks of contacts | I can track the status of completion of the tasks assigned to contacts. | -| `*` | user | add a tag to each guest indicating their table number | track the table each guest is seated at. | -| `*` | user | key in the table number and get the list of guests seated at that table | I can quickly identify all the groups seated at one table. | -| `*` | user | assign a rating out of 5 to each vendor | I can track the experience with this vendor for future reference. | -| `*` | busy user | add multiple wedding events | I can track contacts for multiple weddings at once. | -| `*` | busy user | tag each contact to a wedding | I can easily see which contacts are relevant to which wedding. | -| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled. | -| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled. | -| `*` | user | filter contacts by wedding | I can keep track of which contacts are relevant for each wedding. | -| `*` | user | send out (standardised formatted) information (text/email) from the application | I can efficiently send out information without any mistakes. | -| `*` | user | share the contact details to relevant third-parties for bookings (eg: venue bookings, suit/dress rental, etc.) | I can easily send out all relevant information (including dietary restriction, and other tags) to all the third-parties. | -| `*` | user | exclude tags from search and filter | I can focus on contacts that are relevant to certain events or requirements without being overwhelmed by unnecessary information. | -| `*` | busy user | autocomplete existing tags when user is inputting tag information | I can quickly assign roles for people that might be working with others I have already input into the system and not have to type the same roles in multiple times. | -| `*` | user | assign availability to vendors | I can check who will be available for a particular wedding. | -| `*` | user | filter availability of vendors | I can easily find vendors that can cater to a wedding. | -| `*` | user | store multiple contact methods | I can contact the vendors through different means. | -| `*` | user | re-assign tasks to another contact | I can account for vendors suddenly being unavailable. | -| `*` | user | set reminders for tasks to different contacts | I can easily track and follow up with clients and vendors for deliverables. | -| `*` | user | see a list of all tasks and reminders I have assigned to contacts in its own window | I can quickly and easily see what my earliest priorities are and act on them quickly. | -| `*` | user | see a calendar view of tasks, reminders, and weddings I have assigned | I can see the whole timelines of my planned weddings and see how much time there is between tasks. | -| `*` | user | set privacy setting for different contacts | I can keep personal and sensitive information private when sharing address book. | -| `*` | forgetful user | create links between different contacts, such as assigning a vendor to a bride or groom in a wedding | I can easily navigate from key stakeholders in the wedding that I remember better to vendors who I might not remember as well. | -| `*` | user | add certain vendors as favorites | I can remember which vendors performed well and see if they are favorites. | -| `*` | user | access a list of all my favorite vendors | I can easily check who the best vendors were that I previously engaged with. | -| | user | generate a checklist of all the contacts for a particular wedding, grouped by roles | I can keep track of who is meant to be present at the wedding. | -| | user | assign a time for each contact for when they are meant to arrive | I can easily keep track of which people are on time and check who to contact in case they have not arrived yet. | -| | user | attach extra documents as a file to various contacts | I can store all the information in one place, eg. Invoices from a vendor. | -| | user | categorize tasks based on its nature (e.g. procurement, arrangement) | I can view tasks in a more organised manner. | +| Priority | As a …​ | I want to …​ | So that…​ | +|:--------:|---------------------------|----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `* * *` | user | add tag(s) to each contact based on created tags such as florist, musician etc. | I can easily understand the group this person belongs to | +| `* * *` | user | add a phone number associated with each contact | I can easily find the contact information for each contact. | +| `* * *` | user | add the address associated with each contact | I can easily find the address of each contact. | +| `* * *` | user | filter contacts by tag | I can quickly see all the groups under the same tag, and find the right vendor based on the type of services provided | +| `* * *` | user | add new contacts into WedLinker | I can store the contact details of new contacts | +| `* * *` | user | delete contacts that are no longer needed | I can remove unnecessary contacts and have a more organised address book | +| `* * *` | user | search for contact by name | I can find specific contacts that I am looking for | +| `* * *` | user | create tags | I can have special categories for non traditional vendors | +| `* *` | user | edit information such as the contact number and address of each contact | all contacts have the most updated information | +| `* *` | user | clear all the contacts in the system | I can clear all my contacts quickly without having to individually delete them if I want to add in a completely new set of contacts | +| `* *` | careless user | receive a prompt that requires me to key in a confirmation that I want to delete a contact or clear the address book | I will not lose all my contacts when I accidentally type delete/ clear | +| `* *` | user | assign each guest contact its dietary requirements status | I can track the dietary requirement of each guest. | +| `* *` | user | sort contacts by alphabetical order | I can easily find the contacts required in a large address book. | +| `* *` | user | assign additional information for each contact | I can include important notes that may not fit into other categories, such as reminders for what the contact might need | +| `* *` | first-time user | see some sample contacts already available in the app | I can try out the different features without needing to add my own data (e.g allocating people to wedding, allocating task to contacts) | +| `* *` | careless, first-time user | reload the sample contacts into the app | I can continue trying out different features without needing to add my own data in case I accidentally cleared the contacts | +| `* *` | first-time user | see a help message showing all the commands/feature I can use | I can try out all the different features by referring to the message | +| `*` | user | assign tasks to contacts | I can track which tasks have been assigned to each contact. | +| `*` | user | update the status of tasks of contacts | I can track the status of completion of the tasks assigned to contacts | +| `*` | user | add a tag to each guest indicating their table number | track the table each guest is seated at | +| `*` | user | key in the table number and get the list of guests seated at that table | I can quickly identify all the groups seated at one table | +| `*` | user | assign a rating out of 5 to each vendor | I can track the experience with this vendor for future reference | +| `*` | busy user | add multiple wedding events | I can track contacts for multiple weddings at once | +| `*` | busy user | tag each contact to a wedding | I can easily see which contacts are relevant to which wedding | +| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled | +| `*` | user | assign dates to a wedding | I can keep track of when different weddings are scheduled | +| `*` | user | filter contacts by wedding | I can keep track of which contacts are relevant for each wedding | +| `*` | user | send out (standardised formatted) information (text/email) from the application | I can efficiently send out information without any mistakes | +| `*` | user | share the contact details to relevant third-parties for bookings (eg: venue bookings, suit/dress rental, etc.) | I can easily send out all relevant information (including dietary restriction, and other tags) to all the third-parties | +| `*` | user | exclude tags from search and filter | I can focus on contacts that are relevant to certain events or requirements without being overwhelmed by unnecessary information | +| `*` | busy user | autocomplete existing tags when user is inputting tag information | I can quickly assign roles for people that might be working with others I have already input into the system and not have to type the same roles in multiple times | +| `*` | user | assign availability to vendors | I can check who will be available for a particular wedding | +| `*` | user | filter availability of vendors | I can easily find vendors that can cater to a wedding | +| `*` | user | store multiple contact methods | I can contact the vendors through different means | +| `*` | user | re-assign tasks to another contact | I can account for vendors suddenly being unavailable | +| `*` | user | set reminders for tasks to different contacts | I can easily track and follow up with clients and vendors for deliverables | +| `*` | user | see a list of all tasks and reminders I have assigned to contacts in its own window | I can quickly and easily see what my earliest priorities are and act on them quickly | +| `*` | user | see a calendar view of tasks, reminders, and weddings I have assigned | I can see the whole timelines of my planned weddings and see how much time there is between tasks | +| `*` | user | set privacy setting for different contacts | I can keep personal and sensitive information private when sharing address book | +| `*` | forgetful user | create links between different contacts, such as assigning a vendor to a bride or groom in a wedding | I can easily navigate from key stakeholders in the wedding that I remember better to vendors who I might not remember as well | +| `*` | user | add certain vendors as favorites | I can remember which vendors performed well and see if they are favorites | +| `*` | user | access a list of all my favorite vendors | I can easily check who the best vendors were that I previously engaged with | +| | user | generate a checklist of all the contacts for a particular wedding, grouped by roles | I can keep track of who is meant to be present at the wedding | +| | user | assign a time for each contact for when they are meant to arrive | I can easily keep track of which people are on time and check who to contact in case they have not arrived yet | +| | user | attach extra documents as a file to various contacts | I can store all the information in one place, eg. Invoices from a vendor | +| | user | categorize tasks based on its nature (e.g. procurement, arrangement) | I can view tasks in a more organised manner | ### Use cases @@ -347,18 +347,16 @@ Those without any stars are user stories that were considered but will not be im > > Use Cases beginning with 'UCSH' cover non-core AddressBook functionality. -**Use case: UC01 List all Contacts** +**Use case: UC01 List all contacts** **MSS** 1. User issues the list command. 2. The system retrieves and displays the list of all contacts to the user. - Use case ends. - -**Use case: UC02 Add a Contact** +**Use case: UC02 Add a contact** **MSS** @@ -379,31 +377,31 @@ Those without any stars are user stories that were considered but will not be im * 1b. The system detects a phone number input error (invalid format). * 1b1. The system displays an error message stating the correct format. - Use case ends. + Use case ends. * 1c. The system detects an address input error (too long). * 1c1. The system displays an error message stating the maximum length. - Use case ends. + Use case ends. * 1d. The system detects an email input error (invalid format). * 1d1. The system displays an error message stating the correct format. - Use case ends. + Use case ends. * 1e. The system detects a duplicate phone number error. * 1e1. The system displays an error message mentioning the existence of a duplicate phone number. - Use case ends. + Use case ends. * 1f. The system detects an invalid tag input. * 1f1. The system displays an error message stating the tag is invalid. - Use case ends. + Use case ends. @@ -412,6 +410,7 @@ Those without any stars are user stories that were considered but will not be im **Guarantees:** * No duplicate phone numbers will be stored in two different contacts. + **MSS** 1. User lists all contacts (UC01). @@ -431,19 +430,19 @@ Those without any stars are user stories that were considered but will not be im * 2a. The system detects an invalid contact index. * 2a1. The system displays an error message stating the contact index is invalid. - Use case resumes at step 1. + Use case resumes at step 1. * 2b. The system detects a phone number input error (invalid format). * 2b1. The system displays an error message stating the correct format. - Use case resumes at step 1. + Use case resumes at step 1. * 2c. The system detects a duplicate phone number error. * 2c1. The system displays an error message mentioning the existence of a duplicate phone number. - Use case resumes at step 1. + Use case resumes at step 1. @@ -453,28 +452,28 @@ Those without any stars are user stories that were considered but will not be im 1. User lists all contacts (UC01). 2. User requests to add address for a contact with the corresponding details. -3. The system adds the address to the contact and displays a success message. +3. The system adds the address to the contact and displays a success message 4. The system displays the updated contact information in the address book. - Use case ends. + Use case ends. **Extensions** * 1a. The list is empty. - Use case ends. + Use case ends. * 2a. The system detects an invalid contact index. * 2a1. The system displays an error message stating the contact index is invalid. - Use case resumes at step 1. + Use case resumes at step 1. * 2b. The system detects an address input error (too long). - * 2b1. The system displays an error message stating the maximum length. + * 2b1. The system displays an error message stating the maximum length - Use case resumes at step 1. + Use case resumes at step 1. @@ -484,10 +483,10 @@ Those without any stars are user stories that were considered but will not be im 1. User lists all contacts (UC01). 2. User requests to add email address for a contact with the corresponding details. -3. The system adds the email address to the contact and displays a success message. +3. The system adds the email address to the contact and displays a success message 4. The system displays the updated contact information in the address book. - Use case ends. + Use case ends. **Extensions** @@ -499,27 +498,22 @@ Those without any stars are user stories that were considered but will not be im * 2a. The system detects an invalid contact index. * 2a1. The system displays an error message stating the contact index is invalid. - Use case resumes at step 1. + Use case resumes at step 1. * 2b. The system detects an email input error (invalid format). * 2b1. The system displays an error message stating the correct format. - Use case resumes at step 1. + Use case resumes at step 1. - -**Use case: UC06 Search for Contacts by Name** +**Use case: UC06 Search for contacts by Name** **MSS** 1. User searches for the contact by name. 2. System shows a list of contacts containing the name. - Use case ends. - - - **Use case: UC07 Filter by Tag** **MSS** @@ -536,7 +530,6 @@ Those without any stars are user stories that were considered but will not be im Use case ends. - **Use case: UC08 Create Tags** **MSS** @@ -555,7 +548,6 @@ Those without any stars are user stories that were considered but will not be im Use case ends. - **Use case: UC09 Tagging a contact with a specified tag** **MSS** @@ -575,7 +567,7 @@ Those without any stars are user stories that were considered but will not be im * 2a. System detects that the tag does not exist. - * 2a1. System creates a new tag (UC08). + * 2a1. System creates a new tag (UC08) Use case resumes at step 3. @@ -585,8 +577,6 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. - - **Use case: UC10 Delete Contact** **MSS** @@ -611,14 +601,13 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. - **Use case: UCSH01 Edit details for a contact** **MSS** -1. User lists all contacts (UC01). -2. User requests to edit the details of a person and specifies what they want to change the details to. -3. AddressBook changes the existing details to the specified details and shows list of persons with new details. +1. User lists all contacts (UC01) +2. User requests to edit the details of a person and specifies what they want to change the details to +3. AddressBook changes the existing details to the specified details and shows list of persons with new details **Extensions** @@ -655,7 +644,6 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. - **Use case: UCSH02 Clear all contacts from the system** **Guarantees:** @@ -663,8 +651,8 @@ Those without any stars are user stories that were considered but will not be im **MSS** -1. User requests to clear all contact. -2. System deletes all contacts and shows a blank list of persons. +1. User requests to clear all contact +2. System deletes all contacts and shows a blank list of persons Use case ends. @@ -672,11 +660,11 @@ Those without any stars are user stories that were considered but will not be im **MSS** -1. User lists all contacts (UC01). -2. User requests to delete a contact. -3. System gives a prompt to confirm whether the user wants to delete the contact. -4. User confirms they want to delete the contact. -5. System deletes the contact and shows the updated list of persons. +1. User lists all contacts (UC01) +2. User requests to delete a contact +3. System gives a prompt to confirm whether the user wants to delete the contact +4. User confirms they want to delete the contact +5. System deletes the contact and shows the updated list of persons Use case ends. @@ -701,15 +689,14 @@ Those without any stars are user stories that were considered but will not be im Use case ends. - **Use case: UCSH04 Receive a prompt when clearing the system** **MSS** -1. User requests to clear the system of all persons. -2. System gives a prompt to confirm whether the user wants to clear all contacts. -3. User confirms they want to clear all contacts. -4. System deletes all contacts and shows a blank list of persons. +1. User requests to clear the system of all persons +2. System gives a prompt to confirm whether the user wants to clear all contacts +3. User confirms they want to clear all contacts +4. System deletes all contacts and shows a blank list of persons Use case ends. @@ -722,14 +709,13 @@ Those without any stars are user stories that were considered but will not be im Use case ends. - **Use case: UCSH05 Assign dietary requirement to contact** **MSS** -1. User lists all contacts (UC01). -2. User requests to add a dietary status to the person. -3. System adds the dietary status to the contact and shows list of persons with new details. +1. User lists all contacts (UC01) +2. User requests to add a dietary status to the person +3. System adds the dietary status to the contact and shows list of persons with new details Use case ends. **Extensions** @@ -745,13 +731,12 @@ Those without any stars are user stories that were considered but will not be im Use case resumes at step 1. - **Use case: UCSH06 Sort contacts in alphabetical order** **MSS** -1. User requests to show a list of persons sorted alphabetically. -2. System shows the list of persons sorted in alphabetical order. +1. User requests to show a list of persons sorted alphabetically +2. System shows the list of persons sorted in alphabetical order Use case ends. @@ -761,15 +746,13 @@ Those without any stars are user stories that were considered but will not be im Use case ends. - - **Use case: UCSH07 Add additional information for a person** **MSS** -1. User lists all contacts (UC01). -2. User requests to add additional information for a person. -3. System adds the additional information to the contact and shows list of persons with new details. +1. User lists all contacts (UC01) +2. User requests to add additional information for a person +3. System adds the additional information to the contact and shows list of persons with new details Use case ends. @@ -796,39 +779,34 @@ Those without any stars are user stories that were considered but will not be im **Use case: UCSH08 See sample contacts in the system before starting to modify it** -Preconditions: User has not added or edited contacts previously. +Preconditions: User has not added or edited contacts previously **MSS** -1. User opens the application. -2. System shows a list of sample contacts. +1. User opens the application +2. System shows a list of sample contacts Use case ends. - - **Use case: UCSH09 Reload sample contacts in the system** **MSS** -1. User requests to reload sample contacts into the system. -2. System deletes all current persons in the system and shows a list of sample contacts. +1. User requests to reload sample contacts into the system +2. System deletes all current persons in the system and shows a list of sample contacts Use case ends. - - **Use case: UCSH10 See a list of all possible commands** **MSS** -1. User requests to see a list of all possible commands they can use in the system. -2. System shows a list of commands with their corresponding input format. +1. User requests to see a list of all possible commands they can use in the system +2. System shows a list of commands with their corresponding input format Use case ends. - ### Non-Functional Requirements **Performance Requirements** From a917fa3eb451c5925f4d82e82ca6014e3a7ea89e Mon Sep 17 00:00:00 2001 From: Han Bin Date: Sun, 6 Oct 2024 23:01:10 +0800 Subject: [PATCH 03/13] Add Feature Create Tag Update Tags to include TagName as a Name Add CreateTagCommand to allow creation of Tags. Add CreateTagCommandParser to parse arguments for creating Tags. Add 2 exceptions related to Tags. Add new class TagName. Edit Storage to save and load Tags across runs. --- .../java/seedu/address/logic/Messages.java | 10 ++ .../logic/commands/CreateTagCommand.java | 71 +++++++++ .../logic/parser/AddressBookParser.java | 4 + .../logic/parser/CreateTagCommandParser.java | 44 ++++++ .../address/logic/parser/ParserUtil.java | 3 +- .../java/seedu/address/model/AddressBook.java | 43 ++++- src/main/java/seedu/address/model/Model.java | 21 +++ .../seedu/address/model/ModelManager.java | 28 ++++ .../address/model/ReadOnlyAddressBook.java | 6 + .../java/seedu/address/model/tag/Tag.java | 18 ++- .../java/seedu/address/model/tag/TagName.java | 70 ++++++++ .../address/model/tag/UniqueTagList.java | 149 ++++++++++++++++++ .../tag/exceptions/DuplicateTagException.java | 11 ++ .../tag/exceptions/TagNotFoundException.java | 6 + .../address/model/util/SampleDataUtil.java | 4 +- .../seedu/address/storage/JsonAdaptedTag.java | 5 +- .../storage/JsonSerializableAddressBook.java | 18 ++- .../java/seedu/address/ui/PersonCard.java | 4 +- .../seedu/address/testutil/PersonUtil.java | 4 +- 19 files changed, 504 insertions(+), 15 deletions(-) create mode 100644 src/main/java/seedu/address/logic/commands/CreateTagCommand.java create mode 100644 src/main/java/seedu/address/logic/parser/CreateTagCommandParser.java create mode 100644 src/main/java/seedu/address/model/tag/TagName.java create mode 100644 src/main/java/seedu/address/model/tag/UniqueTagList.java create mode 100644 src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java create mode 100644 src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..02ba5119b2c 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -6,6 +6,7 @@ import seedu.address.logic.parser.Prefix; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * Container for user visible messages. @@ -48,4 +49,13 @@ public static String format(Person person) { return builder.toString(); } + /** + * Formats the {@code tag} for display to the user. + */ + public static String format(Tag tag) { + final StringBuilder builder = new StringBuilder(); + builder.append(tag.getTagName()); + return builder.toString(); + } + } diff --git a/src/main/java/seedu/address/logic/commands/CreateTagCommand.java b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java new file mode 100644 index 00000000000..bd5c0081b8e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +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.tag.Tag; + +/** + * Adds a tag to the address book. + */ +public class CreateTagCommand extends Command { + + public static final String COMMAND_WORD = "create-tag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tag to the address book. " + + "Parameters: " + + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TAG + "florist"; + + public static final String MESSAGE_SUCCESS = "New tag added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This tag already exists in the address book"; + + private final Tag toAdd; + + /** + * Creates an CreateTagCommand to add the specified {@code Tag} + */ + public CreateTagCommand(Tag tag) { + requireNonNull(tag); + toAdd = tag; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasTag(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.addTag(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CreateTagCommand)) { + return false; + } + + CreateTagCommand otherCreateTagCommand = (CreateTagCommand) other; + return toAdd.equals(otherCreateTagCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..f7a5b5d5a9c 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -11,6 +11,7 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CreateTagCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; @@ -77,6 +78,9 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case CreateTagCommand.COMMAND_WORD: + return new CreateTagCommandParser().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/CreateTagCommandParser.java b/src/main/java/seedu/address/logic/parser/CreateTagCommandParser.java new file mode 100644 index 00000000000..d53116ed45f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CreateTagCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.CreateTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class CreateTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CreateTagCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTagCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_TAG); + Tag tag = ParserUtil.parseTag(argMultimap.getValue(PREFIX_TAG).get()); + return new CreateTagCommand(tag); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..5aeb7790401 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -14,6 +14,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -107,7 +108,7 @@ public static Tag parseTag(String tag) throws ParseException { if (!Tag.isValidTagName(trimmedTag)) { throw new ParseException(Tag.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Tag(new TagName(trimmedTag)); } /** diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..083c1181cb4 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -8,6 +8,8 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.UniqueTagList; /** * Wraps all data at the address-book level @@ -16,16 +18,18 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueTagList tags; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html * * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. + * among constructors. */ { persons = new UniquePersonList(); + tags = new UniqueTagList(); } public AddressBook() {} @@ -55,8 +59,15 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setTags(newData.getTagList()); } + /** + * Replaces the contents of the tag list with {@code tags}. + * {@code tags} must not contain duplicate tags. + */ + public void setTags(List tags) { this.tags.setTags(tags); } + //// person-level operations /** @@ -94,6 +105,31 @@ public void removePerson(Person key) { persons.remove(key); } + /** + * Adds a tag to the address book. + * The tag must not already exist in the address book. + */ + public void addTag(Tag tag) { tags.add(tag); } + + /** + * Returns true if a tag with the same name as {@code tag} exists in the address book. + */ + public boolean hasTag(Tag tag) { + requireNonNull(tag); + return tags.contains(tag); + } + + /** + * Replaces the given tag {@code target} in the list with {@code editedTag}. + * {@code target} must exist in the address book. + * The person identity of {@code editedTag} must not be the same as another existing tag in the address book. + */ + public void setTag(Tag target, Tag editedTag) { + requireNonNull(editedTag); + + tags.setTag(target, editedTag); + } + //// util methods @Override @@ -108,6 +144,9 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getTagList() { return tags.asUnmodifiableObservableList(); } + @Override public boolean equals(Object other) { if (other == this) { @@ -127,4 +166,6 @@ public boolean equals(Object other) { public int hashCode() { return persons.hashCode(); } + + } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..a6d0c2a7b2d 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -6,6 +6,7 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * The API of the Model component. @@ -13,6 +14,7 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_TAGS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -76,6 +78,8 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + void setTag(Tag target, Tag editedTag); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); @@ -84,4 +88,21 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Returns true if a tag with the same name as (@code tag} exists in the addres book. + */ + boolean hasTag(Tag toAdd); + + /** + * Adds the given tag. + * {@code person} must not already exist in the address book. + */ + void addTag(Tag toAdd); + + /** + * Updates the filter of the filtered tag list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTagList(Predicate predicate); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..1eac2c36f5d 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -12,6 +12,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * Represents the in-memory model of the address book data. @@ -22,6 +23,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredTags; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,6 +36,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredTags = new FilteredList<>(this.addressBook.getTagList()); } public ModelManager() { @@ -111,6 +114,25 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + @Override + public void addTag(Tag tag) { + addressBook.addTag(tag); + updateFilteredTagList(PREDICATE_SHOW_ALL_TAGS); + } + + @Override + public boolean hasTag(Tag tag) { + requireNonNull(tag); + return addressBook.hasTag(tag); + } + + @Override + public void setTag(Tag target, Tag editedTag) { + requireAllNonNull(target, editedTag); + + addressBook.setTag(target, editedTag); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -128,6 +150,12 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + @Override + public void updateFilteredTagList(Predicate predicate) { + requireNonNull(predicate); + filteredTags.setPredicate(predicate); + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..273d41cd864 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -2,6 +2,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * Unmodifiable view of an address book @@ -14,4 +15,9 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the tags list. + * This list will not contain any duplicate tags. + */ + ObservableList getTagList(); } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index f1a0d4e233b..a52a4c050c3 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -12,16 +12,15 @@ public class Tag { public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - public final String tagName; + private final TagName tagName; /** * Constructs a {@code Tag}. * * @param tagName A valid tag name. */ - public Tag(String tagName) { + public Tag(TagName tagName) { requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); this.tagName = tagName; } @@ -32,6 +31,17 @@ public static boolean isValidTagName(String test) { return test.matches(VALIDATION_REGEX); } + public boolean isSameTag(Tag otherTag) { + if (otherTag == this) { + return true; + } + + return otherTag != null + && otherTag.getTagName().equals(getTagName()); + } + + public TagName getTagName() { return tagName; } + @Override public boolean equals(Object other) { if (other == this) { @@ -56,7 +66,7 @@ public int hashCode() { * Format state as text for viewing. */ public String toString() { - return '[' + tagName + ']'; + return '[' + tagName.toString() + ']'; } } diff --git a/src/main/java/seedu/address/model/tag/TagName.java b/src/main/java/seedu/address/model/tag/TagName.java new file mode 100644 index 00000000000..f3cce58a91b --- /dev/null +++ b/src/main/java/seedu/address/model/tag/TagName.java @@ -0,0 +1,70 @@ +package seedu.address.model.tag; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Tag's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class TagName { + + public static final String MESSAGE_CONSTRAINTS = + "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String tagName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public TagName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + tagName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return tagName; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TagName)) { + return false; + } + + TagName otherName = (TagName) other; + return tagName.equals(otherName.tagName); + } + + @Override + public int hashCode() { + return tagName.hashCode(); + } + + public boolean matches(String validationRegex) { + return tagName.matches(validationRegex); + } +} diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java new file mode 100644 index 00000000000..3d20c5e5b93 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/UniqueTagList.java @@ -0,0 +1,149 @@ +package seedu.address.model.tag; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.tag.exceptions.DuplicateTagException; +import seedu.address.model.tag.exceptions.TagNotFoundException; + +import java.util.Iterator; +import java.util.List; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +/** + * A list of tags that enforces uniqueness and does not allow nulls. + * A person is considered unique by comparing using {@code Tag#isSameTag(Tag)}. As such, adding and updating of + * tags uses Tag#isSameTag(Tag) for equality to ensure that the person being added or updated is + * unique in terms of identity in the UniqueTagList. However, the removal of a tag uses Tag#equals(Object) to + * ensure that the person with exactly the same fields will be removed. + * Supports a minimal set of list operations. + * + * @see Tag#isSameTag(Tag) + */ +public class UniqueTagList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Tag toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTag); + } + + /** + * Adds a tag to the list. + * The tag must not already exist in the list. + */ + public void add(Tag toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTagException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the tag {@code target} in the list with {@code editedTag}. + * {@code target} must exist in the list. + * The person identity of {@code editedTag} must not be the same as another existing tag in the list. + */ + public void setTag(Tag target, Tag editedPerson) { + requireAllNonNull(target, editedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TagNotFoundException(); + } + + if (!target.isSameTag(editedPerson) && contains(editedPerson)) { + throw new DuplicateTagException(); + } + + internalList.set(index, editedPerson); + } + + /** + * Removes the equivalent tag from the list. + * The tag must exist in the list. + */ + public void remove(Tag toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TagNotFoundException(); + } + } + + public void setTag(UniqueTagList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setTags(List tags) { + requireAllNonNull(tags); + if (!tagsAreUnique(tags)) { + throw new DuplicateTagException(); + } + + internalList.setAll(tags); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueTagList)) { + return false; + } + + UniqueTagList otherUniqueTagList = (UniqueTagList) other; + return internalList.equals(otherUniqueTagList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code tags} contains only unique tags. + */ + private boolean tagsAreUnique(List tags) { + for (int i = 0; i < tags.size() - 1; i++) { + for (int j = i + 1; j < tags.size(); j++) { + if (tags.get(i).isSameTag(tags.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java b/src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java new file mode 100644 index 00000000000..84a530cff03 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java @@ -0,0 +1,11 @@ +package seedu.address.model.tag.exceptions; + +/** + * Signals that the operation will result in duplicate Tags (Tags are considered duplicates if they have the same + * TagName). + */ +public class DuplicateTagException extends RuntimeException { + public DuplicateTagException() { + super("Operation would result in duplicate tags"); + } +} diff --git a/src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java b/src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java new file mode 100644 index 00000000000..9de0aa2c897 --- /dev/null +++ b/src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.tag.exceptions; + +/** + * Signals that the operation is unable to find the specified tag. + */ +public class TagNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..44cadf6c0f2 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -12,6 +12,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -49,10 +50,11 @@ public static ReadOnlyAddressBook getSampleAddressBook() { } /** - * Returns a tag set containing the list of strings given. + * Returns a tag set containing the list of tags given. */ public static Set getTagSet(String... strings) { return Arrays.stream(strings) + .map(TagName::new) .map(Tag::new) .collect(Collectors.toSet()); } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..b32e378a21d 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -5,6 +5,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; /** * Jackson-friendly version of {@link Tag}. @@ -25,7 +26,7 @@ public JsonAdaptedTag(String tagName) { * Converts a given {@code Tag} into this class for Jackson use. */ public JsonAdaptedTag(Tag source) { - tagName = source.tagName; + tagName = source.getTagName().toString(); } @JsonValue @@ -42,7 +43,7 @@ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); } - return new Tag(tagName); + return new Tag(new TagName(tagName)); } } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..3c2737d4dcc 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -12,6 +12,7 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; /** * An Immutable AddressBook that is serializable to JSON format. @@ -20,15 +21,20 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_TAG = "Tags list contains duplicate tag(s)."; private final List persons = new ArrayList<>(); + private final List tags = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons and tag. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook( + @JsonProperty("persons") List persons, + @JsonProperty("tags") List tags) { this.persons.addAll(persons); + this.tags.addAll(tags); } /** @@ -38,6 +44,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + .sorted(Comparator.comparing(tag -> tag.getTagName().toString())) + .forEach(tag -> tags.getChildren().add(new Label(tag.getTagName().toString()))); } } diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 90849945183..ee92d61b655 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -35,7 +35,7 @@ public static String getPersonDetails(Person person) { sb.append(PREFIX_EMAIL + person.getEmail().value + " "); sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); person.getTags().stream().forEach( - s -> sb.append(PREFIX_TAG + s.tagName + " ") + s -> sb.append(PREFIX_TAG + s.getTagName().toString() + " ") ); return sb.toString(); } @@ -54,7 +54,7 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip if (tags.isEmpty()) { sb.append(PREFIX_TAG); } else { - tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); + tags.forEach(s -> sb.append(PREFIX_TAG).append(s.getTagName()).append(" ")); } } return sb.toString(); From 317cf4b97b39987ddd9a09b01c806f0b87e71677 Mon Sep 17 00:00:00 2001 From: Han Bin Date: Tue, 8 Oct 2024 22:22:23 +0800 Subject: [PATCH 04/13] Add Feature Delete Tag Add DeleteTagCommand to allow deletion of Tags. Add DeleteTagCommandParser to parse arguments for deleting Tags. --- .../logic/commands/DeleteTagCommand.java | 75 +++++++++++++++++++ .../logic/parser/AddressBookParser.java | 14 +--- .../logic/parser/DeleteTagCommandParser.java | 44 +++++++++++ .../java/seedu/address/model/AddressBook.java | 8 ++ src/main/java/seedu/address/model/Model.java | 9 +++ .../seedu/address/model/ModelManager.java | 11 ++- .../java/seedu/address/model/tag/Tag.java | 20 +++++ 7 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 src/main/java/seedu/address/logic/commands/DeleteTagCommand.java create mode 100644 src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java diff --git a/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java new file mode 100644 index 00000000000..89b41b4697b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java @@ -0,0 +1,75 @@ +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.tag.Tag; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class DeleteTagCommand extends Command { + + public static final String COMMAND_WORD = "delete-tag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the tag identified by the tag name.\n" + + "Parameters: TAG_NAME (must exists in the AddressBook)\n" + + "Example: " + COMMAND_WORD + " florist"; + + public static final String MESSAGE_DELETE_TAG_SUCCESS = "Deleted Tag: %1$s"; + public static final String MESSAGE_DELETE_TAG_FAILURE_STILL_TAGGED = "The Tag: %1$s is still used"; + public static final String MESSAGE_DELETE_TAG_FAILURE_NOT_FOUND = "The Tag: %1$s does not exist"; + + private final Tag targetTag; + + public DeleteTagCommand(Tag targetTag) { + this.targetTag = targetTag; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List allTags = model.getFilteredTagList(); + + for (Tag tag : allTags) { + if (tag.getTagName().equals(targetTag.getTagName())) { + if (tag.canBeDeleted()) { + model.deleteTag(tag); + return new CommandResult(String.format(MESSAGE_DELETE_TAG_SUCCESS, Messages.format(targetTag))); + } else { + throw new CommandException( + String.format(MESSAGE_DELETE_TAG_FAILURE_STILL_TAGGED, Messages.format(targetTag))); + } + } + } + throw new CommandException(String.format(MESSAGE_DELETE_TAG_FAILURE_NOT_FOUND, Messages.format(targetTag))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteTagCommand)) { + return false; + } + + DeleteTagCommand otherDeleteTagCommand = (DeleteTagCommand) other; + return targetTag.equals(otherDeleteTagCommand.targetTag); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetTag", targetTag) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index f7a5b5d5a9c..0b3a4562d54 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -8,16 +8,7 @@ import java.util.regex.Pattern; import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CreateTagCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.*; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -81,6 +72,9 @@ public Command parseCommand(String userInput) throws ParseException { case CreateTagCommand.COMMAND_WORD: return new CreateTagCommandParser().parse(arguments); + case DeleteTagCommand.COMMAND_WORD: + return new DeleteTagCommandParser().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/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java new file mode 100644 index 00000000000..a634fc3eb46 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.commands.CreateTagCommand; +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +import java.util.stream.Stream; + +/** + * Parses input arguments and creates a new DeleteTagCommand object + */ +public class DeleteTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns a DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTagCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTagCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_TAG); + Tag tag = ParserUtil.parseTag(argMultimap.getValue(PREFIX_TAG).get()); + return new DeleteTagCommand(tag); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 083c1181cb4..e6f10f30876 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -105,6 +105,8 @@ public void removePerson(Person key) { persons.remove(key); } + //// tag-level operations + /** * Adds a tag to the address book. * The tag must not already exist in the address book. @@ -130,6 +132,12 @@ public void setTag(Tag target, Tag editedTag) { tags.setTag(target, editedTag); } + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTag(Tag key) { tags.remove(key); } + //// util methods @Override diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index a6d0c2a7b2d..35482e3ac20 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -105,4 +105,13 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredTagList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered tag list */ + ObservableList getFilteredTagList(); + + /** + * Deletes the given tag. + * The tag must exist in the AddressBook. + */ + void deleteTag(Tag toDelete); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 1eac2c36f5d..80cb22cecf9 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -133,6 +133,9 @@ public void setTag(Tag target, Tag editedTag) { addressBook.setTag(target, editedTag); } + @Override + public void deleteTag(Tag target) { addressBook.removeTag(target); } + //=========== Filtered Person List Accessors ============================================================= /** @@ -150,6 +153,11 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + @Override + public ObservableList getFilteredTagList() { + return filteredTags; + } + @Override public void updateFilteredTagList(Predicate predicate) { requireNonNull(predicate); @@ -170,7 +178,8 @@ 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) + && filteredTags.equals(otherModelManager.filteredTags); } } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index a52a4c050c3..6e26d13ec5d 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -13,6 +13,7 @@ public class Tag { public static final String VALIDATION_REGEX = "\\p{Alnum}+"; private final TagName tagName; + private int taggedCount; /** * Constructs a {@code Tag}. @@ -22,6 +23,7 @@ public class Tag { public Tag(TagName tagName) { requireNonNull(tagName); this.tagName = tagName; + this.taggedCount = 0; } /** @@ -42,6 +44,24 @@ public boolean isSameTag(Tag otherTag) { public TagName getTagName() { return tagName; } + public int getNumberOfPersonsTagged() { return taggedCount; } + + public void increaseTaggedCount() { + taggedCount++; + } + + public void decreaseTaggedCount() { + taggedCount--; + } + + /** + * Returns true if the tag can be deleted. + * The tag can be deleted if TaggedCount is 0. + */ + public boolean canBeDeleted() { + return taggedCount == 0; + } + @Override public boolean equals(Object other) { if (other == this) { From ccd2979f10c942f75a96f9e7acd229b855dc80ab Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 13:07:58 +0800 Subject: [PATCH 05/13] Add tests for CreateTag feature. Implemented tests for CreateTagCommand. Implemented tests for CreateTagParserCommand. --- .../logic/commands/CreateTagCommand.java | 4 +- .../logic/parser/AddressBookParser.java | 12 +- .../logic/parser/DeleteTagCommandParser.java | 4 +- .../address/logic/parser/ParserUtil.java | 2 +- .../seedu/address/model/ModelManager.java | 2 +- .../java/seedu/address/model/tag/Tag.java | 7 +- .../address/model/tag/UniqueTagList.java | 50 ++-- .../seedu/address/storage/JsonAdaptedTag.java | 2 +- .../logic/commands/AddCommandTest.java | 31 +++ .../logic/commands/CommandTestUtil.java | 4 + .../logic/commands/CreateTagCommandTest.java | 226 ++++++++++++++++++ .../logic/parser/AddCommandParserTest.java | 3 +- .../parser/CreateTagCommandParserTest.java | 43 ++++ .../logic/parser/EditCommandParserTest.java | 12 +- .../address/logic/parser/ParserUtilTest.java | 20 +- .../seedu/address/model/AddressBookTest.java | 12 +- .../java/seedu/address/model/tag/TagTest.java | 2 +- .../testutil/EditPersonDescriptorBuilder.java | 3 +- 18 files changed, 386 insertions(+), 53 deletions(-) create mode 100644 src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java create mode 100644 src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java diff --git a/src/main/java/seedu/address/logic/commands/CreateTagCommand.java b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java index bd5c0081b8e..3a890b8dec3 100644 --- a/src/main/java/seedu/address/logic/commands/CreateTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java @@ -23,7 +23,7 @@ public class CreateTagCommand extends Command { + PREFIX_TAG + "florist"; public static final String MESSAGE_SUCCESS = "New tag added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This tag already exists in the address book"; + public static final String MESSAGE_DUPLICATE_TAG = "This tag already exists in the address book"; private final Tag toAdd; @@ -40,7 +40,7 @@ public CommandResult execute(Model model) throws CommandException { requireNonNull(model); if (model.hasTag(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + throw new CommandException(MESSAGE_DUPLICATE_TAG); } model.addTag(toAdd); diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 0b3a4562d54..1dd5bfa9a28 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -8,7 +8,17 @@ import java.util.regex.Pattern; import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.*; +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CreateTagCommand; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; /** diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java index a634fc3eb46..27e0c06b557 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -3,13 +3,13 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.stream.Stream; + import seedu.address.logic.commands.CreateTagCommand; import seedu.address.logic.commands.DeleteTagCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; -import java.util.stream.Stream; - /** * Parses input arguments and creates a new DeleteTagCommand object */ diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5aeb7790401..05b34275e2c 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -106,7 +106,7 @@ public static Tag parseTag(String tag) throws ParseException { requireNonNull(tag); String trimmedTag = tag.trim(); if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + throw new ParseException(TagName.MESSAGE_CONSTRAINTS); } return new Tag(new TagName(trimmedTag)); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 80cb22cecf9..9ad63e67f86 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -154,7 +154,7 @@ public void updateFilteredPersonList(Predicate predicate) { } @Override - public ObservableList getFilteredTagList() { + public ObservableList getFilteredTagList() { return filteredTags; } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 6e26d13ec5d..5984a982fce 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -1,7 +1,6 @@ package seedu.address.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. @@ -10,7 +9,7 @@ public class Tag { public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; private final TagName tagName; private int taggedCount; @@ -33,6 +32,10 @@ public static boolean isValidTagName(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns true if another tag has the same TagName as this tag. + * @param otherTag A tag to compare with. + */ public boolean isSameTag(Tag otherTag) { if (otherTag == this) { return true; diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java index 3d20c5e5b93..33e1bdeb683 100644 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ b/src/main/java/seedu/address/model/tag/UniqueTagList.java @@ -1,15 +1,15 @@ package seedu.address.model.tag; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.tag.exceptions.DuplicateTagException; -import seedu.address.model.tag.exceptions.TagNotFoundException; +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Iterator; import java.util.List; -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.tag.exceptions.DuplicateTagException; +import seedu.address.model.tag.exceptions.TagNotFoundException; /** * A list of tags that enforces uniqueness and does not allow nulls. @@ -67,6 +67,11 @@ public void setTag(Tag target, Tag editedPerson) { internalList.set(index, editedPerson); } + public void setTag(UniqueTagList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + /** * Removes the equivalent tag from the list. * The tag must exist in the list. @@ -78,11 +83,6 @@ public void remove(Tag toRemove) { } } - public void setTag(UniqueTagList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - /** * Replaces the contents of this list with {@code persons}. * {@code persons} must not contain duplicate persons. @@ -103,6 +103,20 @@ public ObservableList asUnmodifiableObservableList() { return internalUnmodifiableList; } + /** + * Returns true if {@code tags} contains only unique tags. + */ + private boolean tagsAreUnique(List tags) { + for (int i = 0; i < tags.size() - 1; i++) { + for (int j = i + 1; j < tags.size(); j++) { + if (tags.get(i).isSameTag(tags.get(j))) { + return false; + } + } + } + return true; + } + @Override public Iterator iterator() { return internalList.iterator(); @@ -132,18 +146,4 @@ public int hashCode() { public String toString() { return internalList.toString(); } - - /** - * Returns true if {@code tags} contains only unique tags. - */ - private boolean tagsAreUnique(List tags) { - for (int i = 0; i < tags.size() - 1; i++) { - for (int j = i + 1; j < tags.size(); j++) { - if (tags.get(i).isSameTag(tags.get(j))) { - return false; - } - } - } - return true; - } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index b32e378a21d..7a5ae0f804d 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -41,7 +41,7 @@ public String getTagName() { */ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); + throw new IllegalValueException(TagName.MESSAGE_CONSTRAINTS); } return new Tag(new TagName(tagName)); } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 90e8253f48e..2d0b4b9030f 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -23,6 +23,7 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.testutil.PersonBuilder; public class AddCommandTest { @@ -148,6 +149,11 @@ public void setPerson(Person target, Person editedPerson) { throw new AssertionError("This method should not be called."); } + @Override + public void setTag(Tag target, Tag editedTag) { + throw new AssertionError("This method should not be called."); + } + @Override public ObservableList getFilteredPersonList() { throw new AssertionError("This method should not be called."); @@ -157,6 +163,31 @@ public ObservableList getFilteredPersonList() { public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + + @Override + public boolean hasTag(Tag toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTag(Tag toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTagList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTagList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTag(Tag toDelete) { + throw new AssertionError("This method should not be called."); + } } /** diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..aedc6a5ac06 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -36,6 +36,8 @@ public class CommandTestUtil { public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_TAG_FLORIST = "florist"; + public static final String VALID_TAG_PHOTOGRAPHER = "photographer"; public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -47,6 +49,8 @@ public class CommandTestUtil { public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; + public static final String TAG_DESC_FLORIST = " " + PREFIX_TAG + VALID_TAG_FLORIST; + public static final String TAG_DESC_PHOTOGRAPHER = " " + PREFIX_TAG + VALID_TAG_FLORIST; 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 diff --git a/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java b/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java new file mode 100644 index 00000000000..68567346a81 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java @@ -0,0 +1,226 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FLORIST; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_PHOTOGRAPHER; + + +import javafx.collections.ObservableList; +import org.junit.jupiter.api.Test; +import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.*; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Predicate; + + + +public class CreateTagCommandTest { + + @Test + public void constructor_nullTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new CreateTagCommand(null)); + } + + @Test + public void execute_tagAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingTagAdded modelStub = new ModelStubAcceptingTagAdded(); + Tag validTag = new Tag(new TagName(VALID_TAG_FLORIST)); + + CommandResult commandResult = new CreateTagCommand(validTag).execute(modelStub); + assertEquals(String.format(CreateTagCommand.MESSAGE_SUCCESS, Messages.format(validTag)), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validTag), modelStub.tagsAdded); + } + + @Test + public void execute_duplicateTag_throwsCommandException() { + Tag validTag = new Tag(new TagName(VALID_TAG_FLORIST)); + CreateTagCommand createTagCommand = new CreateTagCommand(validTag); + ModelStub modelStub = new ModelStubWithTag(validTag); + + assertThrows(CommandException.class, CreateTagCommand.MESSAGE_DUPLICATE_TAG, + () -> createTagCommand.execute(modelStub)); + } + + @Test + public void equals() { + Tag florist = new Tag(new TagName(VALID_TAG_FLORIST)); + Tag photographer = new Tag(new TagName(VALID_TAG_PHOTOGRAPHER)); + CreateTagCommand createFloristCommand = new CreateTagCommand(florist); + CreateTagCommand createPhotographerCommand = new CreateTagCommand(photographer); + + // same object -> returns ture + assertEquals(createFloristCommand, createFloristCommand); + + // same values -> returns false + CreateTagCommand createFloristCommandCopy = new CreateTagCommand(florist); + assertEquals(createFloristCommand, createFloristCommandCopy); + + // different types -> returns false + assertFalse(createFloristCommand.equals(1)); + + // null -> return false + assertFalse(createFloristCommand.equals(null)); + + // different tag -> return false + assertFalse(createFloristCommand.equals(createPhotographerCommand)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getAddressBookFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setAddressBookFilePath(Path addressBookFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Person person) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setAddressBook(ReadOnlyAddressBook newData) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Person person) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Person target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPerson(Person target, Person editedPerson) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTag(Tag target, Tag editedTag) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredPersonList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredPersonList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTag(Tag toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTag(Tag toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTagList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTagList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTag(Tag tag) { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single tag. + */ + private class ModelStubWithTag extends CreateTagCommandTest.ModelStub { + private final Tag tag; + + ModelStubWithTag(Tag tag) { + requireNonNull(tag); + this.tag = tag; + } + + @Override + public boolean hasTag(Tag tag) { + requireNonNull(tag); + return this.tag.isSameTag(tag); + } + } + + /** + * A Model stub that always accept the tag being added. + */ + private class ModelStubAcceptingTagAdded extends CreateTagCommandTest.ModelStub { + final ArrayList tagsAdded = new ArrayList<>(); + + @Override + public boolean hasTag(Tag tag) { + requireNonNull(tag); + return tagsAdded.stream().anyMatch(tag::isSameTag); + } + + @Override + public void addTag(Tag tag) { + requireNonNull(tag); + tagsAdded.add(tag); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index 5bc11d3cdaa..0b75fb8dd87 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -43,6 +43,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; import seedu.address.testutil.PersonBuilder; public class AddCommandParserTest { @@ -182,7 +183,7 @@ public void parse_invalidValue_failure() { // 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); + + INVALID_TAG_DESC + VALID_TAG_FRIEND, TagName.MESSAGE_CONSTRAINTS); // two invalid values, only first invalid value reported assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, diff --git a/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java new file mode 100644 index 00000000000..b81717a58ae --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FLORIST; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FLORIST; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.CreateTagCommand; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; + +public class CreateTagCommandParserTest { + private CreateTagCommandParser parser = new CreateTagCommandParser(); + + @Test + public void parse_validTagName_success() { + Tag expectedTag = new Tag(new TagName(VALID_TAG_FLORIST)); + assertParseSuccess(parser, PREAMBLE_WHITESPACE + TAG_DESC_FLORIST, new CreateTagCommand(expectedTag)); + } + + @Test + public void parse_invalidTagName_failure() { + assertParseFailure(parser, PREAMBLE_WHITESPACE + INVALID_TAG_DESC, TagName.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_duplicateTagName_failure() { + String validExpectedTagString = TAG_DESC_FLORIST; + assertParseFailure(parser, TAG_DESC_FLORIST + validExpectedTagString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_TAG)); + } + + @Test + public void parse_emptyTagName_failure() { + String expectedMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, CreateTagCommand.MESSAGE_USAGE); + assertParseFailure(parser, VALID_TAG_FLORIST, expectedMessage); + } +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index cc7175172d4..b4a32388004 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -43,6 +43,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; import seedu.address.testutil.EditPersonDescriptorBuilder; public class EditCommandParserTest { @@ -87,16 +88,19 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address - assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag + assertParseFailure(parser, "1" + INVALID_TAG_DESC, TagName.MESSAGE_CONSTRAINTS); // invalid tag // invalid phone followed by valid email assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited, // parsing it together with a valid tag results in error - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); + assertParseFailure( + parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, TagName.MESSAGE_CONSTRAINTS); + assertParseFailure( + parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, TagName.MESSAGE_CONSTRAINTS); + assertParseFailure( + parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, TagName.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, diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..6cc5882e497 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -19,6 +19,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; public class ParserUtilTest { private static final String INVALID_NAME = "R@chel"; @@ -31,8 +32,8 @@ public class ParserUtilTest { private static final String VALID_PHONE = "123456"; private static final String VALID_ADDRESS = "123 Main Street #0505"; private static final String VALID_EMAIL = "rachel@example.com"; - private static final String VALID_TAG_1 = "friend"; - private static final String VALID_TAG_2 = "neighbour"; + private static final String VALID_TAG_1_NAME = "florist"; + private static final String VALID_TAG_2_NAME = "photographer"; private static final String WHITESPACE = " \t\r\n"; @@ -160,14 +161,14 @@ public void parseTag_invalidValue_throwsParseException() { @Test public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception { - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1)); + Tag expectedTag = new Tag(new TagName(VALID_TAG_1_NAME)); + assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1_NAME)); } @Test public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Exception { - String tagWithWhitespace = WHITESPACE + VALID_TAG_1 + WHITESPACE; - Tag expectedTag = new Tag(VALID_TAG_1); + String tagWithWhitespace = WHITESPACE + VALID_TAG_1_NAME + WHITESPACE; + Tag expectedTag = new Tag(new TagName(VALID_TAG_1_NAME)); assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace)); } @@ -178,7 +179,7 @@ public void parseTags_null_throwsNullPointerException() { @Test public void parseTags_collectionWithInvalidTags_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG))); + assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1_NAME, INVALID_TAG))); } @Test @@ -188,8 +189,9 @@ public void parseTags_emptyCollection_returnsEmptySet() throws Exception { @Test public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { - Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); - Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1_NAME, VALID_TAG_2_NAME)); + Set expectedTagSet = new HashSet(Arrays.asList(new Tag(new TagName(VALID_TAG_1_NAME)), + new Tag(new TagName(VALID_TAG_2_NAME)))); assertEquals(expectedTagSet, actualTagSet); } diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 68c8c5ba4d5..33595b9595c 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -20,6 +20,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tag.Tag; import seedu.address.testutil.PersonBuilder; public class AddressBookTest { @@ -49,7 +50,7 @@ public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) .build(); List newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons); + AddressBookStub newData = new AddressBookStub(newPersons, null); assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); } @@ -94,15 +95,22 @@ public void toStringMethod() { */ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); + private final ObservableList tags = FXCollections.observableArrayList(); - AddressBookStub(Collection persons) { + AddressBookStub(Collection persons, Collection tags) { this.persons.setAll(persons); + this.tags.setAll(tags); } @Override public ObservableList getPersonList() { return persons; } + + @Override + public ObservableList getTagList() { + return tags; + } } } diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java index 64d07d79ee2..229f0ddc731 100644 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ b/src/test/java/seedu/address/model/tag/TagTest.java @@ -14,7 +14,7 @@ public void constructor_null_throwsNullPointerException() { @Test public void constructor_invalidTagName_throwsIllegalArgumentException() { String invalidTagName = ""; - assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName)); + assertThrows(IllegalArgumentException.class, () -> new Tag(new TagName(invalidTagName))); } @Test diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e..5e5a196a8ea 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java @@ -11,6 +11,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; /** * A utility class to help with building EditPersonDescriptor objects. @@ -76,7 +77,7 @@ public EditPersonDescriptorBuilder withAddress(String address) { * that we are building. */ public EditPersonDescriptorBuilder withTags(String... tags) { - Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); + Set tagSet = Stream.of(tags).map(TagName::new).map(Tag::new).collect(Collectors.toSet()); descriptor.setTags(tagSet); return this; } From 8169bad8fdf19d2da484cbc73471f10dfb5af863 Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 13:41:45 +0800 Subject: [PATCH 06/13] Add tests for DeleteTag feature. Implemented tests for DeleteTagCommand. Implemented tests for DeleteTagParserCommand. --- .../logic/parser/DeleteTagCommandParser.java | 2 +- .../duplicatePersonAddressBook.json | 4 +- .../invalidPersonAddressBook.json | 3 +- .../typicalPersonsAddressBook.json | 3 +- .../logic/commands/CommandTestUtil.java | 4 ++ .../logic/commands/DeleteTagCommandTest.java | 69 +++++++++++++++++++ .../parser/DeleteTagCommandParserTest.java | 25 +++++++ .../seedu/address/model/AddressBookTest.java | 6 +- .../address/testutil/TypicalPersons.java | 21 +++--- 9 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java create mode 100644 src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java index 27e0c06b557..edb6aa2a48b 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -26,7 +26,7 @@ public DeleteTagCommand parse(String args) throws ParseException { if (!arePrefixesPresent(argMultimap, PREFIX_TAG) || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTagCommand.MESSAGE_USAGE)); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE)); } argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_TAG); diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json index a7427fe7aa2..02bfd8316c8 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json @@ -10,5 +10,7 @@ "phone": "94351253", "email": "pauline@example.com", "address": "4th street" - } ] + } ], + + "tags": ["florist"] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json index ad3f135ae42..2cf46c1b310 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json @@ -4,5 +4,6 @@ "phone": "9482424", "email": "invalid@email!3e", "address": "4th street" - } ] + } ], + "tags": ["florist"] } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json index 72262099d35..43eb9a325a0 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json @@ -42,5 +42,6 @@ "email" : "anna@example.com", "address" : "4th street", "tags" : [ ] - } ] + } ], + "tags": ["florist"] } diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index aedc6a5ac06..04e0dbc5195 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -19,6 +19,8 @@ import seedu.address.model.Model; import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; import seedu.address.testutil.EditPersonDescriptorBuilder; /** @@ -64,6 +66,8 @@ public class CommandTestUtil { public static final EditCommand.EditPersonDescriptor DESC_AMY; public static final EditCommand.EditPersonDescriptor DESC_BOB; + public static final Tag TEST_TAG_FLORIST = new Tag(new TagName(VALID_TAG_FLORIST)); + static { DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) diff --git a/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java new file mode 100644 index 00000000000..5c69f7589ce --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java @@ -0,0 +1,69 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.*; + +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.tag.Tag; + +public class DeleteTagCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validDeleteTagCommand_success() { + Tag tagToDelete = model.getFilteredTagList().get(0); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(tagToDelete); + + String expectedMessage = String.format(DeleteTagCommand.MESSAGE_DELETE_TAG_SUCCESS, + Messages.format(tagToDelete)); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTag(tagToDelete); + + assertCommandSuccess(deleteTagCommand, model, expectedMessage, expectedModel); + } + + @Test void execute_invalidNotFoundDeleteTagCommand() { + Tag tagToDelete = model.getFilteredTagList().get(0); + DeleteTagCommand deleteTagCommand = new DeleteTagCommand(tagToDelete); + + String expectedMessage = String.format(DeleteTagCommand.MESSAGE_DELETE_TAG_FAILURE_NOT_FOUND, + Messages.format(tagToDelete)); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTag(tagToDelete); + + DeleteTagCommand expectedDeleteTagCommand = new DeleteTagCommand(tagToDelete); + + assertCommandFailure(expectedDeleteTagCommand, expectedModel, expectedMessage); + } + + @Test + public void equals() { + DeleteTagCommand deleteFloristTagCommand = new DeleteTagCommand(FLORIST); + DeleteTagCommand deletePhotographerTagCommandCopy = new DeleteTagCommand(PHOTOGRAPHER); + + // same object -> returns true + assertTrue(deleteFloristTagCommand.equals(deleteFloristTagCommand)); + + // same values -> returns true + DeleteTagCommand deleteFloristTagCommandCopy = new DeleteTagCommand(FLORIST); + assertTrue(deleteFloristTagCommand.equals(deleteFloristTagCommandCopy)); + + // different types -> return false + assertFalse(deleteFloristTagCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFloristTagCommand.equals(null)); + + // different tag -> returns false + assertFalse(deleteFloristTagCommand.equals(deletePhotographerTagCommandCopy)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java new file mode 100644 index 00000000000..2dfb3079522 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java @@ -0,0 +1,25 @@ +package seedu.address.logic.parser; + +import org.junit.jupiter.api.Test; +import seedu.address.logic.commands.DeleteTagCommand; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.TEST_TAG_FLORIST; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +public class DeleteTagCommandParserTest { + + private DeleteTagCommandParser parser = new DeleteTagCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteTagCommand() { + assertParseSuccess(parser, " t/florist", new DeleteTagCommand(TEST_TAG_FLORIST)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 33595b9595c..cefec0921e5 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -6,8 +6,7 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalPersons.*; import java.util.Arrays; import java.util.Collection; @@ -50,7 +49,8 @@ public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) .build(); List newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons, null); + List tags = Arrays.asList(FLORIST); + AddressBookStub newData = new AddressBookStub(newPersons, tags); assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); } diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index fec76fb7129..65b596d7831 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -1,22 +1,15 @@ package seedu.address.testutil; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import seedu.address.model.AddressBook; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.TagName; + +import static seedu.address.logic.commands.CommandTestUtil.*; /** * A utility class containing a list of {@code Person} objects to be used in tests. @@ -55,6 +48,10 @@ public class TypicalPersons { .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) .build(); + // Manually added Tags + public static final Tag FLORIST = new Tag(new TagName(VALID_TAG_FLORIST)); + public static final Tag PHOTOGRAPHER = new Tag(new TagName(VALID_TAG_PHOTOGRAPHER)); + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER private TypicalPersons() {} // prevents instantiation @@ -67,6 +64,8 @@ public static AddressBook getTypicalAddressBook() { for (Person person : getTypicalPersons()) { ab.addPerson(person); } + ab.addTag(FLORIST); + ab.addTag(PHOTOGRAPHER); return ab; } From 7e8e310dc598c899001790ce00057691c5db8d46 Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 13:57:23 +0800 Subject: [PATCH 07/13] Fix check style issues in DeleteTagCommandParser.java Fix unused import in DeleteTagCommandParser. --- .../java/seedu/address/logic/parser/DeleteTagCommandParser.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java index edb6aa2a48b..6df60a5574f 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -5,7 +5,6 @@ import java.util.stream.Stream; -import seedu.address.logic.commands.CreateTagCommand; import seedu.address.logic.commands.DeleteTagCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; From aa5ee67d944fb7d119001546fd2f63ea49967abc Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 14:13:48 +0800 Subject: [PATCH 08/13] Fix check style issues in Test classes Fix check style issues in all Test classes. --- .../logic/commands/CreateTagCommandTest.java | 25 ++++++++++--------- .../logic/commands/DeleteTagCommandTest.java | 6 +++-- .../logic/parser/AddCommandParserTest.java | 1 - .../parser/CreateTagCommandParserTest.java | 1 + .../parser/DeleteTagCommandParserTest.java | 7 +++--- .../logic/parser/EditCommandParserTest.java | 1 - .../seedu/address/model/AddressBookTest.java | 4 ++- .../address/testutil/TypicalPersons.java | 15 +++++++++-- 8 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java b/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java index 68567346a81..9d8e37c6278 100644 --- a/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java @@ -3,28 +3,29 @@ import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FLORIST; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_PHOTOGRAPHER; +import static seedu.address.testutil.Assert.assertThrows; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Predicate; -import javafx.collections.ObservableList; import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.*; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.function.Predicate; - - - public class CreateTagCommandTest { @Test @@ -49,8 +50,8 @@ public void execute_duplicateTag_throwsCommandException() { CreateTagCommand createTagCommand = new CreateTagCommand(validTag); ModelStub modelStub = new ModelStubWithTag(validTag); - assertThrows(CommandException.class, CreateTagCommand.MESSAGE_DUPLICATE_TAG, - () -> createTagCommand.execute(modelStub)); + assertThrows(CommandException.class, + CreateTagCommand.MESSAGE_DUPLICATE_TAG, () -> createTagCommand.execute(modelStub)); } @Test diff --git a/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java index 5c69f7589ce..29ccad16588 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java @@ -4,9 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.*; +import static seedu.address.testutil.TypicalPersons.FLORIST; +import static seedu.address.testutil.TypicalPersons.PHOTOGRAPHER; +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; @@ -32,7 +35,6 @@ public void execute_validDeleteTagCommand_success() { @Test void execute_invalidNotFoundDeleteTagCommand() { Tag tagToDelete = model.getFilteredTagList().get(0); - DeleteTagCommand deleteTagCommand = new DeleteTagCommand(tagToDelete); String expectedMessage = String.format(DeleteTagCommand.MESSAGE_DELETE_TAG_FAILURE_NOT_FOUND, Messages.format(tagToDelete)); diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index 0b75fb8dd87..f7484d1ac6b 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -42,7 +42,6 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; import seedu.address.testutil.PersonBuilder; diff --git a/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java index b81717a58ae..ebc89f6ca8c 100644 --- a/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/CreateTagCommandParserTest.java @@ -9,6 +9,7 @@ import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; import org.junit.jupiter.api.Test; + import seedu.address.logic.Messages; import seedu.address.logic.commands.CreateTagCommand; import seedu.address.model.tag.Tag; diff --git a/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java index 2dfb3079522..855010ec946 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteTagCommandParserTest.java @@ -1,13 +1,14 @@ package seedu.address.logic.parser; -import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.DeleteTagCommand; - import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.commands.CommandTestUtil.TEST_TAG_FLORIST; 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.DeleteTagCommand; + public class DeleteTagCommandParserTest { private DeleteTagCommandParser parser = new DeleteTagCommandParser(); diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index b4a32388004..5db89377341 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -42,7 +42,6 @@ import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; import seedu.address.testutil.EditPersonDescriptorBuilder; diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index cefec0921e5..4f00d27ed0b 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -6,7 +6,9 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.*; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.FLORIST; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.Arrays; import java.util.Collection; diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index 65b596d7831..c1a7d1fd66f 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -1,5 +1,18 @@ package seedu.address.testutil; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FLORIST; +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_TAG_PHOTOGRAPHER; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -9,8 +22,6 @@ import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; -import static seedu.address.logic.commands.CommandTestUtil.*; - /** * A utility class containing a list of {@code Person} objects to be used in tests. */ From 1febc224e488a33c1482b6c8930e287895a4fe08 Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 21:53:15 +0800 Subject: [PATCH 09/13] Add more test for Tagging feature. Add test for ModelManager. Add test for Tag. Add test for UniqueTagList. --- .../seedu/address/model/ModelManagerTest.java | 22 +++ .../java/seedu/address/model/tag/TagTest.java | 29 ++++ .../address/model/tag/UniqueTagListTest.java | 135 ++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/test/java/seedu/address/model/tag/UniqueTagListTest.java diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..2243924488f 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -7,6 +7,7 @@ import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.FLORIST; import java.nio.file.Path; import java.nio.file.Paths; @@ -93,6 +94,27 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); } + @Test + public void hasTag_nullTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasTag(null)); + } + + @Test + public void hasTag_tagNotInAddressBook_returnsFalse() { + assertFalse(modelManager.hasTag(FLORIST)); + } + + @Test + public void hasTag_tagInAddressBook_returnsTrue() { + modelManager.addTag(FLORIST); + assertTrue(modelManager.hasTag(FLORIST)); + } + + @Test + public void getFilteredTagList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredTagList().remove(0)); + } + @Test public void equals() { AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java index 229f0ddc731..16151b7dee8 100644 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ b/src/test/java/seedu/address/model/tag/TagTest.java @@ -1,6 +1,9 @@ package seedu.address.model.tag; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.FLORIST; import org.junit.jupiter.api.Test; @@ -23,4 +26,30 @@ public void isValidTagName() { assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); } + @Test + public void canBeDeleted() { + Tag florist = FLORIST; + assertTrue(FLORIST.canBeDeleted()); + } + + @Test + public void noPersonsTaggedCheck() { + Tag florist = FLORIST; + assertEquals(0, FLORIST.getNumberOfPersonsTagged()); + } + + @Test + public void incrementTaggedCount() { + Tag florist = FLORIST; + florist.increaseTaggedCount(); + assertEquals(1, florist.getNumberOfPersonsTagged()); + } + + @Test + public void decrementTaggedCount() { + Tag florist = FLORIST; + florist.increaseTaggedCount(); + florist.decreaseTaggedCount(); + assertEquals(0, florist.getNumberOfPersonsTagged()); + } } diff --git a/src/test/java/seedu/address/model/tag/UniqueTagListTest.java b/src/test/java/seedu/address/model/tag/UniqueTagListTest.java new file mode 100644 index 00000000000..2b1f09aa0bf --- /dev/null +++ b/src/test/java/seedu/address/model/tag/UniqueTagListTest.java @@ -0,0 +1,135 @@ +package seedu.address.model.tag; + +import org.junit.jupiter.api.Test; +import seedu.address.model.tag.exceptions.DuplicateTagException; +import seedu.address.model.tag.exceptions.TagNotFoundException; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.FLORIST; +import static seedu.address.testutil.TypicalPersons.PHOTOGRAPHER; + +public class UniqueTagListTest { + private final UniqueTagList uniqueTagList = new UniqueTagList(); + + @Test + public void contains_nullTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTagList.contains(null)); + } + + @Test + public void contains_tagNotInList_returnsFalse() { + assertFalse(uniqueTagList.contains(FLORIST)); + } + + @Test + public void contains_tagInList_returnsTrue() { + uniqueTagList.add(FLORIST); + assertTrue(uniqueTagList.contains(FLORIST)); + } + + @Test + public void add_nullTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTagList.add(null)); + } + + @Test + public void add_duplicateTag_throwsDuplicateTagException() { + uniqueTagList.add(FLORIST); + assertThrows(DuplicateTagException.class, () -> uniqueTagList.add(FLORIST)); + } + + @Test + public void setTag_nullTargetTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTagList.setTag(null, FLORIST)); + } + + @Test + public void setTag_nullEditedTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTagList.setTag(FLORIST, null)); + } + + @Test + public void setTag_targetTagNotInList_throwsTagNotFoundException() { + assertThrows(TagNotFoundException.class, () -> uniqueTagList.setTag(FLORIST, FLORIST)); + } + + @Test + public void setTag_editedTagIsSameTag_success() { + uniqueTagList.add(FLORIST); + uniqueTagList.setTag(FLORIST, FLORIST); + UniqueTagList expectedUniqueTagList = new UniqueTagList(); + expectedUniqueTagList.add(FLORIST); + assertEquals(expectedUniqueTagList, uniqueTagList); + } + + @Test + public void setTag_editedTagHasDifferentIdentity_success() { + uniqueTagList.add(FLORIST); + uniqueTagList.setTag(FLORIST, PHOTOGRAPHER); + UniqueTagList expectedUniqueTagList = new UniqueTagList(); + expectedUniqueTagList.add(PHOTOGRAPHER); + assertEquals(expectedUniqueTagList, uniqueTagList); + } + + @Test + public void setTag_editedTagHasNonUniqueIdentity_throwsDuplicateTagException() { + uniqueTagList.add(FLORIST); + uniqueTagList.add(PHOTOGRAPHER); + assertThrows(DuplicateTagException.class, () -> uniqueTagList.setTag(FLORIST, PHOTOGRAPHER)); + } + + @Test + public void remove_nullTag_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTagList.remove(null)); + } + + @Test + public void remove_tagDoesNotExist_throwsTagNotFoundException() { + assertThrows(TagNotFoundException.class, () -> uniqueTagList.remove(FLORIST)); + } + + @Test + public void remove_existingTag_removesTag() { + uniqueTagList.add(FLORIST); + uniqueTagList.remove(FLORIST); + UniqueTagList expectedUniqueTagList = new UniqueTagList(); + assertEquals(expectedUniqueTagList, uniqueTagList); + } + + @Test + public void setTags_nullUniqueTagList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTagList.setTags((List) null)); + } + + @Test + public void setTags_uniqueTagList_replacesOwnListWithProvidedUniqueTagList() { + uniqueTagList.add(FLORIST); + List tagList = Collections.singletonList(PHOTOGRAPHER); + uniqueTagList.setTags(tagList); + UniqueTagList expectedUniqueTagList = new UniqueTagList(); + expectedUniqueTagList.add(PHOTOGRAPHER); + assertEquals(expectedUniqueTagList, uniqueTagList); + } + + @Test + public void setTags_listWithDuplicateTags_throwsDuplicateTagException() { + List listWithDuplicateTags = Arrays.asList(FLORIST, FLORIST); + assertThrows(DuplicateTagException.class, () -> uniqueTagList.setTags(listWithDuplicateTags)); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> + uniqueTagList.asUnmodifiableObservableList().remove(0)); + } + + @Test + public void toStringTest() { + assertEquals(uniqueTagList.asUnmodifiableObservableList().toString(), uniqueTagList.toString()); + } +} From 2c8d3a9b67fbab5fa495126862e1d69407cb31f9 Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 21:58:46 +0800 Subject: [PATCH 10/13] Fix check style issues in test class Fix check style issues in UniqueTagListTest.java --- .../address/model/tag/UniqueTagListTest.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/test/java/seedu/address/model/tag/UniqueTagListTest.java b/src/test/java/seedu/address/model/tag/UniqueTagListTest.java index 2b1f09aa0bf..ba1fefe7c6d 100644 --- a/src/test/java/seedu/address/model/tag/UniqueTagListTest.java +++ b/src/test/java/seedu/address/model/tag/UniqueTagListTest.java @@ -1,17 +1,20 @@ package seedu.address.model.tag; -import org.junit.jupiter.api.Test; -import seedu.address.model.tag.exceptions.DuplicateTagException; -import seedu.address.model.tag.exceptions.TagNotFoundException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.FLORIST; +import static seedu.address.testutil.TypicalPersons.PHOTOGRAPHER; import java.util.Arrays; import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.FLORIST; -import static seedu.address.testutil.TypicalPersons.PHOTOGRAPHER; +import org.junit.jupiter.api.Test; + +import seedu.address.model.tag.exceptions.DuplicateTagException; +import seedu.address.model.tag.exceptions.TagNotFoundException; public class UniqueTagListTest { private final UniqueTagList uniqueTagList = new UniqueTagList(); From a43b61d35f8723f4f52258a885866bd0383167da Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 22:05:59 +0800 Subject: [PATCH 11/13] Fix test cases Fix test case in TagTest.java. --- .../java/seedu/address/model/tag/TagTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java index 16151b7dee8..6a7e09be0cc 100644 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ b/src/test/java/seedu/address/model/tag/TagTest.java @@ -26,30 +26,30 @@ public void isValidTagName() { assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); } - @Test - public void canBeDeleted() { - Tag florist = FLORIST; - assertTrue(FLORIST.canBeDeleted()); - } - @Test public void noPersonsTaggedCheck() { - Tag florist = FLORIST; - assertEquals(0, FLORIST.getNumberOfPersonsTagged()); + Tag florist = new Tag(new TagName("Florist")); + assertEquals(0, florist.getNumberOfPersonsTagged()); } @Test public void incrementTaggedCount() { - Tag florist = FLORIST; + Tag florist = new Tag(new TagName("Florist")); florist.increaseTaggedCount(); assertEquals(1, florist.getNumberOfPersonsTagged()); } @Test public void decrementTaggedCount() { - Tag florist = FLORIST; + Tag florist = new Tag(new TagName("Florist"));; florist.increaseTaggedCount(); florist.decreaseTaggedCount(); assertEquals(0, florist.getNumberOfPersonsTagged()); } + + @Test + public void canBeDeleted() { + Tag florist = new Tag(new TagName("Florist")); + assertTrue(florist.canBeDeleted()); + } } From 7cfc2396c8982d371ae05114d23824adc3bcf593 Mon Sep 17 00:00:00 2001 From: Han Bin Date: Wed, 9 Oct 2024 22:09:51 +0800 Subject: [PATCH 12/13] Fix check style issues in test class Fix check style issues in TagTest.java --- src/test/java/seedu/address/model/tag/TagTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java index 6a7e09be0cc..6529096dc3a 100644 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ b/src/test/java/seedu/address/model/tag/TagTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.FLORIST; import org.junit.jupiter.api.Test; From fb3b69da5e4daac974a63819f3d38e57407e74ef Mon Sep 17 00:00:00 2001 From: "LAPTOP-37SSO5I3\\Han Bin" Date: Thu, 10 Oct 2024 09:59:19 +0800 Subject: [PATCH 13/13] Edit command usage message Edit command usage message for CreateTagCommand.java to better reflect the functionality. --- .../java/seedu/address/logic/commands/CreateTagCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/logic/commands/CreateTagCommand.java b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java index 3a890b8dec3..7639c1e8439 100644 --- a/src/main/java/seedu/address/logic/commands/CreateTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java @@ -16,7 +16,7 @@ public class CreateTagCommand extends Command { public static final String COMMAND_WORD = "create-tag"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tag to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a tag in the address book. " + "Parameters: " + PREFIX_TAG + "TAG\n" + "Example: " + COMMAND_WORD + " "