diff --git a/src/main/java/seedu/address/logic/commands/AddNoteCommand.java b/src/main/java/seedu/address/logic/commands/AddNoteCommand.java index 58a1addee02..7d6155019ad 100644 --- a/src/main/java/seedu/address/logic/commands/AddNoteCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddNoteCommand.java @@ -3,7 +3,6 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.List; @@ -74,12 +73,29 @@ public CommandResult execute(Model model) throws CommandException { personToEdit.getIdentityCardNumber(), personToEdit.getAge(), personToEdit.getSex(), personToEdit.getAddress(), updatedNote, personToEdit.getTags()); } + model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + if (model.isPersonDisplayed(personToEdit)) { + model.setDisplayNote(editedPerson); + } return new CommandResult(generateSuccessMessage(editedPerson)); } + /** + * Checks if the note of the person is changed. + * @param person the person to be compared with + * @param newNote the new note to be compared with + * @return true if the note is changed, false otherwise + */ + public boolean isNoteChanged(Person person, Note newNote) { + if (person != null) { + return !person.getNote().equals(newNote); + } + return false; + } + /** * Generates a command execution success message based on whether the remark is added to or removed from * {@code personToEdit}. diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 96092d97708..beaf7bd8adb 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -6,6 +6,7 @@ import seedu.address.logic.Messages; import seedu.address.model.Model; import seedu.address.model.person.IdentityCardNumberMatchesPredicate; +import seedu.address.model.person.Person; /** * Finds and lists all persons in address book whose IC matches the argument IC. @@ -17,7 +18,7 @@ public class FindCommand extends Command { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose profile matches " + "the specified IC (case-insensitive) and displays them.\n" - + "Parameters: IC (must be a valid identity card number) \n" + + "Parameters: IC\n" + "Example: " + COMMAND_WORD + " t1234567A"; private final IdentityCardNumberMatchesPredicate predicate; @@ -30,7 +31,14 @@ public FindCommand(IdentityCardNumberMatchesPredicate predicate) { public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(predicate); - model.setDisplayNoteAsFirstFilteredPerson(); + + assert model.getFilteredPersonList().size() <= 1 : "There should be at most one person in the filtered list"; + + if (model.getFilteredPersonList().size() == 1) { + Person person = model.getFilteredPersonList().get(0); + model.setDisplayNote(person); + } + return new CommandResult( String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); } diff --git a/src/main/java/seedu/address/logic/commands/ShowCommand.java b/src/main/java/seedu/address/logic/commands/ShowCommand.java new file mode 100644 index 00000000000..274f39c003e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowCommand.java @@ -0,0 +1,117 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.IdentityCardNumberMatchesPredicate; +import seedu.address.model.person.Person; + + + +/** + * Shows the user a note of a person in the address book. + * If no IC is given, the displayed note panel will be cleared. + */ +public class ShowCommand extends Command { + + public static final String COMMAND_WORD = "show"; + public static final String MESSAGE_SHOW_NOTE_SUCCESS = "Displayed note of person: %1$s"; + + public static final String MESSAGE_CLEAR_NOTE_SUCCESS = "Note cleared."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows the note of the person whose IC matches the " + + "specified valid IC (case-insensitive) and displays it.\n" + + "If no IC is given, the displayed note panel will be cleared.\n" + + "Parameters: IC (optional)\n" + + "Example (to display note): " + COMMAND_WORD + " t1234567A\n" + + "Example (to clear display): " + COMMAND_WORD; + private final IdentityCardNumberMatchesPredicate icPredicate; + + private final boolean isClear; + + /** + * Creates a ShowCommand to show the note of the person whose profile matches the specified {@code icPredicate}. + * @param icPredicate of the person in the filtered person list to show the note + */ + private ShowCommand(IdentityCardNumberMatchesPredicate icPredicate, boolean isClear) { + this.icPredicate = icPredicate; + this.isClear = isClear; + } + + /** + * Factory method to create a ShowCommand to show the note of the person whose profile matches + * the specified {@code icPredicate}. + * @param icPredicate of the person in the filtered person list to show the note + */ + public static ShowCommand createShowCommand(IdentityCardNumberMatchesPredicate icPredicate) { + return new ShowCommand(icPredicate, false); + } + + /** + * Factory method to create a ShowCommand to clear the displayed note panel. + */ + public static ShowCommand createClearCommand() { + return new ShowCommand(null, true); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (isClear) { + model.clearDisplayNote(); + return new CommandResult(MESSAGE_CLEAR_NOTE_SUCCESS); + } + + List allPatients = model.getAddressBook().getPersonList(); + + Person person = allPatients.stream() + .filter(icPredicate::test) + .findFirst() + .orElseThrow(() -> new CommandException(Messages.MESSAGE_NO_MATCHING_IC)); + + model.setDisplayNote(person); + + return new CommandResult( + String.format(MESSAGE_SHOW_NOTE_SUCCESS, person.getIdentityCardNumber()) + ); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ShowCommand)) { + return false; + } + + ShowCommand e = (ShowCommand) other; + // icPredicate is not important if isClear is true + if (isClear && e.isClear) { + return true; + } + if (isClear != e.isClear) { + return false; + } + if (icPredicate == null || e.icPredicate == null) { + return icPredicate == e.icPredicate; + } + return icPredicate.equals(e.icPredicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", icPredicate) + .add("isClear", isClear) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 9af37735848..96d7a9f8c7d 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -18,6 +18,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ShowCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -69,6 +70,9 @@ public Command parseCommand(String userInput) throws ParseException { case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case ShowCommand.COMMAND_WORD: + return new ShowCommandParser().parse(arguments); + case AddNoteCommand.COMMAND_WORD: return new AddNoteCommandParser().parse(arguments); diff --git a/src/main/java/seedu/address/logic/parser/ShowCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowCommandParser.java new file mode 100644 index 00000000000..11bb78b8689 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ShowCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ShowCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.IdentityCardNumber; +import seedu.address.model.person.IdentityCardNumberMatchesPredicate; + +/** + * Parses input arguments and creates a new ShowCommand object + */ +public class ShowCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ShowCommand + * and returns a ShowCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ShowCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + return ShowCommand.createClearCommand(); + } + + try { + IdentityCardNumber ic = ParserUtil.parseIC(trimmedArgs); + return ShowCommand.createShowCommand(new IdentityCardNumberMatchesPredicate(ic)); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowCommand.MESSAGE_USAGE), e); + } + } + +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index ee6c7cdb08e..bfefcb6ab8e 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -30,7 +30,7 @@ public class AddressBook implements ReadOnlyAddressBook { persons = new UniquePersonList(); } - private Optional displayNote = Optional.empty(); + private Optional displayPerson = Optional.empty(); public AddressBook() {} @@ -102,22 +102,28 @@ public void removePerson(Person key) { * Get the display note. */ public Note getDisplayNote() { - return displayNote.orElse(Note.DEFAULT); + return displayPerson.map(Person::getNote).orElse(Note.EMPTY); } /** - * Sets the display note to the given note. - * @param note The note to be displayed. + * Set the display person. */ - public void setDisplayNote(Note note) { - this.displayNote = Optional.of(note); + public void setDisplayPerson(Person person) { + this.displayPerson = Optional.of(person); + } + + /** + * Check if person is displayed. + */ + public boolean isPersonDisplayed(Person person) { + return displayPerson.isPresent() && displayPerson.get().isSamePerson(person); } /** * Clears the display note. */ - public void clearDisplayNote() { - this.displayNote = Optional.empty(); + public void clearDisplayPerson() { + this.displayPerson = Optional.empty(); } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 6e965e82c7d..427acea7d59 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -86,14 +86,21 @@ public interface Model { */ void updateFilteredPersonList(Predicate predicate); + /** + * Returns the display note. + */ Note getDisplayNote(); + /** + * Sets the displayed note to the note of the given person. + */ void setDisplayNote(Person person); /** - * Replaces the note that is displayed in the note panel. + * Returns true if the person is displayed in the note panel. */ - void setDisplayNote(Note note); + boolean isPersonDisplayed(Person person); + /** * Sets the display note to the first filtered person. diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index b695a7312fd..92fa0931548 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -137,16 +137,15 @@ public Note getDisplayNote() { } @Override - public void setDisplayNote(Note note) { - requireNonNull(note); - addressBook.setDisplayNote(note); + public void setDisplayNote(Person person) { + requireNonNull(person); + addressBook.setDisplayPerson(person); } @Override - public void setDisplayNote(Person person) { + public boolean isPersonDisplayed(Person person) { requireNonNull(person); - Note displayNote = person.getNote(); - setDisplayNote(displayNote); + return addressBook.isPersonDisplayed(person); } /** @@ -168,7 +167,7 @@ public void setDisplayNoteAsFirstFilteredPerson() { */ @Override public void clearDisplayNote() { - addressBook.clearDisplayNote(); + addressBook.clearDisplayPerson(); } @Override diff --git a/src/main/java/seedu/address/model/person/Note.java b/src/main/java/seedu/address/model/person/Note.java index 0348550a94c..706067e2f5b 100644 --- a/src/main/java/seedu/address/model/person/Note.java +++ b/src/main/java/seedu/address/model/person/Note.java @@ -10,6 +10,7 @@ public class Note { public static final Note DEFAULT = new Note(""); + public static final Note EMPTY = new Note(""); public static final String MESSAGE_CONSTRAINTS = "Notes can take any values, and it should not be blank"; public final String value; diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 30fb409bb77..986d1a937df 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -178,7 +178,7 @@ public void setDisplayNote(Person person) { } @Override - public void setDisplayNote(Note note) { + public boolean isPersonDisplayed(Person person) { throw new AssertionError("This method should not be called."); } diff --git a/src/test/java/seedu/address/logic/commands/AddNoteCommandTest.java b/src/test/java/seedu/address/logic/commands/AddNoteCommandTest.java index 7befdf32278..499335d6031 100644 --- a/src/test/java/seedu/address/logic/commands/AddNoteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddNoteCommandTest.java @@ -50,6 +50,70 @@ public void generateSuccessMessage_noteAdded_successMessage() { new IdentityCardNumberMatchesPredicate(personToEdit.getIdentityCardNumber()), new Note("new note"), false).generateSuccessMessage(personToEdit)); } + @Test + public void isNoteChanged_noteChanged_returnsTrue() { + // Setup + Person person = new PersonBuilder().withNote("old note").build(); + Note newNote = new Note("new note"); + + // Action + boolean isChanged = new AddNoteCommand( + new IdentityCardNumberMatchesPredicate(person.getIdentityCardNumber()), + newNote, false).isNoteChanged(person, newNote); + + // Verify + assertTrue(isChanged); + } + + @Test + public void isNoteChanged_noteNotChanged_returnsFalse() { + // Setup + Person person = new PersonBuilder().withNote("same note").build(); + Note sameNote = new Note("same note"); + + // Action + boolean isChanged = new AddNoteCommand( + new IdentityCardNumberMatchesPredicate(person.getIdentityCardNumber()), + sameNote, false).isNoteChanged(person, sameNote); + + // Verify + assertFalse(isChanged); + } + + @Test + public void execute_noteAdded_success() throws CommandException { + // Setup + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Person person = model.getFilteredPersonList().get(0); + AddNoteCommand command = new AddNoteCommand( + new IdentityCardNumberMatchesPredicate(person.getIdentityCardNumber()), + new Note("new note"), false); + + // Action + CommandResult result = command.execute(model); + + // Verify + assertEquals(String.format(AddNoteCommand.MESSAGE_MODIFY_NOTE_SUCCESS, + person.getName(), person.getIdentityCardNumber()), result.getFeedbackToUser()); + } + + @Test + public void execute_noteAppended_success() throws CommandException { + // Setup + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Person person = model.getFilteredPersonList().get(0); + Note originalNote = person.getNote(); + AddNoteCommand command = new AddNoteCommand( + new IdentityCardNumberMatchesPredicate(person.getIdentityCardNumber()), + new Note("new note"), false); + + // Action + command.execute(model); + + // Verify + Person updatedPerson = model.getFilteredPersonList().get(0); + assertEquals(originalNote.append("\nnew note").toString(), updatedPerson.getNote().toString()); + } @Test public void equals() { diff --git a/src/test/java/seedu/address/logic/commands/ShowCommandTest.java b/src/test/java/seedu/address/logic/commands/ShowCommandTest.java new file mode 100644 index 00000000000..a60ec185ea3 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ShowCommandTest.java @@ -0,0 +1,93 @@ +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.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +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.IdentityCardNumber; +import seedu.address.model.person.IdentityCardNumberMatchesPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code ShowCommand}. + */ +public class ShowCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + IdentityCardNumberMatchesPredicate firstPredicate = + new IdentityCardNumberMatchesPredicate(new IdentityCardNumber("S1234567A")); + IdentityCardNumberMatchesPredicate secondPredicate = + new IdentityCardNumberMatchesPredicate(new IdentityCardNumber("S9876543B")); + + ShowCommand showFirstCommand = ShowCommand.createShowCommand(firstPredicate); + ShowCommand showSecondCommand = ShowCommand.createShowCommand(secondPredicate); + + // same object -> returns true + assertTrue(showFirstCommand.equals(showFirstCommand)); + + // same values -> returns true + ShowCommand showFirstCommandCopy = ShowCommand.createShowCommand(firstPredicate); + assertTrue(showFirstCommand.equals(showFirstCommandCopy)); + + // different types -> returns false + assertFalse(showFirstCommand.equals(1)); + + // null -> returns false + assertFalse(showFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(showFirstCommand.equals(showSecondCommand)); + + // different isClear -> returns false + ShowCommand showFirstCommandClear = ShowCommand.createClearCommand(); + assertFalse(showFirstCommand.equals(showFirstCommandClear)); + + // same isClear -> returns true + ShowCommand showSecondCommandClear = ShowCommand.createClearCommand(); + assertTrue(showFirstCommandClear.equals(showSecondCommandClear)); + } + + @Test + public void execute_validIC_singlePersonFound() { + String testNumber = ALICE.getIdentityCardNumber().value; + String expectedMessage = String.format(ShowCommand.MESSAGE_SHOW_NOTE_SUCCESS, testNumber); + IdentityCardNumberMatchesPredicate predicate = preparePredicate(testNumber); + ShowCommand command = ShowCommand.createShowCommand(predicate); + expectedModel.setDisplayNote(ALICE); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noIC_clearNote() { + String expectedMessage = ShowCommand.MESSAGE_CLEAR_NOTE_SUCCESS; + ShowCommand command = ShowCommand.createClearCommand(); + expectedModel.clearDisplayNote(); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void toStringMethod() { + IdentityCardNumberMatchesPredicate predicate = new IdentityCardNumberMatchesPredicate( + new IdentityCardNumber("S1234567A")); + ShowCommand showCommand = ShowCommand.createShowCommand(predicate); + String expected = ShowCommand.class.getCanonicalName() + "{predicate=" + predicate + ", isClear=" + "false}"; + assertEquals(expected, showCommand.toString()); + } + + /** + * Parses {@code userInput} into a {@code IdentityCardNumberMatchesPredicate}. + */ + private IdentityCardNumberMatchesPredicate preparePredicate(String userInput) { + return new IdentityCardNumberMatchesPredicate(new IdentityCardNumber(userInput)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ShowCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ShowCommandParserTest.java new file mode 100644 index 00000000000..13bc8250028 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ShowCommandParserTest.java @@ -0,0 +1,54 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.ShowCommand; +import seedu.address.model.person.IdentityCardNumber; +import seedu.address.model.person.IdentityCardNumberMatchesPredicate; + +public class ShowCommandParserTest { + + private ShowCommandParser parser = new ShowCommandParser(); + + @Test + public void parse_emptyArg_returnsShowCommand() { + ShowCommand expectedShowCommand = ShowCommand.createClearCommand(); + assertParseSuccess(parser, "", expectedShowCommand); + assertParseSuccess(parser, " ", expectedShowCommand); + assertParseSuccess(parser, " \n \t \t", expectedShowCommand); + } + + @Test + public void parse_validArgs_returnsShowCommand() { + ShowCommand expectedShowCommand = ShowCommand.createShowCommand(new IdentityCardNumberMatchesPredicate( + new IdentityCardNumber("S1234567A"))); + assertParseSuccess(parser, "S1234567A", expectedShowCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n" + + " S1234567A \n" + + " \t \t", expectedShowCommand); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // IC with incorrect format + assertParseFailure(parser, "S1234", IdentityCardNumber.MESSAGE_CONSTRAINTS); + + // IC with incorrect format and additional arguments + assertParseFailure(parser, "S1234 extra", IdentityCardNumber.MESSAGE_CONSTRAINTS); + + // IC with correct format but contains non-alphanumeric characters + assertParseFailure(parser, "S1234$%^", IdentityCardNumber.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_nullArgs_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> parser.parse(null)); + } + +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index fb00143650c..61b2cda2c1d 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -103,19 +103,6 @@ public void setDisplayedNote_firstPerson_setsDisplayedNote() { assertEquals(ALICE.getNote(), modelManager.getDisplayNote()); } - @Test - public void setDisplayedNote_validNote_setsDisplayedNote() { - Note note = Note.DEFAULT; - modelManager.setDisplayNote(note); - assertEquals(note, modelManager.getDisplayNote()); - } - - @Test - public void setDisplayedNote_nullNote_throwsNullPointerException() { - Note note = null; - assertThrows(NullPointerException.class, () -> modelManager.setDisplayNote(note)); - } - @Test public void clearDisplayedNote_validNote_clearsDisplayedNote() { modelManager.setDisplayNote(ALICE);