diff --git a/docs/UserGuide.md b/docs/UserGuide.md index de0de87bd42..96cf99d0554 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -147,25 +147,30 @@ Examples: Shows the details of a particular member in the IG. -Format: `details NAME` +Format: `details NAME` or `details INDEX` +* The argument interprets as an `INDEX` if it is a positive integer, `NAME` otherwise. +* Displays the details of the person at the specified `INDEX`. +* The index refers to the index number shown in the displayed person list. +* The index **must be a positive integer** 1, 2, 3, …​ * The NAME entered must match the member’s name exactly (case-sensitive). Examples: +* `list` followed by `details 3` displays details of the 3rd person in the displayed person list. * `details Xiao Ming` will output: ``` - Name: Xiao Ming - Phone: 61234567 - Tele: @xiao_ming - Email: xiaoming@gmail.com + Xiao Ming details + 61234567 + @xiao_ming + xiaoming@gmail.com ``` * `details John Doe` will output: ``` - Name: John Doe - Phone: NIL - Tele: NIL - Email: NIL + John Doe details + NIL + NIL + NIL ``` ### Clearing all entries : `clear` diff --git a/src/main/java/seedu/address/logic/commands/DetailsCommand.java b/src/main/java/seedu/address/logic/commands/DetailsCommand.java index ce7c2b65b2f..31620d1548b 100644 --- a/src/main/java/seedu/address/logic/commands/DetailsCommand.java +++ b/src/main/java/seedu/address/logic/commands/DetailsCommand.java @@ -2,9 +2,14 @@ import static java.util.Objects.requireNonNull; +import java.util.List; + import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.NameEqualKeywordPredicate; +import seedu.address.model.person.Person; /** * Finds and displays the details of person whose name matches the keyword exactly. @@ -15,18 +20,41 @@ 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" + + "specified keywords (case-sensitive and exact match of name) or index (must be a positive integer).\n" + + "Parameters: NAME (case-sensitive) or INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " Xiao Ming"; - private final NameEqualKeywordPredicate predicate; + private NameEqualKeywordPredicate predicate; + private Index targetIndex; public DetailsCommand(NameEqualKeywordPredicate predicate) { this.predicate = predicate; } + public DetailsCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + @Override - public CommandResult execute(Model model) { + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + convertIndexToPredicate(model); + return executePredicate(model); + } + + private void convertIndexToPredicate(Model model) throws CommandException { + requireNonNull(model); + if (targetIndex != null) { + List lastShownList = model.getFilteredPersonList(); + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Person personToDisplay = lastShownList.get(targetIndex.getZeroBased()); + predicate = new NameEqualKeywordPredicate(personToDisplay.getName()); + } + } + + private CommandResult executePredicate(Model model) { requireNonNull(model); model.updateFilteredPersonList(predicate); assert model.getFilteredPersonList().size() < 2; @@ -43,8 +71,19 @@ public CommandResult execute(Model model) { @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 + if (other == this) { + return true; + } + if (!(other instanceof DetailsCommand)) { + return false; + } + DetailsCommand o = (DetailsCommand) other; + if (predicate == null) { + return o.predicate == null && targetIndex.equals(o.targetIndex); + } + if (targetIndex == null) { + return o.targetIndex == null && predicate.equals(o.predicate); + } + return targetIndex.equals(o.targetIndex) && predicate.equals(o.predicate); } } diff --git a/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java b/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java index ecf739a2191..dc8a159f758 100644 --- a/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DetailsCommandParser.java @@ -2,6 +2,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DetailsCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Name; @@ -19,11 +20,16 @@ public class DetailsCommandParser implements Parser { */ public DetailsCommand parse(String args) throws ParseException { try { - Name name = ParserUtil.parseName(args); - return new DetailsCommand(new NameEqualKeywordPredicate(name)); + Index index = ParserUtil.parseIndex(args); + return new DetailsCommand(index); } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DetailsCommand.MESSAGE_USAGE)); + try { + Name name = ParserUtil.parseName(args); + return new DetailsCommand(new NameEqualKeywordPredicate(name)); + } catch (ParseException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DetailsCommand.MESSAGE_USAGE)); + } } } diff --git a/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java b/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java index 1e8778097cc..373f074d41a 100644 --- a/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DetailsCommandTest.java @@ -5,7 +5,11 @@ 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.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; import static seedu.address.testutil.TypicalPersons.CARL; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; @@ -13,11 +17,14 @@ import org.junit.jupiter.api.Test; +import seedu.address.commons.core.Messages; +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.Name; import seedu.address.model.person.NameEqualKeywordPredicate; +import seedu.address.model.person.Person; /** * Contains integration tests (interaction with the Model) for {@code DetailsCommand}. @@ -33,24 +40,34 @@ public void equals() { NameEqualKeywordPredicate secondPredicate = new NameEqualKeywordPredicate(new Name("second")); - DetailsCommand findFirstCommand = new DetailsCommand(firstPredicate); - DetailsCommand findSecondCommand = new DetailsCommand(secondPredicate); + DetailsCommand detailsFirstCommand = new DetailsCommand(firstPredicate); + DetailsCommand detailsSecondCommand = new DetailsCommand(secondPredicate); + DetailsCommand detailsThirdCommand = new DetailsCommand(Index.fromOneBased(1)); + DetailsCommand detailsFourthCommand = new DetailsCommand(Index.fromOneBased(2)); // same object -> returns true - assertTrue(findFirstCommand.equals(findFirstCommand)); + assertTrue(detailsFirstCommand.equals(detailsFirstCommand)); // same values -> returns true - DetailsCommand findFirstCommandCopy = new DetailsCommand(firstPredicate); - assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + DetailsCommand detailsFirstCommandCopy = new DetailsCommand(firstPredicate); + assertTrue(detailsFirstCommand.equals(detailsFirstCommandCopy)); // different types -> returns false - assertFalse(findFirstCommand.equals(1)); + assertFalse(detailsFirstCommand.equals(1)); + assertFalse(detailsThirdCommand.equals(1)); // null -> returns false - assertFalse(findFirstCommand.equals(null)); + assertFalse(detailsFirstCommand.equals(null)); + assertFalse(detailsThirdCommand.equals(null)); // different person -> returns false - assertFalse(findFirstCommand.equals(findSecondCommand)); + assertFalse(detailsFirstCommand.equals(detailsSecondCommand)); + + // same index -> returns true + assertTrue(detailsThirdCommand.equals(new DetailsCommand(Index.fromOneBased(1)))); + + // different index -> returns false + assertFalse(detailsThirdCommand.equals(detailsFourthCommand)); } @Test @@ -74,4 +91,53 @@ public void execute_multipleKeywords_multiplePersonsFound() { assertCommandSuccess(command, model, expectedMessage, expectedModel); assertEquals(Collections.singletonList(CARL), model.getFilteredPersonList()); } + + @Test + public void execute_validIndexUnfilteredList_success() { + Person personToDisplay = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + DetailsCommand detailsCommand = new DetailsCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(Messages.MESSAGE_PERSON_DETAILS_FOUND, personToDisplay.getName()); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.updateFilteredPersonList(x -> x.getName().equals(personToDisplay.getName())); + + assertCommandSuccess(detailsCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + DetailsCommand detailsCommand = new DetailsCommand(outOfBoundIndex); + + assertCommandFailure(detailsCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person personToDisplay = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + DetailsCommand detailsCommand = new DetailsCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(Messages.MESSAGE_PERSON_DETAILS_FOUND, personToDisplay.getName()); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.updateFilteredPersonList(x -> x.getName().equals(personToDisplay.getName())); + + assertCommandSuccess(detailsCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + + DetailsCommand detailsCommand = new DetailsCommand(outOfBoundIndex); + + assertCommandFailure(detailsCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } } diff --git a/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java index 16c001782c3..020b5de4e68 100644 --- a/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DetailsCommandParserTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; +import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DetailsCommand; import seedu.address.model.person.Name; import seedu.address.model.person.NameEqualKeywordPredicate; @@ -22,19 +23,25 @@ public void parse_emptyArg_throwsParseException() { @Test public void parse_validArgs_returnsDetailsCommand() { - DetailsCommand expectedFindCommand = + DetailsCommand firstExpectedDetailsCommand = new DetailsCommand(new NameEqualKeywordPredicate(new Name("Alice Bob"))); + DetailsCommand secondExpectedDetailsCommand = + new DetailsCommand(Index.fromOneBased(3)); // No trailing and leading whitespace - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); + assertParseSuccess(parser, "Alice Bob", firstExpectedDetailsCommand); + assertParseSuccess(parser, "3", secondExpectedDetailsCommand); // Leading whitespace - assertParseSuccess(parser, " Alice Bob", expectedFindCommand); + assertParseSuccess(parser, " Alice Bob", firstExpectedDetailsCommand); + assertParseSuccess(parser, " 3", secondExpectedDetailsCommand); // Trailing whitespace - assertParseSuccess(parser, "Alice Bob ", expectedFindCommand); + assertParseSuccess(parser, "Alice Bob ", firstExpectedDetailsCommand); + assertParseSuccess(parser, "3 ", secondExpectedDetailsCommand); // Leading and trailing whitespace - assertParseSuccess(parser, " Alice Bob ", expectedFindCommand); + assertParseSuccess(parser, " Alice Bob ", firstExpectedDetailsCommand); + assertParseSuccess(parser, " 3 ", secondExpectedDetailsCommand); } }