Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Untag feature #64

Merged
merged 12 commits into from
Oct 10, 2024
115 changes: 115 additions & 0 deletions src/main/java/seedu/address/logic/commands/UntagCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package seedu.address.logic.commands;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;

/**
* Removes a tag associated with an existing person in the address book.
*/
public class UntagCommand extends Command {

public static final String COMMAND_WORD = "untag";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid.";
public static final String MESSAGE_TAG_NOT_FOUND = "Some tags were not found in the person's tag list.";
public static final String MESSAGE_REMOVE_TAG_SUCCESS = "Removed tag(s) %1$s from %2$s.";

public static final String MESSAGE_USAGE = COMMAND_WORD
+ ": Removes one or multiple tags from the person identified "
+ "by the index number used in the last person listing.\n"
+ "Parameters: INDEX (must be a positive integer) "
+ "[TAG]... (can specify multiple tags)\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ "florist photographer.";

private final Index index;
private final List<Tag> tagsToRemove;
DanzaSeah marked this conversation as resolved.
Show resolved Hide resolved



/**
* Constructs an UntagCommand to remove tags from a person.
*
* @param index The index of the person in the person list.
* @param tagsToRemove The list of tags to be removed.
*/
public UntagCommand(Index index, List<Tag> tagsToRemove) {
this.index = index;
this.tagsToRemove = tagsToRemove;
}

/**
* Generates a command execution success message showing the removed tags and the person.
*
* @param personToEdit The person from whom the tags were removed.
* @return A success message indicating the tags that were removed and the name of the person.
*/
private String generateSuccessMessage(Person personToEdit) {
String removedTags = tagsToRemove.stream()
.map(tag -> tag.toString().replaceAll("[\\[\\]]", ""))
.collect(Collectors.joining(", "));
return String.format(MESSAGE_REMOVE_TAG_SUCCESS, removedTags, personToEdit.getName().toString());
}


@Override
public CommandResult execute(Model model) throws CommandException {
List<Person> lastShownList = model.getFilteredPersonList();

if (index.getZeroBased() >= lastShownList.size()) {
throw new CommandException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}

Person personToEdit = lastShownList.get(index.getZeroBased());
Set<Tag> updatedTags = new HashSet<>(personToEdit.getTags());

if (personToEdit.getTags().isEmpty()) {
throw new CommandException(MESSAGE_TAG_NOT_FOUND);
}

if (tagsToRemove.isEmpty()) {
throw new CommandException(MESSAGE_TAG_NOT_FOUND);
}

if (!updatedTags.containsAll(tagsToRemove)) {
throw new CommandException(MESSAGE_TAG_NOT_FOUND);
}
updatedTags.removeAll(tagsToRemove);

Person editedPerson = new Person(
personToEdit.getName(),
personToEdit.getPhone(),
personToEdit.getEmail(),
personToEdit.getAddress(),
updatedTags);

model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);

return new CommandResult(generateSuccessMessage(personToEdit));
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;

Check warning on line 102 in src/main/java/seedu/address/logic/commands/UntagCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/UntagCommand.java#L102

Added line #L102 was not covered by tests
}

if (!(other instanceof UntagCommand)) {
return false;

Check warning on line 106 in src/main/java/seedu/address/logic/commands/UntagCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/UntagCommand.java#L106

Added line #L106 was not covered by tests
}

UntagCommand otherCommand = (UntagCommand) other;
return index.equals(otherCommand.index)
&& tagsToRemove.equals(otherCommand.tagsToRemove);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.UntagCommand;
import seedu.address.logic.parser.exceptions.ParseException;






/**
* Parses user input.
*/
Expand Down Expand Up @@ -77,6 +83,9 @@
case HelpCommand.COMMAND_WORD:
return new HelpCommand();

case UntagCommand.COMMAND_WORD:
return new UntagCommandParser().parse(arguments);

Check warning on line 87 in src/main/java/seedu/address/logic/parser/AddressBookParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/AddressBookParser.java#L87

Added line #L87 was not covered by tests

default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/seedu/address/logic/parser/UntagCommandParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package seedu.address.logic.parser;

import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import seedu.address.commons.core.index.Index;
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.logic.commands.UntagCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.tag.Tag;

/**
* Parses input arguments and creates a new UntagCommand object.
*/
public class UntagCommandParser implements Parser<UntagCommand> {

/**
* Parses the given String of arguments in the context of the UntagCommand
* and returns a UntagCommand object for execution.
*
* @param args the user input string containing the index and tags to be removed
* @return a new UntagCommand object that contains the parsed index and list of tags
* @throws ParseException if the input does not conform to the expected format (i.e., invalid index or missing tags)
*/
public UntagCommand parse(String args) throws ParseException {
requireNonNull(args);

// Trim and split input to separate index from tags
String trimmedArgs = args.trim();
String[] splitArgs = trimmedArgs.split("\\s+", 2);

if (splitArgs.length < 2) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE));
}

Index index;
try {
// Parse the index from the first part of the split arguments
index = ParserUtil.parseIndex(splitArgs[0]);
} catch (IllegalValueException ive) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE), ive);
}

// Split the second part (tags) by whitespace
String[] tagValues = splitArgs[1].split("\\s+");
if (tagValues.length == 0) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE));

Check warning on line 51 in src/main/java/seedu/address/logic/parser/UntagCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/UntagCommandParser.java#L51

