From 2ec3b790129b95bd89c8390b7770dda84bb28334 Mon Sep 17 00:00:00 2001 From: cheweejia Date: Wed, 20 Oct 2021 22:06:56 +0800 Subject: [PATCH 1/3] Implement TagCommand that adds tag(s) to a student --- .../logic/commands/TagCommand.java | 97 +++++++++++++++++++ .../logic/parser/AcademyDirectoryParser.java | 4 + .../logic/parser/TagCommandParser.java | 44 +++++++++ 3 files changed, 145 insertions(+) create mode 100644 src/main/java/seedu/academydirectory/logic/commands/TagCommand.java create mode 100644 src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java diff --git a/src/main/java/seedu/academydirectory/logic/commands/TagCommand.java b/src/main/java/seedu/academydirectory/logic/commands/TagCommand.java new file mode 100644 index 00000000000..aa5276cf246 --- /dev/null +++ b/src/main/java/seedu/academydirectory/logic/commands/TagCommand.java @@ -0,0 +1,97 @@ +package seedu.academydirectory.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.academydirectory.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.academydirectory.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.academydirectory.commons.core.Messages; +import seedu.academydirectory.commons.core.index.Index; +import seedu.academydirectory.logic.commands.exceptions.CommandException; +import seedu.academydirectory.model.Model; +import seedu.academydirectory.model.student.Student; +import seedu.academydirectory.model.tag.Tag; + +/** + * Represents a command that tags a student. + */ +public class TagCommand extends Command { + public static final String COMMAND_WORD = "tag"; + + public static final String HELP_MESSAGE = "### Assigns tag(s) to a student: `tag`\\\n" + + "Tutors will be able to tag a student with relevant information.\\\n" + + "Format: `tag INDEX t/tag`\\\n" + + "Recommended use of tags:\n" + + "- Set a reminder to mark the student's mission with a `mission` tag\n" + + "- Note down the topics that the student needs help with\\\n" + + "Multiple tags can be assigned in a single command.\n" + + "Example:\n" + + "tag 1 t/mission\n" + + "tag 1 t/streams t/envModel"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Assigns tag(s) to a student.\n" + + "Parameters: " + + "INDEX " + + PREFIX_TAG + "TAG...\n" + + "Example: " + COMMAND_WORD + " " + + "1 " + + PREFIX_TAG + "mission " + + PREFIX_TAG + "streams"; + + public static final String MESSAGE_SUCCESS = "%1$s's tag(s) updated!"; + + private final Index index; + private final Set tag; + + /** + * Creates a TagCommand to add the specified {@code Tag} + * @param tag The tag to be assigned. + */ + public TagCommand(Index index, Set tag) { + requireAllNonNull(index, tag); + this.index = index; + this.tag = tag; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + Set editedTagSet = new HashSet<>(tag); + Student editedStudent = new Student( + studentToEdit.getName(), studentToEdit.getPhone(), studentToEdit.getEmail(), + studentToEdit.getTelegram(), studentToEdit.getStudioRecord(), + studentToEdit.getAssessment(), editedTagSet); + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(Model.PREDICATE_SHOW_ALL_STUDENTS); + return new CommandResult(String.format(MESSAGE_SUCCESS, studentToEdit.getName())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TagCommand)) { + return false; + } + + // state check + TagCommand e = (TagCommand) other; + return index.equals(e.index) + && tag.equals(e.tag); + } +} diff --git a/src/main/java/seedu/academydirectory/logic/parser/AcademyDirectoryParser.java b/src/main/java/seedu/academydirectory/logic/parser/AcademyDirectoryParser.java index c6db6e3ab4f..29f601dd7cc 100644 --- a/src/main/java/seedu/academydirectory/logic/parser/AcademyDirectoryParser.java +++ b/src/main/java/seedu/academydirectory/logic/parser/AcademyDirectoryParser.java @@ -23,6 +23,7 @@ import seedu.academydirectory.logic.commands.RetrieveCommand; import seedu.academydirectory.logic.commands.ShowCommand; import seedu.academydirectory.logic.commands.SortCommand; +import seedu.academydirectory.logic.commands.TagCommand; import seedu.academydirectory.logic.parser.exceptions.ParseException; /** @@ -93,6 +94,9 @@ public Command parseCommand(String userInput) throws ParseException { case SortCommand.COMMAND_WORD: return new SortCommandParser().parse(arguments); + case TagCommand.COMMAND_WORD: + return new TagCommandParser().parse(arguments); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); diff --git a/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java b/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java new file mode 100644 index 00000000000..a2cfdb2b3d9 --- /dev/null +++ b/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java @@ -0,0 +1,44 @@ +package seedu.academydirectory.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.academydirectory.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.academydirectory.logic.parser.CliSyntax.*; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.academydirectory.commons.core.index.Index; +import seedu.academydirectory.logic.commands.TagCommand; +import seedu.academydirectory.logic.parser.exceptions.ParseException; +import seedu.academydirectory.model.tag.Tag; + +public class TagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TagCommand + * and returns an TagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TAG); + if (!arePrefixesPresent(argMultimap, PREFIX_TAG) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getPreamble()); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + return new TagCommand(index, tagList); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} From 1685a8733be8c68ca3a65e762cef31cf0f43dbce Mon Sep 17 00:00:00 2001 From: cheweejia Date: Thu, 21 Oct 2021 11:16:07 +0800 Subject: [PATCH 2/3] Add test cases for tagging --- .../logic/parser/TagCommandParser.java | 2 +- .../logic/commands/GradeCommandTest.java | 21 ++--- .../logic/commands/TagCommandTest.java | 91 +++++++++++++++++++ .../logic/parser/TagCommandParserTest.java | 43 +++++++++ 4 files changed, 144 insertions(+), 13 deletions(-) create mode 100644 src/test/java/seedu/academydirectory/logic/commands/TagCommandTest.java create mode 100644 src/test/java/seedu/academydirectory/logic/parser/TagCommandParserTest.java diff --git a/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java b/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java index a2cfdb2b3d9..ee9aaa1e3d6 100644 --- a/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java +++ b/src/main/java/seedu/academydirectory/logic/parser/TagCommandParser.java @@ -2,7 +2,7 @@ import static java.util.Objects.requireNonNull; import static seedu.academydirectory.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.academydirectory.logic.parser.CliSyntax.*; +import static seedu.academydirectory.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; import java.util.stream.Stream; diff --git a/src/test/java/seedu/academydirectory/logic/commands/GradeCommandTest.java b/src/test/java/seedu/academydirectory/logic/commands/GradeCommandTest.java index b59bb578cab..cb555689c64 100644 --- a/src/test/java/seedu/academydirectory/logic/commands/GradeCommandTest.java +++ b/src/test/java/seedu/academydirectory/logic/commands/GradeCommandTest.java @@ -1,7 +1,7 @@ package seedu.academydirectory.logic.commands; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static seedu.academydirectory.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.academydirectory.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.academydirectory.testutil.Assert.assertThrows; @@ -71,28 +71,25 @@ public void equals() { // same values -> returns true GradeCommand commandWithSameValues = new GradeCommand(INDEX_FIRST_STUDENT, validAssessmentName1, validGrade1); - assertTrue(standardCommand.equals(commandWithSameValues)); + assertEquals(standardCommand, commandWithSameValues); // same object -> returns true - assertTrue(standardCommand.equals(standardCommand)); + assertEquals(standardCommand, standardCommand); // null -> returns false - assertFalse(standardCommand.equals(null)); + assertNotEquals(null, standardCommand); // different types -> returns false - assertFalse(standardCommand.equals(new ClearCommand())); + assertNotEquals(standardCommand, new ClearCommand()); // different index -> returns false - assertFalse(standardCommand.equals( - new GradeCommand(INDEX_SECOND_STUDENT, validAssessmentName1, validGrade1))); + assertNotEquals(standardCommand, new GradeCommand(INDEX_SECOND_STUDENT, validAssessmentName1, validGrade1)); // different assessment -> returns false - assertFalse(standardCommand.equals( - new GradeCommand(INDEX_FIRST_STUDENT, validAssessmentName2, validGrade1))); + assertNotEquals(standardCommand, new GradeCommand(INDEX_FIRST_STUDENT, validAssessmentName2, validGrade1)); // different grade -> returns false - assertFalse(standardCommand.equals( - new GradeCommand(INDEX_FIRST_STUDENT, validAssessmentName1, validGrade2))); + assertNotEquals(standardCommand, new GradeCommand(INDEX_FIRST_STUDENT, validAssessmentName1, validGrade2)); } } diff --git a/src/test/java/seedu/academydirectory/logic/commands/TagCommandTest.java b/src/test/java/seedu/academydirectory/logic/commands/TagCommandTest.java new file mode 100644 index 00000000000..9289c6fc64f --- /dev/null +++ b/src/test/java/seedu/academydirectory/logic/commands/TagCommandTest.java @@ -0,0 +1,91 @@ +package seedu.academydirectory.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static seedu.academydirectory.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.academydirectory.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.academydirectory.testutil.Assert.assertThrows; +import static seedu.academydirectory.testutil.TypicalIndexes.INDEX_FIRST_STUDENT; +import static seedu.academydirectory.testutil.TypicalIndexes.INDEX_SECOND_STUDENT; +import static seedu.academydirectory.testutil.TypicalStudents.getTypicalAcademyDirectory; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.academydirectory.commons.core.Messages; +import seedu.academydirectory.commons.core.index.Index; +import seedu.academydirectory.model.AcademyDirectory; +import seedu.academydirectory.model.Model; +import seedu.academydirectory.model.ModelManager; +import seedu.academydirectory.model.UserPrefs; +import seedu.academydirectory.model.student.Student; +import seedu.academydirectory.model.tag.Tag; +import seedu.academydirectory.testutil.StudentBuilder; + +class TagCommandTest { + + private final Model model = new ModelManager(getTypicalAcademyDirectory(), new UserPrefs()); + private final Set tagSet = new HashSet<>(); + private final String validTagName1 = "mission"; + private final String validTagName2 = "streams2"; + + @Test + public void constructor_nullStudent_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new TagCommand(null, null)); + } + + @Test + void execute_addTag_success() { + Student firstStudent = model.getFilteredStudentList().get(0); + + Student editedStudent = new StudentBuilder(firstStudent).withTags().build(); + TagCommand addTagCommand = + new TagCommand(INDEX_FIRST_STUDENT, tagSet); + String expectedMessage = + String.format(TagCommand.MESSAGE_SUCCESS, editedStudent.getName()); + Model expectedModel = new ModelManager(new AcademyDirectory(model.getAcademyDirectory()), new UserPrefs()); + expectedModel.setStudent(firstStudent, editedStudent); + assertCommandSuccess(addTagCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidPersonIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredStudentList().size() + 1); + TagCommand tagCommand = new TagCommand(outOfBoundIndex, tagSet); + + assertCommandFailure(tagCommand, model, Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + @Test + public void equals() { + Set tagSet1 = new HashSet<>(); + Set tagSet2 = new HashSet<>(); + tagSet1.add(new Tag(validTagName1)); + tagSet2.add(new Tag(validTagName2)); + + final TagCommand standardCommand = + new TagCommand(INDEX_FIRST_STUDENT, tagSet1); + + // same values -> returns true + TagCommand commandWithSameValues = + new TagCommand(INDEX_FIRST_STUDENT, tagSet1); + assertEquals(standardCommand, commandWithSameValues); + + // same object -> returns true + assertEquals(standardCommand, standardCommand); + + // null -> returns false + assertNotEquals(null, standardCommand); + + // different types -> returns false + assertNotEquals(standardCommand, new ClearCommand()); + + // different index -> returns false + assertNotEquals(standardCommand, new TagCommand(INDEX_SECOND_STUDENT, tagSet1)); + + // different grade -> returns false + assertNotEquals(standardCommand, new TagCommand(INDEX_FIRST_STUDENT, tagSet2)); + } +} diff --git a/src/test/java/seedu/academydirectory/logic/parser/TagCommandParserTest.java b/src/test/java/seedu/academydirectory/logic/parser/TagCommandParserTest.java new file mode 100644 index 00000000000..f27ffdc3739 --- /dev/null +++ b/src/test/java/seedu/academydirectory/logic/parser/TagCommandParserTest.java @@ -0,0 +1,43 @@ +package seedu.academydirectory.logic.parser; + +import static seedu.academydirectory.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.academydirectory.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.academydirectory.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.academydirectory.testutil.TypicalIndexes.INDEX_FIRST_STUDENT; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.academydirectory.logic.commands.TagCommand; +import seedu.academydirectory.model.tag.Tag; + +public class TagCommandParserTest { + private final TagCommandParser parser = new TagCommandParser(); + private final Set tag = new HashSet<>(); + private final String validTagName = "mission"; + private final String invalidTagName = "!!!!!!"; + private final String expectedInvalidTagMessage = Tag.MESSAGE_CONSTRAINTS; + + @Test + public void parse_validArgument_success() { + String input = INDEX_FIRST_STUDENT.getOneBased() + " " + + PREFIX_TAG + validTagName; + Tag validTag = new Tag(validTagName); + tag.clear(); + tag.add(validTag); + TagCommand expectedCommand = + new TagCommand(INDEX_FIRST_STUDENT, tag); + + assertParseSuccess(parser, input, expectedCommand); + } + + @Test + public void parse_invalidArgument_failure() { + String inputInvalidTag = INDEX_FIRST_STUDENT.getOneBased() + " " + + PREFIX_TAG + invalidTagName; + + assertParseFailure(parser, inputInvalidTag, expectedInvalidTagMessage); + } +} From ba6aa5bbc2faadb12a12ad1ebece81d9db8305cd Mon Sep 17 00:00:00 2001 From: cheweejia Date: Thu, 21 Oct 2021 21:29:27 +0800 Subject: [PATCH 3/3] Enable assertions --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 0a502578d62..cc288a9c25e 100644 --- a/build.gradle +++ b/build.gradle @@ -79,3 +79,7 @@ shadowJar { } defaultTasks 'clean', 'test' + +run { + enableAssertions = true +}