diff --git a/src/main/java/staffconnect/logic/commands/AddMeetingCommand.java b/src/main/java/staffconnect/logic/commands/AddMeetingCommand.java index fe17fb90748..891b7550b2d 100644 --- a/src/main/java/staffconnect/logic/commands/AddMeetingCommand.java +++ b/src/main/java/staffconnect/logic/commands/AddMeetingCommand.java @@ -3,7 +3,6 @@ import static java.util.Objects.requireNonNull; import static staffconnect.logic.parser.CliSyntax.PREFIX_MEETING_DESCRIPTION; import static staffconnect.logic.parser.CliSyntax.PREFIX_MEETING_STARTDATE; -import static staffconnect.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static staffconnect.model.meeting.comparator.MeetingDateThenDescriptionComparator.MEETING_DATE_THEN_DESCRIPTION_COMPARATOR; import java.util.List; @@ -69,8 +68,9 @@ public CommandResult execute(Model model) throws CommandException { //setPerson to force update the ui with the new items model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); + + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)), editedPerson, + index.getZeroBased()); } diff --git a/src/main/java/staffconnect/logic/commands/CommandResult.java b/src/main/java/staffconnect/logic/commands/CommandResult.java index 8fbc27b2091..b806781778a 100644 --- a/src/main/java/staffconnect/logic/commands/CommandResult.java +++ b/src/main/java/staffconnect/logic/commands/CommandResult.java @@ -3,8 +3,10 @@ import static java.util.Objects.requireNonNull; import java.util.Objects; +import java.util.Optional; import staffconnect.commons.util.ToStringBuilder; +import staffconnect.model.person.Person; /** * Represents the result of a command execution. @@ -13,12 +15,28 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * The application should exit. + */ private final boolean exit; + private final int index; + + private final Person personToSwitch; + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * and other fields set to their default value. + */ + public CommandResult(String feedbackToUser) { + this(feedbackToUser, false, false); + } + /** * Constructs a {@code CommandResult} with the specified fields. */ @@ -26,14 +44,19 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + index = -999; + personToSwitch = null; } /** - * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. + * Constructs a {@code CommandResult} with the specified person to store. */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + public CommandResult(String feedbackToUser, Person personToSwitch, int index) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.showHelp = false; + this.exit = false; + this.personToSwitch = personToSwitch; + this.index = index; } public String getFeedbackToUser() { @@ -48,6 +71,34 @@ public boolean isExit() { return exit; } + /** + * Checks if there is a valid person and index to get in the result. + * + * @return + */ + public boolean hasPersonAndIndex() { + return personToSwitch != null && index != -999; + } + + /** + * Returns a person from the result to send to the UI display. + * + * @return a person from the command result. + */ + public Optional getPersonToDisplay() { + return personToSwitch != null ? Optional.of(personToSwitch) : Optional.empty(); + } + + public int getIndex() { + return index; + + } + + @Override + public int hashCode() { + return Objects.hash(feedbackToUser, showHelp, exit, index, personToSwitch); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -62,12 +113,11 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; - } - - @Override - public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + && exit == otherCommandResult.exit + && index == otherCommandResult.index + && (personToSwitch != null + ? personToSwitch.equals(otherCommandResult.personToSwitch) + : personToSwitch == otherCommandResult.personToSwitch); } @Override @@ -76,6 +126,8 @@ public String toString() { .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) .add("exit", exit) + .add("index", index) + .add("person", personToSwitch) .toString(); } diff --git a/src/main/java/staffconnect/logic/commands/DeleteMeetingCommand.java b/src/main/java/staffconnect/logic/commands/DeleteMeetingCommand.java index cf646120340..e8301363f9b 100644 --- a/src/main/java/staffconnect/logic/commands/DeleteMeetingCommand.java +++ b/src/main/java/staffconnect/logic/commands/DeleteMeetingCommand.java @@ -69,7 +69,8 @@ public CommandResult execute(Model model) throws CommandException { //force update the ui model.setPerson(personToSelect, editedPerson); - return new CommandResult(String.format(MESSAGE_DELETE_MEETING_SUCCESS, Messages.format(meetingToSelect))); + return new CommandResult(String.format(MESSAGE_DELETE_MEETING_SUCCESS, Messages.format(meetingToSelect)), + editedPerson, targetPersonIndex.getZeroBased()); } @Override diff --git a/src/main/java/staffconnect/logic/commands/SelectCommand.java b/src/main/java/staffconnect/logic/commands/SelectCommand.java new file mode 100644 index 00000000000..6f4b1f11d22 --- /dev/null +++ b/src/main/java/staffconnect/logic/commands/SelectCommand.java @@ -0,0 +1,70 @@ +package staffconnect.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import staffconnect.commons.core.index.Index; +import staffconnect.commons.util.ToStringBuilder; +import staffconnect.logic.Messages; +import staffconnect.logic.commands.exceptions.CommandException; +import staffconnect.model.Model; +import staffconnect.model.person.Person; + +/** + * Selects a person identified using it's displayed index from the staff book. + */ +public class SelectCommand extends Command { + + public static final String COMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the person identified by the index number used in the displayed person list for display.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; + + private final Index targetIndex; + + public SelectCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getSortedFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToSelect = lastShownList.get(targetIndex.getZeroBased()); + + return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, Messages.format(personToSelect)), + personToSelect, targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SelectCommand)) { + return false; + } + + SelectCommand selectCommand = (SelectCommand) other; + return targetIndex.equals(selectCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/staffconnect/logic/parser/SelectCommandParser.java b/src/main/java/staffconnect/logic/parser/SelectCommandParser.java new file mode 100644 index 00000000000..b08453e7ab1 --- /dev/null +++ b/src/main/java/staffconnect/logic/parser/SelectCommandParser.java @@ -0,0 +1,29 @@ +package staffconnect.logic.parser; + +import static staffconnect.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import staffconnect.commons.core.index.Index; +import staffconnect.logic.commands.SelectCommand; +import staffconnect.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SelectCommand object + */ +public class SelectCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SelectCommand + * and returns a SelectCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SelectCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SelectCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/staffconnect/logic/parser/StaffConnectParser.java b/src/main/java/staffconnect/logic/parser/StaffConnectParser.java index f305cc9f02a..f04c3794760 100644 --- a/src/main/java/staffconnect/logic/parser/StaffConnectParser.java +++ b/src/main/java/staffconnect/logic/parser/StaffConnectParser.java @@ -22,6 +22,7 @@ import staffconnect.logic.commands.HelpCommand; import staffconnect.logic.commands.ListCommand; import staffconnect.logic.commands.RefreshCommand; +import staffconnect.logic.commands.SelectCommand; import staffconnect.logic.commands.SortCommand; import staffconnect.logic.commands.UnfavCommand; import staffconnect.logic.parser.exceptions.ParseException; @@ -105,6 +106,9 @@ public Command parseCommand(String userInput) throws ParseException { case RefreshCommand.COMMAND_WORD: return new RefreshCommand(); + case SelectCommand.COMMAND_WORD: + return new SelectCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/staffconnect/ui/MainWindow.java b/src/main/java/staffconnect/ui/MainWindow.java index f7ca4ffc5a5..772dc8b708e 100644 --- a/src/main/java/staffconnect/ui/MainWindow.java +++ b/src/main/java/staffconnect/ui/MainWindow.java @@ -59,9 +59,9 @@ public MainWindow(Stage primaryStage, Logic logic) { // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); - + // The default divider is 0.43 personOnDisplay = logic.getFirstPersonIfExist() - .map(PersonCard::new).orElse(new PersonCard()); + .map(person -> new PersonCard(person, 0.43)).orElse(new PersonCard()); helpWindow = new HelpWindow(); } @@ -87,7 +87,8 @@ public Stage getPrimaryStage() { */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList(), this::changePersonCard); + personListPanel = + new PersonListPanel(logic.getFilteredPersonList(), this::changePersonCard, this::getDividerPosition); personListPanel.setListSelectedIndex(0); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); @@ -104,7 +105,6 @@ void fillInnerParts() { personCardPanelPlaceholder.getChildren().add(personOnDisplay.getRoot()); - } private void changePersonCard(PersonCard personToUpdate) { @@ -115,6 +115,15 @@ private void changePersonCard(PersonCard personToUpdate) { } + /** + * Gets the current divider position, to be used by the UI only + * + * @return a double value of the divider position. + */ + public double getDividerPosition() { + return personOnDisplay.getCurrentDividerPosition(); + } + /** * Executes the command and returns the result. * @@ -136,6 +145,18 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + if (commandResult.hasPersonAndIndex()) { + + double dividerPosition = personOnDisplay.getCurrentDividerPosition(); + int index = commandResult.getIndex(); + commandResult.getPersonToDisplay() + .ifPresentOrElse( + person -> reloadPersonCardWithPerson(new PersonCard(person, dividerPosition), index), + this::reloadPersonCardWithRoot); + } else { + reloadPersonCardWithRoot(); + } + return commandResult; } catch (CommandException | ParseException e) { logger.info("An error occurred while executing command: " + commandText); @@ -168,8 +189,9 @@ public void handleHelp() { } private void reloadPersonCardWithRoot() { - - personOnDisplay = logic.getFirstPersonIfExist().map(PersonCard::new).orElse(new PersonCard()); + double previousDividerPosition = personOnDisplay.getCurrentDividerPosition(); + personOnDisplay = logic.getFirstPersonIfExist() + .map(person -> new PersonCard(person, previousDividerPosition)).orElse(new PersonCard()); personCardPanelPlaceholder.getChildren().clear(); personCardPanelPlaceholder.getChildren().add(personOnDisplay.getRoot()); @@ -178,6 +200,16 @@ private void reloadPersonCardWithRoot() { } + private void reloadPersonCardWithPerson(PersonCard person, int index) { + + personOnDisplay = person; + personCardPanelPlaceholder.getChildren().clear(); + personCardPanelPlaceholder.getChildren().add(personOnDisplay.getRoot()); + + personListPanel.setListSelectedIndex(index); + + } + void show() { primaryStage.show(); } diff --git a/src/main/java/staffconnect/ui/MeetingsCard.java b/src/main/java/staffconnect/ui/MeetingsCard.java index 7f9ba087151..c69bb15de09 100644 --- a/src/main/java/staffconnect/ui/MeetingsCard.java +++ b/src/main/java/staffconnect/ui/MeetingsCard.java @@ -1,9 +1,9 @@ package staffconnect.ui; - import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.Region; +import javafx.scene.text.Text; import staffconnect.model.meeting.Meeting; /** @@ -12,6 +12,8 @@ public class MeetingsCard extends UiPart { private static final String FXML = "MeetingsListCard.fxml"; + private double pixelHeight = 0; + private double pixelWidth = 0; @FXML private Label id; @FXML @@ -28,6 +30,42 @@ public MeetingsCard(Meeting meeting, int index) { id.setText(index + ". "); description.setText(meeting.getDescription().toString()); date.setText(meeting.getStartDate().toString()); + computeSystemSize(description.getStyle()); + } + + private void computeSystemSize(String style) { + /* + There is no reliable way to compute to the accurate pixel height for different systems screens. + Hence, this is the devastation of trying to accommodate to different resolutions and screen sizes. + */ + Text helper = new Text(); + helper.setStyle(style); + helper.setLineSpacing(0); + char[] testFont = new char[]{'a', 'A', '/', '1', '-', ' ', '|', 'P', '%', '@'}; + double maxWidth = 0; + double maxHeight = 0; + for (char c : testFont) { + String input = c + ""; + helper.setText(input); + double currentWidth = helper.getLayoutBounds().getWidth(); + double currentHeight = helper.getLayoutBounds().getHeight(); + if (currentWidth > maxWidth) { + maxWidth = currentWidth; + } + if (currentHeight > maxHeight) { + maxHeight = currentHeight; + } + } + pixelHeight = maxHeight; + pixelWidth = maxWidth; + } + + public double getWidth() { + return pixelWidth; + } + + public double getHeight() { + return pixelHeight; } } diff --git a/src/main/java/staffconnect/ui/PersonCard.java b/src/main/java/staffconnect/ui/PersonCard.java index e1d77d99332..478af8784a8 100644 --- a/src/main/java/staffconnect/ui/PersonCard.java +++ b/src/main/java/staffconnect/ui/PersonCard.java @@ -20,6 +20,8 @@ import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import staffconnect.model.meeting.Meeting; +import staffconnect.model.meeting.MeetingDateTime; +import staffconnect.model.meeting.MeetingDescription; import staffconnect.model.person.Person; /** @@ -29,9 +31,11 @@ public class PersonCard extends UiPart { private static final String FXML = "PersonListCard.fxml"; - private static final int ROW_HEIGHT = 60; //row height of each meeting + //These values are used for computation of the list view height and width for different system's resolution. + //To ensure that list view is always displayed properly. + private static double pixelHeight = 0; + private static double pixelWidth = 0; - private static final int LABEL_MEETING_WIDTH = 10; //the width of the meeting label /** * 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 @@ -95,7 +99,7 @@ public PersonCard() { /** * Creates a {@code PersonCard} with the given {@code Person} and index to display. */ - public PersonCard(Person person) { + public PersonCard(Person person, double previousDivider) { super(FXML); this.person = person; @@ -108,6 +112,11 @@ public PersonCard(Person person) { module.setText("Module: " + person.getModule().value); email.setText("Email: " + person.getEmail().value); + // Set the previous divider position from the old index + splitDisplay.setDividerPosition(0, previousDivider); + + computePixelHeight(); //Set up the pixel height variables + person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); @@ -127,33 +136,27 @@ public PersonCard(Person person) { } + private void computePixelHeight() { + //Dummy meeting used for computation of font size + Meeting dummy = new Meeting(new MeetingDescription("test"), new MeetingDateTime("12/02/2023 15:00")); + MeetingsCard test = new MeetingsCard(dummy, 0); + pixelHeight = test.getHeight(); + pixelWidth = test.getWidth(); + + } + private void setUpMeetingListView(ObservableList meetingsList) { meetingListView.setFocusTraversable(false); meetingListView.setItems(meetingsList); meetingListView.setCellFactory(listView -> new MeetingsListViewCell()); //Work around to set the correct height and width of the nested list view. - meetingListView.setPrefHeight((meetingsList.size() * ROW_HEIGHT) + 100); + meetingListView.setPrefHeight(meetingsList.size() * (pixelHeight + (2.8 * pixelHeight)) + pixelHeight); meetingListView.setPrefWidth(getLongestWidth(meetingsList)); meetingListView.setFocusTraversable(false); meetingListView.setMouseTransparent(true); } - private double getLongestWidth(List meetingList) { - double maxWidth = 0; - for (Meeting meet : meetingList) { - - //200 is to account for the default spacing within the items - double currentWidth = (meet.getDescription().description.length() + meet.getStartDate().toString().length()) - * LABEL_MEETING_WIDTH + 200; - - if (currentWidth > maxWidth) { - maxWidth = currentWidth; - } - } - return maxWidth; - } - private void setUpScrollPane(VBox display, Region content, boolean enableHbar, boolean swap, Region swapRegion) { ScrollPane scrollPane = new ScrollPane(); scrollPane.setContent(content); @@ -198,6 +201,35 @@ private void setUpScrollPane(VBox display, Region content, boolean enableHbar, b } } + private double getLongestWidth(List meetingList) { + double maxWidth = 0; + Meeting longestMeeting = null; + for (Meeting meet : meetingList) { + + double currentWidth = meet.getDescription().description.length() + meet.getStartDate().toString().length(); + if (currentWidth > maxWidth) { + maxWidth = currentWidth; + longestMeeting = meet; + } + + } + + if (longestMeeting != null) { + return (maxWidth * pixelWidth) + (pixelWidth * 15) + 3 * pixelWidth; + } else { + return 0; // empty meeting return zero. + } + } + + /** + * Gets the current divider position for usage in the UI. + * @return a double value of the current divider position. + */ + public double getCurrentDividerPosition() { + double[] positions = splitDisplay.getDividerPositions(); + return positions[0]; + } + /** * Custom {@code ListCell} that displays the graphics of a {@code Meetings} using a {@code MeetingsCard}. */ diff --git a/src/main/java/staffconnect/ui/PersonListPanel.java b/src/main/java/staffconnect/ui/PersonListPanel.java index fa751fd33ee..a7b09ec1d12 100644 --- a/src/main/java/staffconnect/ui/PersonListPanel.java +++ b/src/main/java/staffconnect/ui/PersonListPanel.java @@ -24,17 +24,20 @@ public class PersonListPanel extends UiPart { /** * Creates a {@code PersonListPanel} with the given {@code ObservableList}. */ - public PersonListPanel(ObservableList personList, PersonDisplay personDisplay) { + public PersonListPanel(ObservableList personList, PersonDisplay personDisplay, + DividerPosition dividerPosition) { super(FXML); personListView.setItems(personList); personListView.setCellFactory(listView -> new NameListViewCell()); personListView.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { - personDisplay.changePersonCard(new PersonCard(personListView.getSelectionModel().getSelectedItem())); + personDisplay.changePersonCard(new PersonCard(personListView.getSelectionModel().getSelectedItem(), + dividerPosition.getDividerPosition())); } }); personListView.setOnMouseClicked(event -> { - personDisplay.changePersonCard(new PersonCard(personListView.getSelectionModel().getSelectedItem())); + personDisplay.changePersonCard(new PersonCard(personListView.getSelectionModel().getSelectedItem(), + dividerPosition.getDividerPosition())); }); } @@ -47,10 +50,23 @@ public void setListSelectedIndex(int index) { // guard clause to prevent invalid index if (index >= 0 && index < personListView.getItems().size()) { personListView.getSelectionModel().clearAndSelect(index); + personListView.scrollTo(index); } } + /** + * Represents a function that gets the previous divider position. + */ + @FunctionalInterface + public interface DividerPosition { + + /** + * Gets the current divider position + */ + double getDividerPosition(); + } + /** * Represents a function that can change PersonCard. */ diff --git a/src/test/java/staffconnect/logic/commands/AddMeetingCommandTest.java b/src/test/java/staffconnect/logic/commands/AddMeetingCommandTest.java index d715d388b85..d984b2bcd0a 100644 --- a/src/test/java/staffconnect/logic/commands/AddMeetingCommandTest.java +++ b/src/test/java/staffconnect/logic/commands/AddMeetingCommandTest.java @@ -9,8 +9,9 @@ import static staffconnect.logic.commands.CommandTestUtil.VALID_MEETING_APRIL; import static staffconnect.logic.commands.CommandTestUtil.VALID_MEETING_FINALS; import static staffconnect.logic.commands.CommandTestUtil.assertCommandFailure; -import static staffconnect.logic.commands.CommandTestUtil.assertCommandSuccess; +import static staffconnect.logic.commands.CommandTestUtil.assertCommandSuccessWithPerson; import static staffconnect.logic.commands.CommandTestUtil.showPersonAtIndex; +import static staffconnect.model.meeting.comparator.MeetingDateThenDescriptionComparator.MEETING_DATE_THEN_DESCRIPTION_COMPARATOR; import static staffconnect.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static staffconnect.testutil.TypicalIndexes.INDEX_SECOND_PERSON; import static staffconnect.testutil.TypicalPersons.getTypicalStaffBook; @@ -44,16 +45,24 @@ private Person buildValidPerson() { @Test public void execute_allFieldsValid_success() { - Person validPerson = buildValidPerson(); + + //Requires both filtered predicate to be the same + TEST_MODEL.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); AddMeetingCommand addMeetingCommand = new AddMeetingCommand(INDEX_FIRST_PERSON, VALID_MEETING); String expectedMessage = String.format(AddMeetingCommand.MESSAGE_SUCCESS, Messages.format(VALID_MEETING)); Model expectedModel = new ModelManager(new StaffBook(TEST_MODEL.getStaffBook()), new UserPrefs()); - expectedModel.setPerson(TEST_MODEL.getSortedFilteredPersonList().get(0), validPerson); + expectedModel.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); + + Person validPerson = buildValidPerson(); + validPerson.addMeetings(VALID_MEETING); + validPerson.updateSortedMeetingList(MEETING_DATE_THEN_DESCRIPTION_COMPARATOR); + expectedModel.setPerson(expectedModel.getSortedFilteredPersonList().get(0), validPerson); - assertCommandSuccess(addMeetingCommand, TEST_MODEL, expectedMessage, expectedModel); + assertCommandSuccessWithPerson(addMeetingCommand, TEST_MODEL, + expectedMessage, expectedModel, validPerson, 0); } @Test @@ -95,7 +104,7 @@ public void equals() { final AddMeetingCommand standardCommand = new AddMeetingCommand(INDEX_FIRST_PERSON, VALID_MEETING); // same values -> returns true final Meeting copyMeeting = - new Meeting(new MeetingDescription(VALID_DESCRIPTION_MIDTERMS), new MeetingDateTime(VALID_DATE_MARCH)); + new Meeting(new MeetingDescription(VALID_DESCRIPTION_MIDTERMS), new MeetingDateTime(VALID_DATE_MARCH)); AddMeetingCommand commandWithSameValues = new AddMeetingCommand(INDEX_FIRST_PERSON, copyMeeting); assertEquals(standardCommand, commandWithSameValues); @@ -123,7 +132,7 @@ public void toStringMethod() { Index index = Index.fromOneBased(1); AddMeetingCommand addMeetingCommand = new AddMeetingCommand(index, VALID_MEETING); String expected = - AddMeetingCommand.class.getCanonicalName() + "{index=" + index + ", toAdd=" + VALID_MEETING + "}"; + AddMeetingCommand.class.getCanonicalName() + "{index=" + index + ", toAdd=" + VALID_MEETING + "}"; assertEquals(expected, addMeetingCommand.toString()); } diff --git a/src/test/java/staffconnect/logic/commands/CommandResultTest.java b/src/test/java/staffconnect/logic/commands/CommandResultTest.java index ae075b60602..d944405cf6e 100644 --- a/src/test/java/staffconnect/logic/commands/CommandResultTest.java +++ b/src/test/java/staffconnect/logic/commands/CommandResultTest.java @@ -4,17 +4,23 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static staffconnect.testutil.TypicalPersons.ALICE; +import static staffconnect.testutil.TypicalPersons.BOB; import org.junit.jupiter.api.Test; +import staffconnect.model.person.Person; + public class CommandResultTest { @Test public void equals() { CommandResult commandResult = new CommandResult("feedback"); + CommandResult commandResultWithPerson = new CommandResult("feedback", ALICE, 0); // same values -> returns true assertTrue(commandResult.equals(new CommandResult("feedback"))); assertTrue(commandResult.equals(new CommandResult("feedback", false, false))); + assertTrue(commandResultWithPerson.equals(new CommandResult("feedback", ALICE, 0))); // same object -> returns true assertTrue(commandResult.equals(commandResult)); @@ -33,14 +39,23 @@ public void equals() { // different exit value -> returns false assertFalse(commandResult.equals(new CommandResult("feedback", false, true))); + + // different person value + assertFalse(commandResultWithPerson.equals(new CommandResult("feedback", BOB, 0))); + // different index value + assertFalse(commandResultWithPerson.equals(new CommandResult("feedback", ALICE, 1))); + } @Test public void hashcode() { CommandResult commandResult = new CommandResult("feedback"); + CommandResult commandResultWithPerson = new CommandResult("feedback", ALICE, 0); // same values -> returns same hashcode assertEquals(commandResult.hashCode(), new CommandResult("feedback").hashCode()); + assertEquals(commandResultWithPerson.hashCode(), + new CommandResult("feedback", ALICE, 0).hashCode()); // different feedbackToUser value -> returns different hashcode assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode()); @@ -50,6 +65,14 @@ public void hashcode() { // different exit value -> returns different hashcode assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode()); + + // different person value -> returns different hashcode + assertNotEquals(commandResultWithPerson.hashCode(), new CommandResult("feedback", BOB, 0)); + + // different index value -> returns different hashcode + assertNotEquals(commandResultWithPerson.hashCode(), new CommandResult("feedback", ALICE, 1)); + + } @Test @@ -57,7 +80,8 @@ public void toStringMethod() { CommandResult commandResult = new CommandResult("feedback"); String expected = CommandResult.class.getCanonicalName() + "{feedbackToUser=" + commandResult.getFeedbackToUser() + ", showHelp=" + commandResult.isShowHelp() - + ", exit=" + commandResult.isExit() + "}"; + + ", exit=" + commandResult.isExit() + ", index=" + commandResult.getIndex() + + ", person=" + commandResult.getPersonToDisplay().map(Person::toString).orElse("null") + "}"; assertEquals(expected, commandResult.toString()); } } diff --git a/src/test/java/staffconnect/logic/commands/CommandTestUtil.java b/src/test/java/staffconnect/logic/commands/CommandTestUtil.java index a609b94967b..e1bbbd93f73 100644 --- a/src/test/java/staffconnect/logic/commands/CommandTestUtil.java +++ b/src/test/java/staffconnect/logic/commands/CommandTestUtil.java @@ -101,11 +101,11 @@ public class CommandTestUtil { public static final EditCommand.EditPersonDescriptor DESC_AMY; public static final EditCommand.EditPersonDescriptor DESC_BOB; public static final Meeting VALID_MEETING = - new Meeting(new MeetingDescription(VALID_DESCRIPTION_MIDTERMS), new MeetingDateTime(VALID_DATE_MARCH)); + new Meeting(new MeetingDescription(VALID_DESCRIPTION_MIDTERMS), new MeetingDateTime(VALID_DATE_MARCH)); public static final Meeting VALID_MEETING_FINALS = - new Meeting(new MeetingDescription(VALID_DESCRIPTION_FINALS), new MeetingDateTime(VALID_DATE_MARCH)); + new Meeting(new MeetingDescription(VALID_DESCRIPTION_FINALS), new MeetingDateTime(VALID_DATE_MARCH)); public static final Meeting VALID_MEETING_APRIL = - new Meeting(new MeetingDescription(VALID_DESCRIPTION_MIDTERMS), new MeetingDateTime(VALID_DATE_APRIL)); + new Meeting(new MeetingDescription(VALID_DESCRIPTION_MIDTERMS), new MeetingDateTime(VALID_DATE_APRIL)); public static final Meeting VALID_MEETING_STUDY = new Meeting(new MeetingDescription(VALID_DESCRIPTION_STUDY), new MeetingDateTime(VALID_DATE_MARCH)); @@ -121,13 +121,23 @@ public class CommandTestUtil { .withAvailabilities(VALID_AVAILABILITY_MON, VALID_AVAILABILITY_THUR).build(); } + /** + * 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, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + /** * Executes the given {@code command}, confirms that
* - the returned {@link CommandResult} matches {@code expectedCommandResult}
* - the {@code actualModel} matches {@code expectedModel} */ public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, - Model expectedModel) { + Model expectedModel) { try { CommandResult result = command.execute(actualModel); assertEquals(expectedCommandResult, result); @@ -139,14 +149,15 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm /** * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} - * that takes a string {@code expectedMessage}. + * that takes a string {@code expectedMessage} with a person and index . */ - public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, - Model expectedModel) { - CommandResult expectedCommandResult = new CommandResult(expectedMessage); + public static void assertCommandSuccessWithPerson(Command command, Model actualModel, + String expectedMessage, Model expectedModel, Person expectedPerson, int expectedIndex) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, expectedPerson, expectedIndex); assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); } + /** * Executes the given {@code command}, confirms that
* - a {@code CommandException} is thrown
diff --git a/src/test/java/staffconnect/logic/commands/DeleteMeetingCommandTest.java b/src/test/java/staffconnect/logic/commands/DeleteMeetingCommandTest.java index b31d2da18f0..4a7e748416d 100644 --- a/src/test/java/staffconnect/logic/commands/DeleteMeetingCommandTest.java +++ b/src/test/java/staffconnect/logic/commands/DeleteMeetingCommandTest.java @@ -6,7 +6,7 @@ import static staffconnect.logic.commands.CommandTestUtil.VALID_MEETING; import static staffconnect.logic.commands.CommandTestUtil.VALID_MEETING_STUDY; import static staffconnect.logic.commands.CommandTestUtil.assertCommandFailure; -import static staffconnect.logic.commands.CommandTestUtil.assertCommandSuccess; +import static staffconnect.logic.commands.CommandTestUtil.assertCommandSuccessWithPerson; import static staffconnect.logic.commands.CommandTestUtil.showMeetingAtIndex; import static staffconnect.testutil.TypicalIndexes.INDEX_FIRST_MEETING; import static staffconnect.testutil.TypicalIndexes.INDEX_FIRST_PERSON; @@ -38,6 +38,7 @@ public void execute_validIndexUnfilteredList_success() { //Set a dummy meeting first Person selectPerson = copyPersonWithNewMeetingManager(TEST_MODEL.getSortedFilteredPersonList() .get(INDEX_FIRST_PERSON.getZeroBased())); + Person expectedPerson = copyPersonWithNewMeetingManager(selectPerson); selectPerson.addMeetings(VALID_MEETING); TEST_MODEL.setPerson(selectPerson, selectPerson); @@ -49,7 +50,8 @@ public void execute_validIndexUnfilteredList_success() { ModelManager expectedModel = new ModelManager(getTypicalStaffBook(), new UserPrefs()); - assertCommandSuccess(deleteMeetingCommand, TEST_MODEL, expectedMessage, expectedModel); + assertCommandSuccessWithPerson(deleteMeetingCommand, TEST_MODEL, expectedMessage, expectedModel, + expectedPerson, 0); } private Person copyPersonWithNewMeetingManager(Person pickPerson) { @@ -103,7 +105,8 @@ public void execute_validIndexFilteredList_success() { showNoMeeting(expectedPersonSelect); //But it is not visible expectedModel.setPerson(expectedPersonSelect, expectedPersonSelect); - assertCommandSuccess(deleteMeetingCommand, TEST_MODEL, expectedMessage, expectedModel); + assertCommandSuccessWithPerson(deleteMeetingCommand, TEST_MODEL, expectedMessage, expectedModel, + expectedPersonSelect, 0); } /** diff --git a/src/test/java/staffconnect/logic/commands/SelectCommandTest.java b/src/test/java/staffconnect/logic/commands/SelectCommandTest.java new file mode 100644 index 00000000000..890110433b5 --- /dev/null +++ b/src/test/java/staffconnect/logic/commands/SelectCommandTest.java @@ -0,0 +1,112 @@ +package staffconnect.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 staffconnect.logic.commands.CommandTestUtil.assertCommandFailure; +import static staffconnect.logic.commands.CommandTestUtil.assertCommandSuccessWithPerson; +import static staffconnect.logic.commands.CommandTestUtil.showPersonAtIndex; +import static staffconnect.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static staffconnect.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static staffconnect.testutil.TypicalPersons.getTypicalStaffBook; + +import org.junit.jupiter.api.Test; + +import staffconnect.commons.core.index.Index; +import staffconnect.logic.Messages; +import staffconnect.model.Model; +import staffconnect.model.ModelManager; +import staffconnect.model.UserPrefs; +import staffconnect.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code SelectCommand}. + */ +public class SelectCommandTest { + + private Model model = new ModelManager(getTypicalStaffBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Person personToSelect = model.getSortedFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + SelectCommand selectCommand = new SelectCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(SelectCommand.MESSAGE_SELECT_PERSON_SUCCESS, + Messages.format(personToSelect)); + + ModelManager expectedModel = new ModelManager(model.getStaffBook(), new UserPrefs()); + + assertCommandSuccessWithPerson(selectCommand, model, expectedMessage, + expectedModel, personToSelect, 0); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getSortedFilteredPersonList().size() + 1); + SelectCommand selectCommand = new SelectCommand(outOfBoundIndex); + + assertCommandFailure(selectCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + //Set a predicate to filter the list to + model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); + Person personToSelect = model.getSortedFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + SelectCommand selectCommand = new SelectCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(SelectCommand.MESSAGE_SELECT_PERSON_SUCCESS, + Messages.format(personToSelect)); + + Model expectedModel = new ModelManager(model.getStaffBook(), new UserPrefs()); + + assertCommandSuccessWithPerson(selectCommand, model, expectedMessage, expectedModel, + personToSelect, 0); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of staff book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getStaffBook().getPersonList().size()); + + SelectCommand selectCommand = new SelectCommand(outOfBoundIndex); + + assertCommandFailure(selectCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void equals() { + SelectCommand selectFirstCommand = new SelectCommand(INDEX_FIRST_PERSON); + SelectCommand selectSecondCommand = new SelectCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(selectFirstCommand.equals(selectFirstCommand)); + + // same values -> returns true + SelectCommand selectFirstCommandCopy = new SelectCommand(INDEX_FIRST_PERSON); + assertTrue(selectFirstCommand.equals(selectFirstCommandCopy)); + + // different types -> returns false + assertFalse(selectFirstCommand.equals(1)); + + // null -> returns false + assertFalse(selectFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(selectFirstCommand.equals(selectSecondCommand)); + } + + @Test + public void toStringMethod() { + Index targetIndex = Index.fromOneBased(1); + SelectCommand selectCommand = new SelectCommand(targetIndex); + String expected = SelectCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}"; + assertEquals(expected, selectCommand.toString()); + } +} diff --git a/src/test/java/staffconnect/logic/parser/SelectCommandParserTest.java b/src/test/java/staffconnect/logic/parser/SelectCommandParserTest.java new file mode 100644 index 00000000000..74f58d45f54 --- /dev/null +++ b/src/test/java/staffconnect/logic/parser/SelectCommandParserTest.java @@ -0,0 +1,32 @@ +package staffconnect.logic.parser; + +import static staffconnect.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static staffconnect.logic.parser.CommandParserTestUtil.assertParseFailure; +import static staffconnect.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static staffconnect.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import staffconnect.logic.commands.SelectCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the SelectCommand code. For example, inputs "1" and "1 abc" take the + * same path through the SelectCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class SelectCommandParserTest { + + private final SelectCommandParser parser = new SelectCommandParser(); + + @Test + public void parse_validArgs_returnsSelectCommand() { + assertParseSuccess(parser, "1", new SelectCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); + } +}