Added line #L51 was not covered by tests
}

// Convert tag values to Tag objects
List<Tag> tags = Arrays.stream(tagValues)
.map(Tag::new)
.collect(Collectors.toList());

return new UntagCommand(index, tags);
}
}
131 changes: 131 additions & 0 deletions src/test/java/seedu/address/logic/commands/UntagCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package seedu.address.logic.commands;

import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.junit.jupiter.api.Test;

import seedu.address.commons.core.index.Index;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;

public class UntagCommandTest {

private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());


@Test
public void execute_validTagsUnfilteredList_success() {
Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
List<Tag> tagsToRemove = Arrays.asList(new Tag("friends"));

UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tagsToRemove);

String expectedMessage = String.format(UntagCommand.MESSAGE_REMOVE_TAG_SUCCESS,
"friends", personToEdit.getName().toString());

Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
Set<Tag> updatedTags = new HashSet<>(personToEdit.getTags());
updatedTags.removeAll(tagsToRemove);
Person editedPerson = new Person(
personToEdit.getName(),
personToEdit.getPhone(),
personToEdit.getEmail(),
personToEdit.getAddress(),
updatedTags);
expectedModel.setPerson(personToEdit, editedPerson);

CommandTestUtil.assertCommandSuccess(untagCommand, model, expectedMessage, expectedModel);
}


@Test
public void execute_validMultipleTagsUnfilteredList_success() {
Person personWithTags = new Person(
new seedu.address.model.person.Name("Test Person"),
new seedu.address.model.person.Phone("99999999"),
new seedu.address.model.person.Email("[email protected]"),
new seedu.address.model.person.Address("123, Test Street"),
new HashSet<>(Arrays.asList(new Tag("friends"), new Tag("owesMoney")))
);
model.setPerson(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()), personWithTags);
List<Tag> tagsToRemove = Arrays.asList(new Tag("friends"), new Tag("owesMoney"));
UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tagsToRemove);
String expectedMessage = String.format(UntagCommand.MESSAGE_REMOVE_TAG_SUCCESS,
"friends, owesMoney", personWithTags.getName().toString());

// Create the expected model with the updated tags (i.e., an empty set of tags)
Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
Set<Tag> updatedTags = new HashSet<>(personWithTags.getTags());
updatedTags.removeAll(tagsToRemove);
Person editedPerson = new Person(
personWithTags.getName(),
personWithTags.getPhone(),
personWithTags.getEmail(),
personWithTags.getAddress(),
updatedTags);
expectedModel.setPerson(personWithTags, editedPerson);

CommandTestUtil.assertCommandSuccess(untagCommand, model, expectedMessage, expectedModel);
}


@Test
public void execute_nonExistentTag_failure() {
List<Tag> tagsToRemove = Arrays.asList(new Tag("nonExistentTag"));
UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tagsToRemove);
String expectedMessage = UntagCommand.MESSAGE_TAG_NOT_FOUND;
CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}


@Test
public void execute_noTagsSpecified_failure() {
// No tags specified to remove
List<Tag> emptyTagsToRemove = Arrays.asList();
UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, emptyTagsToRemove);
String expectedMessage = UntagCommand.MESSAGE_TAG_NOT_FOUND;
CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}


@Test
public void execute_invalidIndexUnfilteredList_failure() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
List<Tag> tagsToRemove = Arrays.asList(new Tag("friends"));

UntagCommand untagCommand = new UntagCommand(outOfBoundIndex, tagsToRemove);

String expectedMessage = UntagCommand.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;

CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}


@Test
public void execute_personWithoutTags_failure() {
Person personWithoutTags = new Person(
new seedu.address.model.person.Name("Test Person"),
new seedu.address.model.person.Phone("99999999"),
new seedu.address.model.person.Email("[email protected]"),
new seedu.address.model.person.Address("123, Test Street"),
new HashSet<>() // No tags
);

model.setPerson(model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()), personWithoutTags);
List<Tag> tagsToRemove = Arrays.asList(new Tag("friends"));
UntagCommand untagCommand = new UntagCommand(INDEX_SECOND_PERSON, tagsToRemove);
String expectedMessage = UntagCommand.MESSAGE_TAG_NOT_FOUND;

CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}
}
Original file line number Diff line number Diff line change
@@ -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.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;

import java.util.Arrays;

import org.junit.jupiter.api.Test;

import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.UntagCommand;
import seedu.address.model.tag.Tag;

public class UntagCommandParserTest {

private UntagCommandParser parser = new UntagCommandParser();

@Test
public void parse_validArgs_returnsUntagCommand() {
Index targetIndex = Index.fromOneBased(1);

// Expected tags
Tag tag1 = new Tag("friends");
Tag tag2 = new Tag("owesMoney");

UntagCommand expectedCommand = new UntagCommand(targetIndex, Arrays.asList(tag1, tag2));

String userInput = "1 friends owesMoney";

assertParseSuccess(parser, userInput, expectedCommand);
}

@Test
public void parse_invalidArgs_throwsParseException() {
// Invalid index (non-numeric)
assertParseFailure(parser, "a friends", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
UntagCommand.MESSAGE_USAGE));

// Missing tags (no tags specified)
assertParseFailure(parser, "1", String.format(MESSAGE_INVALID_COMMAND_FORMAT,
UntagCommand.MESSAGE_USAGE));
}
}