diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 25ae155c495..7e450e058f3 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -543,17 +543,22 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli **Extensions** -* 1a. User inputs a non-alphanumeric tag. +* 1a. User inputs incomplete data. - * 1a1. KeepInTouch shows a message indicating that tags should be alphanumeric. + * 1a1. KeepInTouch shows a message indicating incomplete data. - Use case resumes at step 1. + Use case ends. -* 1b. User inputs a contact that does not exist. +* 1b. User inputs a non-alphanumeric tag. + * 1b1. KeepInTouch shows a message indicating that tags should be alphanumeric. + + Use case ends. - * 1b1. KeepInTouch shows a message indicating the contact cannot be found. +* 1c. User inputs a contact that does not exist. - Use case resumes at step 1. + * 1c1. KeepInTouch shows a message indicating that the contact cannot be found. + + Use case ends. **Use case: UC12 - Delete tags from a contact** @@ -572,15 +577,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. -* 1b. User inputs a contact that does not exist. - - * 1b1. KeepInTouch shows a message indicating that the contact cannot be found. +* 1b. User inputs a non-alphanumeric tag. + * 1b1. KeepInTouch shows a message indicating that tags should be alphanumeric. Use case ends. -* 1c. User inputs a tag that does not exist. +* 1c. User inputs a contact that does not exist. - * 1c1. KeepInTouch shows a message indicating that the tags cannot be found. + * 1c1. KeepInTouch shows a message indicating that the contact cannot be found. Use case ends. @@ -600,7 +604,6 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 1a1. KeepInTouch shows a message indicating incomplete data. Use case ends. - * 1b. User inputs a contact that does not exist. * 1b1. KeepInTouch shows a message indicating that the contact cannot be found. @@ -609,7 +612,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 1c. User inputs a tag that does not exist. - * 1c1. KeepInTouch shows a message indicating that the tags cannot be found. + * 1c1. KeepInTouch shows a message indicating that the tag cannot be found. Use case ends. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index aea3eec985d..5882f5e565c 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -17,6 +17,8 @@ If you can type fast, KeepInTouch can get your contact management tasks done fas * [Adding a person: `add contact`](#adding-a-person--add-contact) * [Listing all persons: `list contact`](#listing-all-persons--list-contact) * [Deleting a person: `delete contact`](#deleting-a-person--delete-contact) + * [Adding tags: `add tag`](#adding-tags--add-tag) + * [Deleting tags: `delete tag`](#deleting-tags--delete-tag) * [Adding a note: `add note`](#adding-notes-to-a-contact--add-note) * [Listing all notes: `list notes`](#listing-all-notes--list-notes) * [Deleting a note: `delete note`](#deleting-a-note--delete-note) @@ -71,6 +73,9 @@ If you can type fast, KeepInTouch can get your contact management tasks done fas * `CONTACT_ID` is the number that is on the left of the person's name in each person card. +* Items with `…`​ after them can be used multiple times.
+ e.g. `[-t TAGNAME]…​` can be used as `-t frontend`, `-t frontend -t java` etc. + * Parameters can be in any order.
e.g. if the command specifies `-n NAME -t NOTE_TITLE`, `-t NOTE_TITLE -n NAME` is also acceptable. @@ -117,6 +122,39 @@ Format: `delete contact NAME` Examples: * `list contact` followed by `delete contact Aaron` deletes the contact with the name Aaron. +### Adding tags : `add tag` + +Adds one or more tags to a contact. + +Format: `add tag -id CONTACT_ID -t TAGNAME...` + +* Adds one or more tags to a contact. +* Duplicates are accepted but only unique tags will be added. + +Requirements: +* `TAGNAME` must be alphanumeric, with no spaces. + +Examples: +* `add tag -id 1 -t frontend` adds a tag with tag name "frontend" to the first contact in the contact list. +* `add tag -id 1 -t frontend -t java` adds two tags with tag name "frontend" and "java" to the first contact in the contact list. + + +### Deleting tags : `delete tag` + +Deletes one or more tags to a contact. + +Format: `delete tag -id CONTACT_ID -t TAGNAME...` + +* Deletes one or more tags to a contact, regardless if the tag exists in the contact or not. +* Duplicates are accepted but only unique tags will be added. + +Requirements: +* `TAGNAME` must be alphanumeric, with no spaces. + +Examples: +* `delete tag -id 1 -t frontend` deletes a tag with tag name "frontend" from the first contact in the contact list. +* `add tag -id 1 -t frontend -t java` deletes two tags with tag name "frontend" and "java" from the first contact in the contact list. + ### Adding notes to a contact: `add note` Adds a note to a contact from the contact list. @@ -232,6 +270,8 @@ Action | Format, Examples **Add Contact** | `add contact -n NAME -p PHONE_NUMBER -a ADDRESS -e EMAIL`
e.g., `add contact -n Aaron -p 12345678 -a Baker Street 12 -e aaron123@gmail.com` **Delete Contact** | `delete contact NAME`
e.g., `delete contact Aaron` **List Contact** | `list contact` +**Add Tag** | `add tag -id CONTACT_ID -t TAGNAME`
eg., `add tag -id 1 -t frontend` +**Delete Tag** | `delete tag -id CONTACT_ID -t TAGNAME`
eg., `delete tag -id 1 -t frontend` **Add Note** | `add note -n NAME -t NOTE_TITLE -c NOTE_CONTENT`
e.g., `add note -n Daniel -t Open Position -e Applications for SWE full-time positions will open soon` **Delete Note** | `delete note -n NAME -t NOTE_TITLE`
e.g., `delete note -n Aaron -t Meeting Topics` **List Notes** | `list notes` diff --git a/src/main/java/seedu/address/logic/commands/AddPersonCommand.java b/src/main/java/seedu/address/logic/commands/AddPersonCommand.java index f760051044e..abc99454606 100644 --- a/src/main/java/seedu/address/logic/commands/AddPersonCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddPersonCommand.java @@ -5,6 +5,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -26,12 +27,14 @@ public class AddPersonCommand extends AddCommand { + PREFIX_NAME + " NAME " + PREFIX_PHONE + " PHONE " + PREFIX_EMAIL + " EMAIL " - + PREFIX_ADDRESS + " ADDRESS" + "\n" + + PREFIX_ADDRESS + " ADDRESS " + + PREFIX_TAG + " TAGNAME" + "\n" + "Example: " + COMMAND_WORD + " " + SECONDARY_COMMAND_WORD + PREFIX_NAME + " John Doe " + PREFIX_PHONE + " 98765432 " + PREFIX_EMAIL + " johnd@example.com " - + PREFIX_ADDRESS + " 311, Clementi Ave 2, #02-25 "; + + PREFIX_ADDRESS + " 311, Clementi Ave 2, #02-25 " + + PREFIX_TAG + " frontend "; public static final String MESSAGE_SUCCESS = "New contact added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This contact already exists in the contact list"; diff --git a/src/main/java/seedu/address/logic/commands/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java new file mode 100644 index 00000000000..1d8e52be160 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java @@ -0,0 +1,79 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Set; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.ContactID; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * The command handler for {@code add tag} command + */ +public class AddTagCommand extends AddCommand { + public static final String SECONDARY_COMMAND_WORD = "tag"; + public static final String MESSAGE_SUCCESS = "New tags added: "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + " " + SECONDARY_COMMAND_WORD + + ": Adds tags to a contact from the contact list.\n" + + "Usage: add tag -id CONTACT_ID -t TAGNAME"; + public static final String MESSAGE_PERSON_NOT_FOUND = "Can not find the target contact with ID: "; + + private final Set toAdd; + private final int contactId; + + /** + * Creates an AddTagCommand to add the specified {@code Tag}(s). + */ + public AddTagCommand(int contactId, Set tagList) { + requireNonNull(tagList); + this.contactId = contactId; + this.toAdd = tagList; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Person person = model.findPersonByUserFriendlyId(ContactID.fromInt(this.contactId)); + if (person == null) { + throw new CommandException(MESSAGE_PERSON_NOT_FOUND + this.contactId); + } + person.addTags(this.toAdd); + + final StringBuilder builder = new StringBuilder(); + builder.append(MESSAGE_SUCCESS); + toAdd.forEach(builder::append); + + return new CommandResult(builder.toString()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddTagCommand)) { + return false; + } + + AddTagCommand otherAddNoteCommand = (AddTagCommand) other; + + boolean equalToAdd = toAdd.equals(otherAddNoteCommand.toAdd); + boolean equalContactId = (contactId == otherAddNoteCommand.contactId); + return equalToAdd && equalContactId; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .add("contactId", contactId) + .toString(); + } +} 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..eb9f94997a9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java @@ -0,0 +1,78 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Set; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.ContactID; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * The command handler for {@code delete tag} command + */ +public class DeleteTagCommand extends DeleteCommand { + public static final String SECONDARY_COMMAND_WORD = "tag"; + public static final String MESSAGE_USAGE = COMMAND_WORD + " " + + SECONDARY_COMMAND_WORD + ": Delete one or more tags from a contact.\n" + + "Usage: delete tag -id CONTACT_ID -t TAGNAME"; + public static final String MESSAGE_PERSON_NOT_FOUND = "Can not find the target contact with ID: "; + public static final String MESSAGE_SUCCESS = "Successfully deleted tags: "; + + private final Set toDelete; + private final int contactId; + + /** + * Creates an DeleteTagCommand to delete the specified {@code Tag}(s). + */ + public DeleteTagCommand(int contactId, Set tagList) { + requireNonNull(tagList); + this.contactId = contactId; + this.toDelete = tagList; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Person person = model.findPersonByUserFriendlyId(ContactID.fromInt(this.contactId)); + if (person == null) { + throw new CommandException(MESSAGE_PERSON_NOT_FOUND + this.contactId); + } + person.removeTags(toDelete); + + final StringBuilder builder = new StringBuilder(); + builder.append(MESSAGE_SUCCESS); + toDelete.forEach(builder::append); + + return new CommandResult(builder.toString()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteTagCommand)) { + return false; + } + + DeleteTagCommand otherAddNoteCommand = (DeleteTagCommand) other; + + boolean equalToDelete = toDelete.equals(otherAddNoteCommand.toDelete); + boolean equalContactId = (contactId == otherAddNoteCommand.contactId); + return equalToDelete && equalContactId; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toDelete", toDelete) + .add("contactId", contactId) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index ecae8d1be41..aeab3fcb1cd 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -6,6 +6,7 @@ import seedu.address.logic.commands.AddEventCommand; import seedu.address.logic.commands.AddNoteCommand; import seedu.address.logic.commands.AddPersonCommand; +import seedu.address.logic.commands.AddTagCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -24,6 +25,8 @@ public AddCommand parse(String userInput) throws ParseException { return new AddEventCommandParser().parse(args); case AddNoteCommand.SECONDARY_COMMAND_WORD: return new AddNoteCommandParser().parse(args); + case AddTagCommand.SECONDARY_COMMAND_WORD: + return new AddTagCommandParser().parse(args); default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java new file mode 100644 index 00000000000..eff1a20282b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java @@ -0,0 +1,41 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.Messages.MESSAGE_INVALID_INTEGER_ARGUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; + +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddTagCommand object + */ +public class AddTagCommandParser implements Parser { + @Override + public AddTagCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_PERSON_ID, + PREFIX_TAG); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_PERSON_ID, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTagCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_PERSON_ID); + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + int contactId = -1; + try { + contactId = Integer.parseInt(argMultimap.getValue(PREFIX_PERSON_ID).get()); + } catch (NumberFormatException e) { + throw new ParseException(String.format(MESSAGE_INVALID_INTEGER_ARGUMENT, e.getMessage())); + } + + return new AddTagCommand(contactId, tagList); + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 8d5c3c6d943..4ac76b6d0b6 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -6,6 +6,7 @@ import seedu.address.logic.commands.DeleteEventCommand; import seedu.address.logic.commands.DeleteNoteCommand; import seedu.address.logic.commands.DeletePersonCommand; +import seedu.address.logic.commands.DeleteTagCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -23,6 +24,8 @@ public DeleteCommand parse(String userInput) throws ParseException { return new DeleteNoteCommandParser().parse(args); case DeleteEventCommand.SECONDARY_COMMAND_WORD: return new DeleteEventCommandParser().parse(args); + case DeleteTagCommand.SECONDARY_COMMAND_WORD: + return new DeleteTagCommandParser().parse(args); default: 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..b556bae7089 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java @@ -0,0 +1,42 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.Messages.MESSAGE_INVALID_INTEGER_ARGUMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON_ID; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; + +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new DeleteTagCommand object + */ +public class DeleteTagCommandParser implements Parser { + + @Override + public DeleteTagCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_PERSON_ID, + PREFIX_TAG); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_PERSON_ID, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_PERSON_ID); + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + int contactId = -1; + try { + contactId = Integer.parseInt(argMultimap.getValue(PREFIX_PERSON_ID).get()); + } catch (NumberFormatException e) { + throw new ParseException(String.format(MESSAGE_INVALID_INTEGER_ARGUMENT, e.getMessage())); + } + + return new DeleteTagCommand(contactId, tagList); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index e1e64d890ef..4fe54bb9d9d 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -96,6 +96,7 @@ public ObservableList getEvents() { /** * Adds a note to this person + * * @param note The note to be added. */ public void addNote(Note note) { @@ -103,10 +104,29 @@ public void addNote(Note note) { } /** - * Remove a note by its user-friendly id + * Adds a set of {@code Tag} to this person + * + * @param tags The tags to be added. + */ + public void addTags(Set tags) { + this.tags.addAll(tags); + } + + /** + * Removes a set of {@code Tag} from this person + * + * @param tags The tags to be removed. + */ + public void removeTags(Set tags) { + tags.forEach(tag -> this.tags.remove(tag)); + } + + /** + * Removes a note by its user-friendly id + * * @param id The id of the note you want to remove * @return The note object that is just deleted if the operation is successful - * or {@code null} if the note with this name does not exist + * or {@code null} if the note with this name does not exist */ public Note removeNoteByUserFriendlyId(NoteID id) { return this.removeNoteByIndex(id.getId() - 1); @@ -121,6 +141,7 @@ private Note removeNoteByIndex(int index) { /** * Adds an event to this person. + * * @param event The event to be added. */ public void addEvent(Event event) { @@ -128,10 +149,11 @@ public void addEvent(Event event) { } /** - * Remove an event by its user-friendly id + * Removes an event by its user-friendly id + * * @param id The id of the event you want to remove * @return The event object that is just deleted if the operation is successful - * or {@code null} if the event with this name does not exist + * or {@code null} if the event with this name does not exist */ public Event removeEventByUserFriendlyId(EventID id) { return this.removeEventByIndex(id.getId() - 1); diff --git a/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java b/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java new file mode 100644 index 00000000000..e4d2a874e85 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddTagCommandTest.java @@ -0,0 +1,95 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.ThrowingSupplier; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.TagBuilder; + +public class AddTagCommandTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @Test + public void execute_correctCommand_success() throws CommandException { + int personId = 1; + final Set tagSet = new TagBuilder().inSet(); + + assertCommandSuccessWithFeedback(() -> new AddTagCommand(personId, tagSet) + .execute(model), AddTagCommand.MESSAGE_SUCCESS + new TagBuilder().build().toString()); + } + + @Test + public void execute_personNotExist_fails() throws CommandException { + int personId = 99999; + final Set tagSet = new TagBuilder().inSet(); + + assertCommandFailWithFeedback(() -> new AddTagCommand(personId, tagSet) + .execute(model), AddTagCommand.MESSAGE_PERSON_NOT_FOUND + personId); + } + + private void assertCommandSuccessWithFeedback(ThrowingSupplier function, String result) { + try { + assertEquals(function.get(), new CommandResult(result)); + } catch (Throwable e) { + throw new AssertionError("Execution of command should not fail.", e); + } + } + + private void assertCommandFailWithFeedback(ThrowingSupplier function, String errResult) { + try { + function.get(); + } catch (Throwable e) { + if (!(e instanceof CommandException)) { + throw new AssertionError("Execution of command failed but not due to CommandException."); + } + assertEquals(e.getMessage(), errResult); + return; + } + throw new AssertionError("Execution of command should fail."); + } + + @Test + public void equals() { + final Set tagSetA = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + final Set tagSetB = new TagBuilder().withTag(VALID_TAG_FRIEND).inSet(); + + AddTagCommand commandA = new AddTagCommand(1, tagSetA); + AddTagCommand commandB = new AddTagCommand(1, tagSetB); + + // same object -> returns true + assertTrue(commandA.equals(commandA)); + + // same values -> returns true + AddTagCommand commandACopy = new AddTagCommand(1, tagSetA); + assertTrue(commandA.equals(commandACopy)); + + // different types -> returns false + assertFalse(commandA.equals(1)); + + // null -> returns false + assertFalse(commandA.equals(null)); + + // different person -> returns false + assertFalse(commandA.equals(commandB)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 5c7f59cefd7..c6f4cfd436f 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -40,6 +40,7 @@ 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_PERSON_ID = "1"; public static final String VALID_NOTE_A_PERSON_ID = "1"; public static final String VALID_NOTE_A_TITLE = "Preferred Qualifications"; public static final String VALID_NOTE_A_CONTENT = "Machine Learning Frameworks"; @@ -59,6 +60,7 @@ 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 PERSON_ID_DESC = " " + PREFIX_PERSON_ID + VALID_PERSON_ID; public static final String NOTE_A_PERSON_ID_DESC = " " + PREFIX_PERSON_ID + VALID_NOTE_A_PERSON_ID; public static final String NOTE_A_TITLE_DESC = " " + PREFIX_NOTE_TITLE + VALID_NOTE_A_TITLE; public static final String NOTE_A_CONTENT_DESC = " " + PREFIX_NOTE_CONTENT + VALID_NOTE_A_CONTENT; @@ -91,7 +93,8 @@ public class CommandTestUtil { /** * Executes the given {@code command}, confirms that
- * - the returned {@link CommandResult} matches {@code expectedCommandResult}
+ * - the returned {@link CommandResult} matches {@code expectedCommandResult} + *
* - the {@code actualModel} matches {@code expectedModel} */ public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, @@ -106,7 +109,8 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm } /** - * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * Convenience wrapper to + * {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} * that takes a string {@code expectedMessage}. */ public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, @@ -119,7 +123,8 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri * Executes the given {@code command}, confirms that
* - a {@code CommandException} is thrown
* - the CommandException message matches {@code expectedMessage}
- * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged + * - the address book, filtered person list and selected person in + * {@code actualModel} remain unchanged */ public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { // we are unable to defensively copy the model for comparison later, so we can @@ -131,8 +136,10 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri assertEquals(expectedAddressBook, actualModel.getAddressBook()); assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); } + /** - * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the + * Updates {@code model}'s filtered list to show only the person at the given + * {@code targetIndex} in the * {@code model}'s address book. */ public static void showPersonAtIndex(Model model, Index targetIndex) { 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..26881168ace --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteTagCommandTest.java @@ -0,0 +1,94 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.ThrowingSupplier; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.TagBuilder; + +public class DeleteTagCommandTest { + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @Test + public void execute_correctCommand_success() throws CommandException { + int personId = 1; + final Set tagSet = new TagBuilder().inSet(); + + assertCommandSuccessWithFeedback(() -> new DeleteTagCommand(personId, tagSet) + .execute(model), DeleteTagCommand.MESSAGE_SUCCESS + new TagBuilder().build().toString()); + } + + @Test + public void execute_personNotExist_fails() throws CommandException { + int personId = 99999; + final Set tagSet = new TagBuilder().inSet(); + + assertCommandFailWithFeedback(() -> new DeleteTagCommand(personId, tagSet) + .execute(model), DeleteTagCommand.MESSAGE_PERSON_NOT_FOUND + personId); + } + + private void assertCommandSuccessWithFeedback(ThrowingSupplier function, String result) { + try { + assertEquals(function.get(), new CommandResult(result)); + } catch (Throwable e) { + throw new AssertionError("Execution of command should not fail.", e); + } + } + + private void assertCommandFailWithFeedback(ThrowingSupplier function, String errResult) { + try { + function.get(); + } catch (Throwable e) { + if (!(e instanceof CommandException)) { + throw new AssertionError("Execution of command failed but not due to CommandException."); + } + assertEquals(e.getMessage(), errResult); + return; + } + throw new AssertionError("Execution of command should fail."); + } + + @Test + public void equals() { + final Set tagSetA = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + final Set tagSetB = new TagBuilder().withTag(VALID_TAG_FRIEND).inSet(); + + DeleteTagCommand commandA = new DeleteTagCommand(1, tagSetA); + DeleteTagCommand commandB = new DeleteTagCommand(1, tagSetB); + + // same object -> returns true + assertTrue(commandA.equals(commandA)); + + // same values -> returns true + DeleteTagCommand commandACopy = new DeleteTagCommand(1, tagSetA); + assertTrue(commandA.equals(commandACopy)); + + // different types -> returns false + assertFalse(commandA.equals(1)); + + // null -> returns false + assertFalse(commandA.equals(null)); + + // different person -> returns false + assertFalse(commandA.equals(commandB)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java index 54289b27d4e..94302f6bad9 100644 --- a/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java @@ -4,11 +4,14 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.Messages.MESSAGE_START_TIME_AFTER_END_TIME; import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.logic.commands.CommandTestUtil.PERSON_ID_DESC; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.ThrowingSupplier; import seedu.address.logic.commands.AddEventCommand; +import seedu.address.logic.commands.AddTagCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; @@ -21,11 +24,12 @@ public class AddEventCommandParserTest { private AddCommandParser parser = new AddCommandParser(); - @Test public void execute_correctCommand_success() throws CommandException { assertParseSuccessWithCommand(() -> parser.parse(" " + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en aa -st 00:01"), AddEventCommand.class.getName()); + assertParseSuccessWithCommand(() -> parser.parse(" " + AddTagCommand.SECONDARY_COMMAND_WORD + " " + + PERSON_ID_DESC + TAG_DESC_HUSBAND), AddTagCommand.class.getName()); } @Test @@ -39,24 +43,27 @@ public void execute_commandFormatError_fails() throws CommandException { assertParseFailedWithError(() -> parser.parse(" " + AddEventCommand.SECONDARY_COMMAND_WORD + " -...."), String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddEventCommand.MESSAGE_USAGE)); + assertParseFailedWithError(() -> parser.parse(" " + + AddTagCommand.SECONDARY_COMMAND_WORD + " -**"), + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTagCommand.MESSAGE_USAGE)); } @Test public void execute_emptyStringArguments_fails() { assertParseFailedWithError(() -> parser.parse(" " - + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en -st 12:00"), + + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en -st 12:00"), EventName.MESSAGE_CONSTRAINTS); assertParseFailedWithError(() -> parser.parse(" " - + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st"), + + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st"), EventTime.MESSAGE_NON_EMPTY); assertParseFailedWithError(() -> parser.parse(" " - + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st 12:00 -et"), + + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st 12:00 -et"), EventTime.MESSAGE_NON_EMPTY); assertParseFailedWithError(() -> parser.parse(" " - + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st 12:00 -loc"), + + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st 12:00 -loc"), EventLocation.MESSAGE_CONSTRAINTS); assertParseFailedWithError(() -> parser.parse(" " - + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st 12:00 -info"), + + AddEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -en Sample -st 12:00 -info"), EventInformation.MESSAGE_CONSTRAINTS); } diff --git a/src/test/java/seedu/address/logic/parser/AddTagParserTest.java b/src/test/java/seedu/address/logic/parser/AddTagParserTest.java new file mode 100644 index 00000000000..0de568d44d8 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddTagParserTest.java @@ -0,0 +1,71 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PERSON_ID_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PERSON_ID; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.TagBuilder; + +public class AddTagParserTest { + private AddTagCommandParser parser = new AddTagCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Set expectedTagSet = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + TAG_DESC_HUSBAND, + new AddTagCommand(Integer.parseInt(VALID_PERSON_ID), expectedTagSet)); + } + + @Test + public void parse_multipleTags_success() { + Set expectedTagSet = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + expectedTagSet.add(new TagBuilder().withTag(VALID_TAG_FRIEND).build()); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + new AddTagCommand(Integer.parseInt(VALID_PERSON_ID), expectedTagSet)); + } + + @Test + public void parse_duplicateTags_success() { + Set expectedTagSet = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + TAG_DESC_HUSBAND + TAG_DESC_HUSBAND, + new AddTagCommand(Integer.parseInt(VALID_PERSON_ID), expectedTagSet)); + } + + @Test + public void parse_invlaidTag_success() { + String expectedMessage = Tag.MESSAGE_CONSTRAINTS; + + assertParseFailure(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + INVALID_TAG_DESC, + expectedMessage); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTagCommand.MESSAGE_USAGE); + + // missing person id prefix + assertParseFailure(parser, PREAMBLE_WHITESPACE + TAG_DESC_HUSBAND, + expectedMessage); + + // missing tag prefix + assertParseFailure(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC, + expectedMessage); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteEventCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteEventCommandParserTest.java index d7cdeb107c9..db33a047344 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteEventCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteEventCommandParserTest.java @@ -3,12 +3,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.logic.commands.CommandTestUtil.PERSON_ID_DESC; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.ThrowingSupplier; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteEventCommand; +import seedu.address.logic.commands.DeleteTagCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; @@ -16,11 +19,12 @@ public class DeleteEventCommandParserTest { private DeleteCommandParser parser = new DeleteCommandParser(); - @Test public void execute_correctCommand_success() throws CommandException { assertParseSuccessWithCommand(() -> parser.parse(" " + DeleteEventCommand.SECONDARY_COMMAND_WORD + " -id 1 -eid 1"), DeleteEventCommand.class.getName()); + assertParseSuccessWithCommand(() -> parser.parse(" " + DeleteTagCommand.SECONDARY_COMMAND_WORD + " " + + PERSON_ID_DESC + TAG_DESC_HUSBAND), DeleteTagCommand.class.getName()); } @Test diff --git a/src/test/java/seedu/address/logic/parser/DeleteTagParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTagParserTest.java new file mode 100644 index 00000000000..d2f1d5f475b --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteTagParserTest.java @@ -0,0 +1,71 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PERSON_ID_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PERSON_ID; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.TagBuilder; + +public class DeleteTagParserTest { + private DeleteTagCommandParser parser = new DeleteTagCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Set expectedTagSet = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + TAG_DESC_HUSBAND, + new DeleteTagCommand(Integer.parseInt(VALID_PERSON_ID), expectedTagSet)); + } + + @Test + public void parse_multipleTags_success() { + Set expectedTagSet = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + expectedTagSet.add(new TagBuilder().withTag(VALID_TAG_FRIEND).build()); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + new DeleteTagCommand(Integer.parseInt(VALID_PERSON_ID), expectedTagSet)); + } + + @Test + public void parse_duplicateTags_success() { + Set expectedTagSet = new TagBuilder().withTag(VALID_TAG_HUSBAND).inSet(); + + assertParseSuccess(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + TAG_DESC_HUSBAND + TAG_DESC_HUSBAND, + new DeleteTagCommand(Integer.parseInt(VALID_PERSON_ID), expectedTagSet)); + } + + @Test + public void parse_invlaidTag_success() { + String expectedMessage = Tag.MESSAGE_CONSTRAINTS; + + assertParseFailure(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC + INVALID_TAG_DESC, + expectedMessage); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE); + + // missing person id prefix + assertParseFailure(parser, PREAMBLE_WHITESPACE + TAG_DESC_HUSBAND, + expectedMessage); + + // missing tag prefix + assertParseFailure(parser, PREAMBLE_WHITESPACE + PERSON_ID_DESC, + expectedMessage); + } +} diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java index 31a10d156c9..5a45cec8ccb 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/address/model/person/PersonTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.TagBuilder; public class PersonTest { @@ -90,6 +91,19 @@ public void equals() { assertFalse(ALICE.equals(editedAlice)); } + @Test + public void addTag_validSet_success() { + ALICE.addTags(new TagBuilder().inSet()); + assertTrue(ALICE.getTags().contains(new TagBuilder().build())); + } + + @Test + public void deleteTag_validSet_success() { + ALICE.addTags(new TagBuilder().inSet()); + ALICE.removeTags(new TagBuilder().inSet()); + assertFalse(ALICE.getTags().contains(new TagBuilder().build())); + } + @Test public void toStringMethod() { String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone() diff --git a/src/test/java/seedu/address/testutil/TagBuilder.java b/src/test/java/seedu/address/testutil/TagBuilder.java new file mode 100644 index 00000000000..bc61774313e --- /dev/null +++ b/src/test/java/seedu/address/testutil/TagBuilder.java @@ -0,0 +1,53 @@ +package seedu.address.testutil; + +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.tag.Tag; + +/** + * A utility class to help with building Tag objects. + */ +public class TagBuilder { + public static final String DEFAULT_TAG = "Frontend"; + + private String tag; + + /** + * Creates a {@code TagBuilder} with the default details. + */ + public TagBuilder() { + this.tag = DEFAULT_TAG; + } + + /** + * Initializes the TagBuilder with the data of {@code tagToCopy}. + */ + public TagBuilder(Tag tagToCopy) { + this.tag = tagToCopy.tagName; + } + + /** + * Sets the tag that we are building. + */ + public TagBuilder withTag(String tag) { + this.tag = tag; + return this; + } + + /** + * Returns the built {@code Tag} in a {@code Set}. + */ + public Set inSet() { + final Set tagSet = new HashSet<>(); + tagSet.add(this.build()); + return tagSet; + } + + /** + * Returns the built {@code Tag}. + */ + public Tag build() { + return new Tag(this.tag); + } +}