diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..bed459cde41 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -9,5 +9,7 @@ public class Messages { public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_PERSON_DETAILS_FOUND = "Showing details of %1$s"; + public static final String MESSAGE_PERSON_DETAILS_NOT_FOUND = "Person with name %1$s not found!"; } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..3e348c2a353 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -17,13 +17,17 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + /** The application should show the details component in the ui. */ + private final boolean showDetails; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean showDetails) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.showDetails = showDetails; } /** @@ -31,7 +35,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false); } public String getFeedbackToUser() { @@ -46,6 +50,10 @@ public boolean isExit() { return exit; } + public boolean isShowDetails() { + return showDetails; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/logic/commands/DetailsCommand.java b/src/main/java/seedu/address/logic/commands/DetailsCommand.java new file mode 100644 index 00000000000..ce7c2b65b2f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DetailsCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.NameEqualKeywordPredicate; + +/** + * Finds and displays the details of person whose name matches the keyword exactly. + * Keyword matching is exact and case sensitive. + */ +public class DetailsCommand extends Command { + + public static final String COMMAND_WORD = "details"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Displays the details of a member from the " + + "specified keywords (case-sensitive and exact match).\n" + + "Parameters: NAME (case-sensitive)\n" + + "Example: " + COMMAND_WORD + " Xiao Ming"; + + private final NameEqualKeywordPredicate predicate; + + public DetailsCommand(NameEqualKeywordPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + assert model.getFilteredPersonList().size() < 2; + if (model.getFilteredPersonList().size() == 0) { + return new CommandResult( + String.format(Messages.MESSAGE_PERSON_DETAILS_NOT_FOUND, predicate), + false, false, true); + } else { + return new CommandResult( + String.format(Messages.MESSAGE_PERSON_DETAILS_FOUND, predicate), + false, false, true); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DetailsCommand // instanceof handles nulls + && predicate.equals(((DetailsCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..acac9a21374 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false); } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..07d26e2a23c 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false); } } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..cad7e8058ff 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -10,6 +10,7 @@ import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DetailsCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; @@ -53,6 +54,9 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case DetailsCommand.COMMAND_WORD: + return new DetailsCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); diff --git a/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java b/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java new file mode 100644 index 00000000000..ecf739a2191 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.DetailsCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameEqualKeywordPredicate; + +/** + * Parses input arguments and creates a new DetailsCommand object + */ +public class DetailsCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DetailsCommand + * and returns a DetailsCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DetailsCommand parse(String args) throws ParseException { + try { + Name name = ParserUtil.parseName(args); + return new DetailsCommand(new NameEqualKeywordPredicate(name)); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DetailsCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/model/person/NameEqualKeywordPredicate.java b/src/main/java/seedu/address/model/person/NameEqualKeywordPredicate.java new file mode 100644 index 00000000000..1d20f81d16a --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameEqualKeywordPredicate.java @@ -0,0 +1,32 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Person}'s {@code Name} matches the keyword given exactly. + */ +public class NameEqualKeywordPredicate implements Predicate { + private final Name keyword; + + public NameEqualKeywordPredicate(Name keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + return keyword.equals(person.getName()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameEqualKeywordPredicate // instanceof handles nulls + && keyword.equals(((NameEqualKeywordPredicate) other).keyword)); // state check + } + + @Override + public String toString() { + return this.keyword.toString(); + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..6f86b3cb037 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -186,6 +186,8 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + personListPanel.setShowDetails(commandResult.isShowDetails()); + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/seedu/address/ui/PersonDetailsCard.java b/src/main/java/seedu/address/ui/PersonDetailsCard.java new file mode 100644 index 00000000000..866c9a0c23a --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonDetailsCard.java @@ -0,0 +1,73 @@ +package seedu.address.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.person.Person; + +/** + * An UI component that displays the detailed information of a {@code Person}. + */ +public class PersonDetailsCard extends UiPart { + + private static final String FXML = "PersonDetailsCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Person person; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private FlowPane tags; + + /** + * Creates a {@code PersonDetailsCard} with the given {@code Person}. + */ + public PersonDetailsCard(Person person) { + super(FXML); + this.person = person; + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + address.setText(person.getAddress().value); + email.setText(person.getEmail().value); + person.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PersonDetailsCard)) { + return false; + } + + // state check + PersonDetailsCard card = (PersonDetailsCard) other; + return person.equals(card.person); + } +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..d62bc7da78e 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -16,6 +16,7 @@ public class PersonListPanel extends UiPart { private static final String FXML = "PersonListPanel.fxml"; private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + private boolean showDetails = false; @FXML private ListView personListView; @@ -29,6 +30,15 @@ public PersonListPanel(ObservableList personList) { personListView.setCellFactory(listView -> new PersonListViewCell()); } + /** + * Sets the details field to parameter passed. + * + * @param showDetails boolean value to show details. + */ + public void setShowDetails(boolean showDetails) { + this.showDetails = showDetails; + } + /** * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. */ @@ -40,6 +50,8 @@ protected void updateItem(Person person, boolean empty) { if (empty || person == null) { setGraphic(null); setText(null); + } else if (showDetails) { + setGraphic(new PersonDetailsCard(person).getRoot()); } else { setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); } diff --git a/src/main/resources/view/PersonDetailsCard.fxml b/src/main/resources/view/PersonDetailsCard.fxml new file mode 100644 index 00000000000..932e7188e46 --- /dev/null +++ b/src/main/resources/view/PersonDetailsCard.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java index 4f3eb46e9ef..d026fa5a9e1 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java @@ -14,7 +14,7 @@ public void equals() { // same values -> returns true assertTrue(commandResult.equals(new CommandResult("feedback"))); - assertTrue(commandResult.equals(new CommandResult("feedback", false, false))); + assertTrue(commandResult.equals(new CommandResult("feedback", false, false, false))); // same object -> returns true assertTrue(commandResult.equals(commandResult)); @@ -29,10 +29,10 @@ public void equals() { assertFalse(commandResult.equals(new CommandResult("different"))); // different showHelp value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", true, false))); + assertFalse(commandResult.equals(new CommandResult("feedback", true, false, false))); // different exit value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", false, true))); + assertFalse(commandResult.equals(new CommandResult("feedback", false, true, false))); } @Test @@ -46,9 +46,9 @@ public void hashcode() { assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode()); // different showHelp value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false, false).hashCode()); // different exit value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true, false).hashCode()); } } diff --git a/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java b/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java new file mode 100644 index 00000000000..1e8778097cc --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java @@ -0,0 +1,77 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_PERSON_DETAILS_FOUND; +import static seedu.address.commons.core.Messages.MESSAGE_PERSON_DETAILS_NOT_FOUND; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.CARL; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameEqualKeywordPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code DetailsCommand}. + */ +class DetailsCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + NameEqualKeywordPredicate firstPredicate = + new NameEqualKeywordPredicate(new Name("first")); + NameEqualKeywordPredicate secondPredicate = + new NameEqualKeywordPredicate(new Name("second")); + + DetailsCommand findFirstCommand = new DetailsCommand(firstPredicate); + DetailsCommand findSecondCommand = new DetailsCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + DetailsCommand findFirstCommandCopy = new DetailsCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_zeroKeywords_noPersonFound() { + String name = "cool name"; + String expectedMessage = String.format(MESSAGE_PERSON_DETAILS_NOT_FOUND, name); + NameEqualKeywordPredicate predicate = new NameEqualKeywordPredicate(new Name(name)); + DetailsCommand command = new DetailsCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredPersonList()); + } + + @Test + public void execute_multipleKeywords_multiplePersonsFound() { + Name name = CARL.getName(); + String expectedMessage = String.format(MESSAGE_PERSON_DETAILS_FOUND, name); + NameEqualKeywordPredicate predicate = new NameEqualKeywordPredicate(name); + DetailsCommand command = new DetailsCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.singletonList(CARL), model.getFilteredPersonList()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java index 9533c473875..a573f42f11b 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java @@ -14,7 +14,7 @@ public class ExitCommandTest { @Test public void execute_exit_success() { - CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false); assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 4904fc4352e..3b35c387c4c 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -14,7 +14,7 @@ public class HelpCommandTest { @Test public void execute_help_success() { - CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false); + CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false, false); assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index d9659205b57..9e1668a4396 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -16,6 +16,7 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DetailsCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.commands.ExitCommand; @@ -24,6 +25,7 @@ import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameEqualKeywordPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; @@ -53,6 +55,14 @@ public void parseCommand_delete() throws Exception { assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); } + @Test + public void parseCommand_details() throws Exception { + String keyword = "foo"; + DetailsCommand command = (DetailsCommand) parser.parseCommand( + DetailsCommand.COMMAND_WORD + " " + keyword); + assertEquals(new DetailsCommand(new NameEqualKeywordPredicate(ParserUtil.parseName(keyword))), command); + } + @Test public void parseCommand_edit() throws Exception { Person person = new PersonBuilder().build(); diff --git a/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java new file mode 100644 index 00000000000..16c001782c3 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.DetailsCommand; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameEqualKeywordPredicate; + +class DetailsCommandParserTest { + + private DetailsCommandParser parser = new DetailsCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DetailsCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsDetailsCommand() { + DetailsCommand expectedFindCommand = + new DetailsCommand(new NameEqualKeywordPredicate(new Name("Alice Bob"))); + + // No trailing and leading whitespace + assertParseSuccess(parser, "Alice Bob", expectedFindCommand); + + // Leading whitespace + assertParseSuccess(parser, " Alice Bob", expectedFindCommand); + + // Trailing whitespace + assertParseSuccess(parser, "Alice Bob ", expectedFindCommand); + + // Leading and trailing whitespace + assertParseSuccess(parser, " Alice Bob ", expectedFindCommand); + } +} diff --git a/src/test/java/seedu/address/model/person/NameEqualKeywordPredicateTest.java b/src/test/java/seedu/address/model/person/NameEqualKeywordPredicateTest.java new file mode 100644 index 00000000000..c01f6088929 --- /dev/null +++ b/src/test/java/seedu/address/model/person/NameEqualKeywordPredicateTest.java @@ -0,0 +1,70 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PersonBuilder; + +class NameEqualKeywordPredicateTest { + @Test + public void equals() { + String firstKeyword = "keyword"; + String secondKeyword = "Keyword"; + String thirdKeyword = "keyword "; + + NameEqualKeywordPredicate firstPredicate = new NameEqualKeywordPredicate(new Name(firstKeyword)); + NameEqualKeywordPredicate secondPredicate = new NameEqualKeywordPredicate(new Name(secondKeyword)); + NameEqualKeywordPredicate thirdPredicate = new NameEqualKeywordPredicate(new Name(thirdKeyword)); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + NameEqualKeywordPredicate firstPredicateCopy = new NameEqualKeywordPredicate(new Name(firstKeyword)); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + + // extra spacing -> returns false + assertFalse(thirdPredicate.equals(firstPredicate)); + } + + @Test + public void test_nameMatchKeyword_returnsTrue() { + // Exact 1 word match + NameEqualKeywordPredicate predicate = new NameEqualKeywordPredicate(new Name("Alice")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice").build())); + + // Exact 2 word match + predicate = new NameEqualKeywordPredicate(new Name("Alice Bob")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + } + + @Test + public void test_nameDoesNotMatchKeyword_returnsFalse() { + // Non-matching keyword + NameEqualKeywordPredicate predicate = new NameEqualKeywordPredicate(new Name("Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); + + // Partial match + predicate = new NameEqualKeywordPredicate(new Name("Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Caroline").build())); + + // Partial match 2 words + predicate = new NameEqualKeywordPredicate(new Name("Bob")); + assertFalse(predicate.test(new PersonBuilder().withName("Bob boy").build())); + + // Predicate with more words than person + predicate = new NameEqualKeywordPredicate(new Name("Bob the builder")); + assertFalse(predicate.test(new PersonBuilder().withName("Bob").build())); + } +}