diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 66d8cb2ccfd..82adfcc4f8c 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -9,6 +9,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -34,6 +35,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of tasks */ + ObservableList getFilteredTaskList(); + /** Returns an unmodifiable view of the filtered list of weddings */ ObservableList getFilteredWeddingList(); diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 8e48bb01f1b..d0b6a132818 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -16,6 +16,7 @@ import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; import seedu.address.storage.Storage; @@ -72,6 +73,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + @Override public ObservableList getFilteredWeddingList() { return model.getFilteredWeddingList(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index 60ae8413d98..122d0c0cdde 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -8,6 +8,7 @@ import seedu.address.logic.parser.Prefix; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -18,6 +19,20 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_INVALID_DATE_FORMAT = "Invalid date format. Expected format: yyyy-MM-dd"; + + public static final String MESSAGE_INVALID_TASK_TYPE = "Unknown task type: %1$s. " + + "Expected one of: todo, deadline, event."; + + public static final String MESSAGE_INVALID_DEADLINE_FORMAT = "Invalid deadline format. " + + "Usage: create-task tk/deadline [description] /by [date]"; + public static final String MESSAGE_INVALID_EVENT_FORMAT = "Invalid event format." + + " Usage: create-task tk/event [description] /from [start] /to [end]"; + + public static final String MESSAGE_INCOMPLETE_TASK_DESCRIPTION = "Task description is incomplete. " + + "Expected format: tk/[task type] [task details]."; + public static final String MESSAGE_TO_BEFORE_FROM_INVALID = "\"From\" date must be before \"To\" date."; public static final String MESSAGE_INVALID_WEDDING_DISPLAYED_INDEX = "The wedding index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = @@ -78,4 +93,7 @@ public static String format(Wedding wedding) { return wedding.getWeddingName().toString(); } + public static String format(Task task) { + return task.getDescription(); + } } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 9719d8c0931..6487cfefa12 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -20,11 +20,12 @@ public class CommandResult { private final boolean exit; /** - * Which view to switch to. + * Enum to indicate which view to switch to. */ public enum SwitchView { PERSON, WEDDING, + TASK, NONE } @@ -77,7 +78,7 @@ public boolean isShowHelp() { */ public boolean isSwitchView() { return switch (switchView) { - case PERSON, WEDDING -> true; + case PERSON, WEDDING, TASK -> true; default -> false; }; } diff --git a/src/main/java/seedu/address/logic/commands/CreateTaskCommand.java b/src/main/java/seedu/address/logic/commands/CreateTaskCommand.java new file mode 100644 index 00000000000..f985dc6038a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CreateTaskCommand.java @@ -0,0 +1,85 @@ +package seedu.address.logic.commands; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK; + +import java.util.HashSet; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * Adds a Task to the address book. + */ +public class CreateTaskCommand extends Command { + + public static final String COMMAND_WORD = "create-task"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a task in the address book. \n" + + "Parameters: " + + PREFIX_TASK + "TASK_TYPE TASK_DESCRIPTION [ADDITIONAL_FIELDS]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TASK + "todo Setup venue decorations\n" + + COMMAND_WORD + " " + + PREFIX_TASK + "deadline Submit proposal /by 2024-10-31"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book"; + + private final HashSet tasksToAdd; + + /** + * Creates a CreateTaskCommand to add the specified {@code task} to the Wedlinker + * @param task The {@code task} to be added to the Wedlinker + */ + public CreateTaskCommand(HashSet task) { + requireNonNull(task); + tasksToAdd = task; + } + + /** + * Returns the set of tasks to be added. + */ + public HashSet getTaskToAdd() { + return tasksToAdd; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + for (Task task : tasksToAdd) { + if (model.hasTask(task)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + model.addTask(task); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, tasksToAdd)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + // null case handled by instanceof + if (!(obj instanceof CreateTaskCommand)) { + return false; + } + + CreateTaskCommand otherCreateTaskCommand = (CreateTaskCommand) obj; + return tasksToAdd.equals(otherCreateTaskCommand.tasksToAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("taskToAdd", tasksToAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java new file mode 100644 index 00000000000..75f6b4a9ee1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java @@ -0,0 +1,69 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +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.task.Task; + +/** + * Deletes a task identified using it's displayed index from the address book. + */ +public class DeleteTaskCommand extends Command { + + public static final String COMMAND_WORD = "delete-task"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task identified by the index number used in the displayed task list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted task: %1$s"; + + private final Index targetIndex; + + public DeleteTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTask(taskToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete.toString())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteTaskCommand)) { + return false; + } + + DeleteTaskCommand otherDeleteTaskCommand = (DeleteTaskCommand) other; + return targetIndex.equals(otherDeleteTaskCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e20b0132d1f..7a4cc344749 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -28,6 +28,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -104,8 +105,10 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); Set updatedWeddings = editPersonDescriptor.getWeddings().orElse(personToEdit.getWeddings()); + Set updatedTasks = editPersonDescriptor.getTasks().orElse(personToEdit.getTasks()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, updatedWeddings); + return new Person(updatedName, updatedPhone, updatedEmail, + updatedAddress, updatedTags, updatedWeddings, updatedTasks); } @Override @@ -143,6 +146,7 @@ public static class EditPersonDescriptor { private Address address; private Set tags; private Set weddings; + private Set tasks; public EditPersonDescriptor() {} @@ -157,6 +161,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setAddress(toCopy.address); setTags(toCopy.tags); setWeddings(toCopy.weddings); + setTasks(toCopy.tasks); } /** @@ -206,6 +211,23 @@ public void setTags(Set tags) { this.tags = (tags != null) ? new HashSet<>(tags) : null; } + /** + * Returns an unmodifiable task set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tasks} is null. + */ + public Optional> getTasks() { + return (tasks != null) ? Optional.of(Collections.unmodifiableSet(tasks)) : Optional.empty(); + } + + /** + * Sets {@code tasks} to this object's {@code tasks}. + * A defensive copy of {@code tasks} is used internally. + */ + public void setTasks(Set tasks) { + this.tasks = (tasks != null) ? new HashSet<>(tasks) : null; + } + /** * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. diff --git a/src/main/java/seedu/address/logic/commands/ListTasksCommand.java b/src/main/java/seedu/address/logic/commands/ListTasksCommand.java new file mode 100644 index 00000000000..f684ebe820f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTasksCommand.java @@ -0,0 +1,25 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import seedu.address.logic.commands.CommandResult.SwitchView; +import seedu.address.model.Model; + + +/** + * Lists all tasks in the address book to the user. + */ +public class ListTasksCommand extends Command { + + public static final String COMMAND_WORD = "list-tasks"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(MESSAGE_SUCCESS, SwitchView.TASK); + } +} diff --git a/src/main/java/seedu/address/logic/commands/TagCommand.java b/src/main/java/seedu/address/logic/commands/TagCommand.java index 372b0b331bb..7719c444d6a 100644 --- a/src/main/java/seedu/address/logic/commands/TagCommand.java +++ b/src/main/java/seedu/address/logic/commands/TagCommand.java @@ -91,7 +91,8 @@ public CommandResult execute(Model model) throws CommandException { personToEdit.getEmail(), personToEdit.getAddress(), updatedTags, - personToEdit.getWeddings()); + personToEdit.getWeddings(), + personToEdit.getTasks()); model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); diff --git a/src/main/java/seedu/address/logic/commands/UntagCommand.java b/src/main/java/seedu/address/logic/commands/UntagCommand.java index 8a40a94b062..513ff0ba5e0 100644 --- a/src/main/java/seedu/address/logic/commands/UntagCommand.java +++ b/src/main/java/seedu/address/logic/commands/UntagCommand.java @@ -92,7 +92,8 @@ public CommandResult execute(Model model) throws CommandException { personToEdit.getEmail(), personToEdit.getAddress(), updatedTags, - personToEdit.getWeddings()); + personToEdit.getWeddings(), + personToEdit.getTasks()); model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); diff --git a/src/main/java/seedu/address/logic/commands/wedding/AssignWeddingCommand.java b/src/main/java/seedu/address/logic/commands/wedding/AssignWeddingCommand.java index f5087fe2838..9e819eed9e8 100644 --- a/src/main/java/seedu/address/logic/commands/wedding/AssignWeddingCommand.java +++ b/src/main/java/seedu/address/logic/commands/wedding/AssignWeddingCommand.java @@ -105,7 +105,8 @@ public CommandResult execute(Model model) throws CommandException { personToEdit.getEmail(), personToEdit.getAddress(), personToEdit.getTags(), - updatedWeddings); + updatedWeddings, + personToEdit.getTasks()); model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); diff --git a/src/main/java/seedu/address/logic/commands/wedding/UnassignWeddingCommand.java b/src/main/java/seedu/address/logic/commands/wedding/UnassignWeddingCommand.java index deb84418f40..4135e1fd4fd 100644 --- a/src/main/java/seedu/address/logic/commands/wedding/UnassignWeddingCommand.java +++ b/src/main/java/seedu/address/logic/commands/wedding/UnassignWeddingCommand.java @@ -90,7 +90,8 @@ public CommandResult execute(Model model) throws CommandException { personToEdit.getEmail(), personToEdit.getAddress(), personToEdit.getTags(), - updatedWeddings); + updatedWeddings, + personToEdit.getTasks()); model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 5eb7e572b60..479eac4ff3e 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -6,6 +6,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK; import static seedu.address.logic.parser.CliSyntax.PREFIX_WEDDING; import java.util.Set; @@ -19,6 +20,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -48,8 +50,9 @@ public AddCommand parse(String args) throws ParseException { Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).orElse("")); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); Set weddingList = ParserUtil.parseWeddings(argMultimap.getAllValues(PREFIX_WEDDING)); + Set taskList = ParserUtil.parseTasks(argMultimap.getAllValues(PREFIX_TASK)); - Person person = new Person(name, phone, email, address, tagList, weddingList); + Person person = new Person(name, phone, email, address, tagList, weddingList, taskList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 56d352a957a..f3084842d71 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -12,12 +12,15 @@ import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CreateTagCommand; +import seedu.address.logic.commands.CreateTaskCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.commands.DeleteTaskCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListTasksCommand; import seedu.address.logic.commands.TagCommand; import seedu.address.logic.commands.UntagCommand; import seedu.address.logic.commands.findcommand.FindCommand; @@ -85,6 +88,9 @@ public Command parseCommand(String userInput) throws ParseException { case UntagCommand.COMMAND_WORD -> new UntagCommandParser().parse(arguments); case AssignWeddingCommand.COMMAND_WORD -> new AssignWeddingCommandParser().parse(arguments); case UnassignWeddingCommand.COMMAND_WORD -> new UnassignWeddingCommandParser().parse(arguments); + case CreateTaskCommand.COMMAND_WORD -> new CreateTaskCommandParser().parse(arguments); + case ListTasksCommand.COMMAND_WORD -> new ListTasksCommand(); + case DeleteTaskCommand.COMMAND_WORD -> new DeleteTaskCommandParser().parse(arguments); default -> { logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index fe1979ffb12..2e81e4ebe82 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -12,6 +12,7 @@ public class CliSyntax { public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_TASK = new Prefix("tk/"); public static final Prefix PREFIX_WEDDING = new Prefix("w/"); public static final Prefix PREFIX_EDIT_WEDDING_PERSON_1 = new Prefix("p1/"); public static final Prefix PREFIX_EDIT_WEDDING_PERSON_2 = new Prefix("p2/"); diff --git a/src/main/java/seedu/address/logic/parser/CreateTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/CreateTaskCommandParser.java new file mode 100644 index 00000000000..3c69228c44e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CreateTaskCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.CreateTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.Task; + +/** + * Parses input arguments and creates a new CreateTaskCommand object + */ +public class CreateTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateTaskCommand + * and returns an CreateTaskCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public CreateTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TASK); + + if (!arePrefixesPresent(argMultimap, PREFIX_TASK)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTaskCommand.MESSAGE_USAGE)); + } + + List taskDescriptions = argMultimap.getAllValues(PREFIX_TASK); + if (taskDescriptions.isEmpty() || taskDescriptions.stream().anyMatch(String::isBlank)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTaskCommand.MESSAGE_USAGE)); + } + + // Parse the tasks from the task descriptions + Set tasks = ParserUtil.parseTasks(taskDescriptions); + + return new CreateTaskCommand(new HashSet<>(tasks)); + } + + /** + * 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()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java new file mode 100644 index 00000000000..5839657bd80 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteTaskCommand object + */ +public class DeleteTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTaskCommand + * and returns a DeleteTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTaskCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteTaskCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 75337073eff..074c1aaffc7 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,12 +2,17 @@ import static java.util.Objects.requireNonNull; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; +import seedu.address.logic.Messages; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; @@ -15,6 +20,11 @@ import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.ParsedTask; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; import seedu.address.model.wedding.Wedding; import seedu.address.model.wedding.WeddingName; @@ -24,6 +34,7 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -38,6 +49,10 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + // ======================================================================= + // Person Parsing Methods + // ======================================================================= + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -133,6 +148,152 @@ public static Set parseTags(Collection tags) throws ParseException return tagSet; } + // ======================================================================= + // Task Parsing Methods + // ======================================================================= + + /** + * Parses a task description to determine the task type and details. + * + * @throws ParseException if the task format is invalid. + */ + public static Task parseTask(String taskDescription) throws ParseException { + requireNonNull(taskDescription); + ParsedTask parsedTask = parseTaskTypeAndDetails(taskDescription); + String taskType = parsedTask.getTaskType(); + String taskDetails = parsedTask.getTaskDetails(); + + switch (taskType) { + case "todo": + return parseTodoTask(taskDetails); + case "deadline": + return parseDeadlineTask(taskDetails); + case "event": + return parseEventTask(taskDetails); + default: + throw new ParseException(String.format(Messages.MESSAGE_INVALID_TASK_TYPE, taskType)); + } + } + + /** + * Parses {@code Collection tags} into a {@code Set}. + */ + public static Set parseTasks(List taskDescriptions) throws ParseException { + final Set taskSet = new HashSet<>(); + for (String taskDescription : taskDescriptions) { + taskSet.add(parseTask(taskDescription)); + } + return taskSet; + } + + /** + * Parses the task description to extract the task type and task details. + * + * @throws ParseException if the format is invalid. + */ + private static ParsedTask parseTaskTypeAndDetails(String taskDescription) throws ParseException { + requireNonNull(taskDescription); + // Splits the descriptions into a maximum of 2 tokens, based on first whitespace present + String[] tokens = taskDescription.split("\\s+", 2); + + if (tokens.length < 2) { + throw new ParseException(Messages.MESSAGE_INCOMPLETE_TASK_DESCRIPTION); + } + + String taskType = tokens[0].toLowerCase(); + String taskDetails = tokens[1].trim(); + + return new ParsedTask(taskType, taskDetails); + } + + /** + * Parses the task details for a "Todo" task and returns a {@code Todo} object. + * + * @param taskDetails The task details to parse. + * @return A Todo object. + */ + private static Todo parseTodoTask(String taskDetails) { + return new Todo(taskDetails.trim()); + } + + /** + * Parses the task details for a "Deadline" task and returns a {@code Deadline} object. + * + * @param taskDetails The task details to parse. + * @return A Deadline object. + * @throws ParseException if the format is invalid. + */ + private static Deadline parseDeadlineTask(String taskDetails) throws ParseException { + String[] deadlineParts = taskDetails.split("/by ", 2); + // Check if both description and deadline are present + if (deadlineParts.length < 2 || deadlineParts[0].trim().isEmpty()) { + throw new ParseException(Messages.MESSAGE_INVALID_DEADLINE_FORMAT); + } + String description = deadlineParts[0].trim(); + String byDate = deadlineParts[1].trim(); + + validateDateFormat(byDate); + return new Deadline(description, byDate); + } + + /** + * Parses the task details for an "Event" task and returns an {@code Event} object. + * + * @param taskDetails The task details to parse. + * @return An Event object. + * @throws ParseException if the format is invalid. + */ + private static Event parseEventTask(String taskDetails) throws ParseException { + String[] eventParts = taskDetails.split("/from", 2); + if (eventParts.length < 2 || !eventParts[1].contains("/to")) { + throw new ParseException(Messages.MESSAGE_INVALID_EVENT_FORMAT); + } + String description = eventParts[0].trim(); + String[] dateParts = eventParts[1].split("/to", 2); + String startDate = dateParts[0].trim(); + String endDate = dateParts[1].trim(); + validateDateFormat(startDate, endDate); + + return new Event(description, startDate, endDate); + } + + /** + * Validates the date format against the predefined pattern (yyyy-MM-dd). + * + * @throws ParseException if the date format is invalid. + */ + private static void validateDateFormat(String date) throws ParseException { + try { + LocalDate.parse(date, DATE_FORMATTER); + } catch (DateTimeParseException e) { + throw new ParseException(Messages.MESSAGE_INVALID_DATE_FORMAT); + } + } + + /** + * Validates the date format for two dates and checks if the "from" date is before the "to" date. + * + * @throws ParseException if the date format is invalid or "from" date is not before "to" date. + */ + private static void validateDateFormat(String fromDate, String toDate) throws ParseException { + LocalDate from; + LocalDate to; + try { + from = LocalDate.parse(fromDate, DATE_FORMATTER); + to = LocalDate.parse(toDate, DATE_FORMATTER); + } catch (DateTimeParseException e) { + throw new ParseException(Messages.MESSAGE_INVALID_DATE_FORMAT); + } + + if (!from.isBefore(to)) { + throw new ParseException(Messages.MESSAGE_TO_BEFORE_FROM_INVALID); + } + } + + // ======================================================================= + // Wedding Parsing Methods + // ======================================================================= + /** * Parses {@code String} wedding into {@code Wedding} object. * Leading and trailing whitespaces will be trimmed. diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index d7100716686..8b5c133014d 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -11,6 +11,8 @@ import seedu.address.model.person.UniquePersonList; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; +import seedu.address.model.task.Task; +import seedu.address.model.task.UniqueTaskList; import seedu.address.model.wedding.UniqueWeddingList; import seedu.address.model.wedding.Wedding; @@ -22,6 +24,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; private final UniqueTagList tags; + private final UniqueTaskList tasks; private final UniqueWeddingList weddings; /* @@ -34,6 +37,7 @@ public class AddressBook implements ReadOnlyAddressBook { { persons = new UniquePersonList(); tags = new UniqueTagList(); + tasks = new UniqueTaskList(); weddings = new UniqueWeddingList(); } @@ -47,6 +51,7 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { resetData(toBeCopied); initialiseTags(); initialiseWeddings(); + initialiseTasks(); } //// list overwrite operations @@ -60,6 +65,7 @@ public void resetData(ReadOnlyAddressBook newData) { setPersons(newData.getPersonList()); setTags(newData.getTagList()); setWeddings(newData.getWeddingList()); + setTasks(newData.getTaskList()); } /** @@ -78,6 +84,14 @@ public void setWeddings(List weddings) { this.weddings.setWeddings(weddings); } + /** + * Replaces the contents of the task list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + this.tasks.setTasks(tasks); + } + //// person-level operations /** @@ -134,6 +148,43 @@ public boolean hasTag(Tag tag) { return tags.contains(tag); } + + /** + * Adds a task to the Wedlinker. + * The task must not already exist in the Wedlinker. + * @param task A {@code Task} object to be added. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Returns true if a task with the same description as {@code task} exists in the Wedlinker. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + + /** + * Replaces the given task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the address book. + * The task identity of {@code editedTask} must not be the same as another existing task in the address book. + */ + public void setTask(Task target, Task editedTask) { + requireNonNull(editedTask); + tasks.setTask(target, editedTask); + } + + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTask(Task key) { + tasks.remove(key); + } + /** * Adds a wedding to the Wedlinker * The wedding must not already exist in the Wedlinker @@ -143,6 +194,7 @@ public void addWedding(Wedding wedding) { weddings.add(wedding); } + /** * Replaces the given wedding {@code target} in the list with {@code editedWedding}. * {@code target} must exist in the address book. @@ -212,6 +264,21 @@ public void initialiseTags() { } } + /** + * Creates any tasks that is attached to a person but not initialised. + * This function is only to be used when loading from Storage. + */ + public void initialiseTasks() { + for (Person person : persons) { + Set taskForPerson = person.getTasks(); + for (Task task : taskForPerson) { + if (!this.hasTask(task)) { + this.addTask(task); + } + } + } + } + /** * Creates any weddings attached to a person but not initialised. * This function is only to be used when loading from Storage. @@ -246,6 +313,11 @@ public ObservableList getTagList() { return tags.asUnmodifiableObservableList(); } + @Override + public ObservableList getTaskList() { + return tasks.asUnmodifiableObservableList(); + } + @Override public ObservableList getWeddingList() { return weddings.asUnmodifiableObservableList(); diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 98b4d2c379a..526b4f8c2ba 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -7,6 +7,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -17,6 +18,7 @@ public interface Model { Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; Predicate PREDICATE_SHOW_ALL_TAGS = unused -> true; Predicate PREDICATE_SHOW_ALL_WEDDINGS = unused -> true; + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -129,6 +131,39 @@ public interface Model { */ void deleteTag(Tag toDelete); + /** + * Returns true if a task with the same name as {@code toAdd} exists in the Wedlinker. + */ + boolean hasTask(Task toAdd); + + /** + * Adds the given task. + * {@code task} must not already exist in the address book. + */ + void addTask(Task toAdd); + + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredTaskList(); + + /** + * Replaces the given Task {@code target} with {@code editedTask}. + * {@code target} must exist in the address book. + * The task identity of {@code editedTask} must not be the same as another existing Task in the address book. + */ + void setTask(Task target, Task editedTask); + + /** + * Deletes the given task. + * The task must exist in the Wedlinker. + */ + void deleteTask(Task toDelete); + + /** + * Updates the filter of the filtered task list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTaskList(Predicate predicate); + /** * Returns true if a wedding with the same name as {@code toAdd} exists in the Wedlinker. * @param toAdd A {@code Wedding} object, will be checked to see if the model already has this. diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0427ffcc59b..0ef46b52485 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -13,6 +13,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -27,6 +28,8 @@ public class ModelManager implements Model { private final FilteredList filteredTags; private final FilteredList filteredWeddings; + private final FilteredList filteredTasks; + /** * Initializes a ModelManager with the given addressBook and userPrefs. */ @@ -40,6 +43,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); filteredTags = new FilteredList<>(this.addressBook.getTagList()); filteredWeddings = new FilteredList<>(this.addressBook.getWeddingList()); + filteredTasks = new FilteredList<>(this.addressBook.getTaskList()); } public ModelManager() { @@ -139,6 +143,28 @@ public void deleteTag(Tag target) { addressBook.removeTag(target); } + @Override + public void addTask(Task task) { + addressBook.addTask(task); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + } + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); + } + + @Override + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + addressBook.setTask(target, editedTask); + } + + @Override + public void deleteTask(Task target) { + addressBook.removeTask(target); + } + @Override public void addWedding(Wedding toAdd) { addressBook.addWedding(toAdd); @@ -196,6 +222,16 @@ public void updateFilteredTagList(Predicate predicate) { filteredTags.setPredicate(predicate); } + @Override + public ObservableList getFilteredTaskList() { + return filteredTasks; + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + requireNonNull(predicate); + filteredTasks.setPredicate(predicate); + } @Override public ObservableList getFilteredWeddingList() { return filteredWeddings; @@ -229,6 +265,7 @@ public boolean equals(Object other) { && userPrefs.equals(otherModelManager.userPrefs) && filteredPersons.equals(otherModelManager.filteredPersons) && filteredTags.equals(otherModelManager.filteredTags) + && filteredTasks.equals(otherModelManager.filteredTasks) && filteredWeddings.equals(otherModelManager.filteredWeddings); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index d0b216efe1c..f6e53267d2f 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -3,6 +3,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -22,6 +23,12 @@ public interface ReadOnlyAddressBook { */ ObservableList getTagList(); + /** + * Returns an unmodifiable view of the tasks list. + * This list will not contain any duplicate tasks. + */ + ObservableList getTaskList(); + /** * Returns an unmodifiable view of the weddings list. * This list will not contain any duplicate weddings. diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index e30bc76a0d2..a8b74a1d43e 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -9,6 +9,7 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; /** @@ -25,19 +26,22 @@ public class Person { // Data fields private final Address address; private final Set tags = new HashSet<>(); + private final Set tasks = new HashSet<>(); private final Set weddings = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags, Set weddings) { - requireAllNonNull(name, phone, email, address, tags, weddings); + public Person(Name name, Phone phone, Email email, Address address, + Set tags, Set weddings, Set tasks) { + requireAllNonNull(name, phone, email, address, tags, weddings, tasks); this.name = name; this.phone = phone; this.email = email; this.address = address; this.tags.addAll(tags); this.weddings.addAll(weddings); + this.tasks.addAll(tasks); } public Name getName() { @@ -64,6 +68,14 @@ public Set getTags() { return Collections.unmodifiableSet(tags); } + /** + * Returns an immutable task set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTasks() { + return Collections.unmodifiableSet(tasks); + } + /** * Returns an immutable wedding set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -106,13 +118,14 @@ public boolean equals(Object other) { && email.equals(otherPerson.email) && address.equals(otherPerson.address) && tags.equals(otherPerson.tags) - && weddings.equals((otherPerson.weddings)); + && weddings.equals(otherPerson.weddings) + && tasks.equals((otherPerson.tasks)); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags, weddings); + return Objects.hash(name, phone, email, address, tags, weddings, tasks); } @Override @@ -124,6 +137,7 @@ public String toString() { .add("address", address) .add("tags", tags) .add("weddings", weddings) + .add("tasks", tasks) .toString(); } diff --git a/src/main/java/seedu/address/model/task/Date.java b/src/main/java/seedu/address/model/task/Date.java new file mode 100644 index 00000000000..cb77c259770 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Date.java @@ -0,0 +1,73 @@ +package seedu.address.model.task; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * Represents a Task's date (either start or end date). + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)}. + */ +public class Date { + public static final String MESSAGE_CONSTRAINTS = "Date should be in the format yyyy-MM-dd."; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private final LocalDate date; + + /** + * Constructs a {@code TaskDate}. + * + * @param date A valid date string in the format "yyyy-MM-dd". + */ + public Date(String date) { + Objects.requireNonNull(date); + this.date = LocalDate.parse(date, FORMATTER); + } + + /** + * Checks if the given string is a valid date in the format "yyyy-MM-dd". + */ + public static boolean isValidDate(String test) { + try { + LocalDate.parse(test, FORMATTER); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Returns the formatted date. + */ + public String format(DateTimeFormatter formatter) { + return date.format(formatter); + } + + public LocalDate getDate() { + return date; + } + + @Override + public String toString() { + return date.format(FORMATTER); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof Date)) { + return false; + } + + Date otherDate = (Date) other; + return date.equals(otherDate.date); + } + + @Override + public int hashCode() { + return Objects.hash(date); + } +} diff --git a/src/main/java/seedu/address/model/task/Deadline.java b/src/main/java/seedu/address/model/task/Deadline.java new file mode 100644 index 00000000000..9304310818e --- /dev/null +++ b/src/main/java/seedu/address/model/task/Deadline.java @@ -0,0 +1,80 @@ +package seedu.address.model.task; + +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * The Deadline class represents a task with a deadline. + * It extends the Task class and adds a LocalDate field to store the deadline date. + */ +public class Deadline extends Task { + + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private Date by; + + + /** + * Constructs a Deadline task with the specified description and deadline date. + * + * @param description The description of the task. + * @param by The deadline date in the format "yyyy-MM-dd". + */ + public Deadline(String description, String by) { + super(description); + this.by = new Date(by); + } + + /** + * Constructs a Deadline task with the specified description, deadline date and isDone status. + * + * @param description The description of the task. + * @param by The deadline date in the format "yyyy-MM-dd". + * @param isDone The completion status of the event. + */ + public Deadline(String description, String by, boolean isDone) { + super(description); + this.by = new Date(by); + this.isDone = isDone; + } + + public Date getBy() { + return by; + } + + /** + * Returns true if both deadline tasks have the same data fields. + * This defines a stronger notion of equality between two deadline tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Deadline)) { + return false; + } + + Deadline otherDeadline = (Deadline) other; + return description.equals(otherDeadline.description) + && isDone == otherDeadline.isDone + && by.equals(otherDeadline.by); + } + + @Override + public int hashCode() { + return Objects.hash(description, isDone, by); + } + + /** + * Returns a string representation of the Deadline task, including its deadline date. + * + * @return A string representation of the Deadline task. + */ + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy"); + return "[D]" + super.toString() + " (by: " + this.by.format(formatter) + ")"; + } +} diff --git a/src/main/java/seedu/address/model/task/Description.java b/src/main/java/seedu/address/model/task/Description.java new file mode 100644 index 00000000000..f75eb6e1311 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Description.java @@ -0,0 +1,62 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Task's description in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)}. + */ +public class Description { + + public static final String MESSAGE_CONSTRAINTS = "Descriptions should not be blank"; + + /* + * The description must not be blank. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs a {@code Description}. + * + * @param description A valid description. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + value = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Description)) { + return false; + } + + Description otherDescription = (Description) other; + return value.equals(otherDescription.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/task/Event.java b/src/main/java/seedu/address/model/task/Event.java new file mode 100644 index 00000000000..7b20f2f4b55 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Event.java @@ -0,0 +1,90 @@ +package seedu.address.model.task; + +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * The Event class represents a task that spans over a period of time. + * It extends the Task class and adds LocalDate fields to store the start and end dates of the event. + */ +public class Event extends Task { + private Date from; + private Date to; + + + /** + * Constructs an Event task with the specified description, start date, and end date. + * + * @param description The description of the event. + * @param from The start date of the event in the format "yyyy-MM-dd". + * @param to The end date of the event in the format "yyyy-MM-dd". + */ + public Event(String description, String from, String to) { + super(description); + this.from = new Date(from); + this.to = new Date(to); + } + + /** + * Constructs an Event task with the specified description, start date, and end date. + * + * @param description The description of the event. + * @param from The start date of the event in the format "yyyy-MM-dd". + * @param to The end date of the event in the format "yyyy-MM-dd". + * @param isDone The completion status of the event. + */ + public Event(String description, String from, String to, boolean isDone) { + super(description); + this.from = new Date(from); + this.to = new Date(to); + this.isDone = isDone; + } + + public Date getFrom() { + return from; + } + + public Date getTo() { + return to; + } + + + /** + * Returns true if both event tasks have the same data fields. + * This defines a stronger notion of equality between two event tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Event)) { + return false; + } + + Event otherEvent = (Event) other; + return description.equals(otherEvent.description) + && isDone == otherEvent.isDone + && from.equals(otherEvent.from) + && to.equals(otherEvent.to); + } + + @Override + public int hashCode() { + return Objects.hash(description, isDone, from, to); + } + + /** + * Returns a string representation of the Event task, including its start and end dates. + * + * @return A string representation of the Event task. + */ + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy"); + return "[E]" + super.toString() + " (from: " + this.from.format(formatter) + + " to: " + this.to.format(formatter) + ")"; + } +} diff --git a/src/main/java/seedu/address/model/task/ParsedTask.java b/src/main/java/seedu/address/model/task/ParsedTask.java new file mode 100644 index 00000000000..a1948abd023 --- /dev/null +++ b/src/main/java/seedu/address/model/task/ParsedTask.java @@ -0,0 +1,56 @@ +package seedu.address.model.task; + +import java.util.Objects; + +/** + * Represents a task that has been parsed into its type and details. + */ +public class ParsedTask { + private final String taskType; + private final String taskDetails; + + /** + * Constructs a {@code ParsedTask} with the specified task type and task details. + * + * @param taskType The type of the task (e.g., todo, deadline, event). + * @param taskDetails The details of the task (e.g., description, date). + */ + public ParsedTask(String taskType, String taskDetails) { + this.taskType = taskType; + this.taskDetails = taskDetails; + } + + public String getTaskType() { + return taskType; + } + + public String getTaskDetails() { + return taskDetails; + } + + /** + * Returns true if both parsed tasks have the same task type and details. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof ParsedTask)) { + return false; + } + + ParsedTask otherParsedTask = (ParsedTask) other; + return Objects.equals(taskType, otherParsedTask.taskType) + && Objects.equals(taskDetails, otherParsedTask.taskDetails); + } + + /** + * Returns the hash code for the parsed task based on the task type and details. + */ + @Override + public int hashCode() { + return Objects.hash(taskType, taskDetails); + } +} diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java new file mode 100644 index 00000000000..64476d6adda --- /dev/null +++ b/src/main/java/seedu/address/model/task/Task.java @@ -0,0 +1,145 @@ +package seedu.address.model.task; + +import java.util.Objects; + +/** + * The Task class represents a general task with a description and a completion status. + * It serves as the base class for more specific types of tasks such as Todo, Deadline, and Event. + */ +public class Task { + protected Description description; + protected boolean isDone; + + /** + * Constructs a Task with the specified description. + * The task is initially marked as not done. + * + * @param description The description of the task. + */ + public Task(String description) { + this.description = new Description(description); + this.isDone = false; + } + + /** + * Constructs a Task with the specified Description type description. + * The task is initially marked as not done. + * + * @param description The description of the task. + */ + public Task(Description description) { + this.description = description; + this.isDone = false; + } + + /** + * Returns the status icon of the task. + * "X" if the task is done, " " if the task is not done. + * + * @return The status icon of the task. + */ + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + /** + * Returns the description of the task. + * + * @return The description of the task as a String. + */ + public String getDescription() { + return this.description.toString(); + } + + /** + * Returns the isDone status the task. + * + * @return The completion status of the task as a Boolean. + */ + public Boolean getIsDone() { + return this.isDone; + } + + /** + * Marks the task as done. + */ + public void markAsDone() { + isDone = true; + } + + /** + * Marks the task as not done. + */ + public void markAsUndone() { + isDone = false; + } + + /** + * Returns the presence of the given keyword if it is + * within a partial/full word in the description, case-insensitive. + * "true" if the keyword is present, "false" if the keyword is not. + * + * @param keyword The supplied keyword string. + * @return The boolean indicating presence of the keyword + */ + public boolean hasKeywordInPartialDescription(String keyword) { + String lowerCaseKeyword = keyword.toLowerCase(); + String[] descriptionArray = description.toString().split(" "); + for (String word : descriptionArray) { + String lowerCaseWord = word.toLowerCase(); + if (lowerCaseWord.contains(lowerCaseKeyword)) { + return true; + } + } + return false; + } + + /** + * Returns true if both tasks have the same description. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameTask(Task otherTask) { + if (otherTask == this) { + return true; + } + + return otherTask != null + && otherTask.getDescription().equals(getDescription()); + } + + /** + * Returns true if both tasks have the same data fields. + * This defines a stronger notion of equality between two tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return description.equals(otherTask.description) + && isDone == otherTask.isDone; + } + + /** + * Returns a string representation of the Task. + * + * @return A string representation of the Task, including its status and description. + */ + public String toString() { + return "[" + this.getStatusIcon() + "] " + this.description; + } + + @Override + public int hashCode() { + return Objects.hash(description, isDone); + } +} + + diff --git a/src/main/java/seedu/address/model/task/Todo.java b/src/main/java/seedu/address/model/task/Todo.java new file mode 100644 index 00000000000..62068075431 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Todo.java @@ -0,0 +1,79 @@ +package seedu.address.model.task; + +import java.util.Objects; + +/** + * The Todo class represents a simple task with no specific date attached. + * It extends the Task class and does not add any additional fields. + */ +public class Todo extends Task { + /** + * Constructs a Todo task with the specified description. + * + * @param description The description of the todo task. + */ + public Todo(String description) { + super(description); + } + + public Todo(Description description) { + super(description); + } + + /** + * Constructs a Todo task with the specified description and isDone status. + * + * @param description The description of the todo task. + * @param isDone The completion status of the event. + */ + public Todo(String description, boolean isDone) { + super(description); + this.isDone = isDone; + } + + /** + * Constructs a Todo task with the specified Description type description and isDone status. + * + * @param description The description of the todo task. + * @param isDone The completion status of the event. + */ + public Todo(Description description, boolean isDone) { + super(description); + this.isDone = isDone; + } + + /** + * Returns true if both Todo tasks have the same data fields. + * This defines a stronger notion of equality between two Todo tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Todo)) { + return false; + } + + Todo otherTodo = (Todo) other; + return description.equals(otherTodo.description) + && isDone == otherTodo.isDone; + } + + @Override + public int hashCode() { + return Objects.hash(description, isDone); + } + + /** + * Returns a string representation of the Todo task. + * + * @return A string representation of the Todo task. + */ + @Override + public String toString() { + return "[T]" + super.toString(); + } +} diff --git a/src/main/java/seedu/address/model/task/UniqueTaskList.java b/src/main/java/seedu/address/model/task/UniqueTaskList.java new file mode 100644 index 00000000000..fa485116e85 --- /dev/null +++ b/src/main/java/seedu/address/model/task/UniqueTaskList.java @@ -0,0 +1,150 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; + +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * A task is considered unique by comparing using {@code Task#isSameTask(Task)}. As such, adding and updating of + * tasks uses Task#isSameTask(Task) for equality so as to ensure that the task being added or updated is + * unique in terms of identity in the UniqueTaskList. However, the removal of a task uses Task#equals(Object) so + * as to ensure that the task with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Task#isSameTask(Task) + */ +public class UniqueTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTask); + } + + /** + * Adds a Task to the list. + * The Task must not already exist in the list. + */ + public void add(Task toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the Task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + * The task identity of {@code editedTask} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedTask) && contains(editedTask)) { + throw new DuplicateTaskException(); + } + + internalList.set(index, editedTask); + } + + /** + * Removes the equivalent Task from the list. + * The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + public void setTasks(UniqueTaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + requireAllNonNull(tasks); + if (!tasksAreUnique(tasks)) { + throw new DuplicateTaskException(); + } + + internalList.setAll(tasks); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueTaskList)) { + return false; + } + + UniqueTaskList otherUniqueTaskList = (UniqueTaskList) other; + return internalList.equals(otherUniqueTaskList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code tasks} contains only unique tasks. + */ + private boolean tasksAreUnique(List tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).isSameTask(tasks.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 00000000000..851d40b560b --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,12 @@ +package seedu.address.model.task.exceptions; + + +/** + * Signals that the operation will result in duplicate Tasks (Tasks are considered duplicates if they have the same + * description). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..7c437784b47 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends RuntimeException { + public TaskNotFoundException() { + super("Task does not exist."); + } +} diff --git a/src/main/java/seedu/address/model/task/keywordspredicate/DescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/keywordspredicate/DescriptionContainsKeywordsPredicate.java new file mode 100644 index 00000000000..76ee8b235eb --- /dev/null +++ b/src/main/java/seedu/address/model/task/keywordspredicate/DescriptionContainsKeywordsPredicate.java @@ -0,0 +1,37 @@ +package seedu.address.model.task.keywordspredicate; + +import java.util.List; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.keywordspredicate.TraitContainsKeywordsPredicate; +import seedu.address.model.task.Task; + +/** + * Tests that a {@code Task}'s {@code Description} matches any of the keywords given. + */ +public class DescriptionContainsKeywordsPredicate extends TraitContainsKeywordsPredicate { + + public DescriptionContainsKeywordsPredicate(List keywords) { + super(keywords); + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(task.getDescription(), keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DescriptionContainsKeywordsPredicate otherDescriptionContainsKeywordsPredicate)) { + return false; + } + + return keywords.equals(otherDescriptionContainsKeywordsPredicate.keywords); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index cc7bb58801f..cc6973923fc 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -13,6 +13,10 @@ import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; import seedu.address.model.wedding.Wedding; import seedu.address.model.wedding.WeddingName; @@ -25,27 +29,33 @@ public static Person[] getSamplePersons() { new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), getTagSet("friends"), - getWeddingSet("Casey's Wedding")), + getWeddingSet("Casey's Wedding"), + getTaskSet("todo: Finalize Catering Menu")), new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), getTagSet("colleagues", "friends"), - getWeddingSet()), + getWeddingSet(), + getTaskSet("todo: Set Up Venue Decorations")), new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), getTagSet("neighbours"), - getWeddingSet("Wedding August 2029", "Wedding 2")), + getWeddingSet("Wedding August 2029", "Wedding 2"), + getTaskSet("todo: Send invitations")), new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), getTagSet("family"), - getWeddingSet()), + getWeddingSet(), + getTaskSet("Order wedding cake")), new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), getTagSet("classmates"), - getWeddingSet("Casey's Wedding")), + getWeddingSet("Casey's Wedding"), + getTaskSet("deadline: Schedule Hair and Makeup Trials")), new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), getTagSet("colleagues"), - getWeddingSet("Wedding 10")) + getWeddingSet("Wedding 10"), + getTaskSet("event: Schedule Hair and Makeup Trials")) }; } @@ -68,6 +78,32 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Returns a task set containing the list of tasks given as strings. + * Strings should follow a format like: "todo: Buy cake", "deadline: Submit report by Monday", "event: Meeting" + */ + public static Set getTaskSet(String... strings) { + return Arrays.stream(strings) + .map(SampleDataUtil::parseTaskFromString) + .collect(Collectors.toSet()); + } + + /** + * Parses a string to return a specific Task instance (e.g., Todo, Deadline, or Event). + */ + private static Task parseTaskFromString(String taskString) { + if (taskString.startsWith("todo:")) { + return new Todo(taskString.substring(5).trim()); + } else if (taskString.startsWith("deadline:")) { + return new Deadline(taskString.substring(9).trim(), "2022-12-22"); + } else if (taskString.startsWith("event:")) { + return new Event(taskString.substring(6).trim(), "2022-12-22", "2022-12-23"); + } else { + throw new IllegalArgumentException("Unknown task type in string: " + taskString); + } + } + + /** * Returns a wedding set containing the list of weddings given. diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDeadline.java b/src/main/java/seedu/address/storage/JsonAdaptedDeadline.java new file mode 100644 index 00000000000..1452677298d --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedDeadline.java @@ -0,0 +1,62 @@ +package seedu.address.storage; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Date; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; + +/** + * Jackson-friendly version of {@link Deadline}. + */ +public class JsonAdaptedDeadline extends JsonAdaptedTask { + + private final String by; + + /** + * Constructs a {@code JsonAdaptedDeadline} with the given {@code description} and {@code by}. + */ + @JsonCreator + public JsonAdaptedDeadline(@JsonProperty("description") String description, + @JsonProperty("isDone") Boolean isDone, + @JsonProperty("by") String by) { + super(description, isDone); + this.by = by; + } + + /** + * Converts a given {@code Deadline} into this class for Jackson use. + */ + public JsonAdaptedDeadline(Deadline source) { + super(source.getDescription(), source.getIsDone()); + by = source.getBy().toString(); + } + + /** + * Converts this Jackson-friendly adapted deadline object into the model's {@code Deadline} object. + * + * @throws IllegalValueException if there are any data constraints violated in the adapted deadline. + */ + @Override + public Task toModelType() throws IllegalValueException { + Description modelDescription = toModelDescription(); // Convert string to Description + if (by == null || by.isEmpty()) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Deadline")); + } + + try { + LocalDate.parse(by, Deadline.FORMATTER); + } catch (DateTimeParseException e) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + + return new Deadline(modelDescription.toString(), by, isDone); + } +} + diff --git a/src/main/java/seedu/address/storage/JsonAdaptedEvent.java b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java new file mode 100644 index 00000000000..0e9423e533e --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java @@ -0,0 +1,67 @@ +package seedu.address.storage; + + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Date; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Description; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; + +/** + * Jackson-friendly version of {@link Event}. + */ +public class JsonAdaptedEvent extends JsonAdaptedTask { + private final String from; + private final String to; + + /** + * Constructs a {@code JsonAdaptedEvent} with the given {@code description} and {@code from} and {@code to}. + */ + @JsonCreator + public JsonAdaptedEvent(@JsonProperty("description") String description, + @JsonProperty("isDone") boolean isDone, + @JsonProperty("from") String from, + @JsonProperty("to") String to) { + super(description, isDone); + this.from = from; + this.to = to; + } + + /** + * Converts a given {@code Event} into this class for Jackson use. + */ + public JsonAdaptedEvent(Event source) { + super(source.getDescription(), source.getIsDone()); + from = source.getFrom().toString(); + to = source.getTo().toString(); + } + + /** + * Converts this Jackson-friendly adapted event object into the model's {@code Event} object. + * + * @throws IllegalValueException if there are any data constraints violated in the adapted event. + */ + @Override + public Task toModelType() throws IllegalValueException { + Description modelDescription = toModelDescription(); // Convert string to Description + if (from == null || to == null || from.isEmpty() || to.isEmpty()) { + throw new IllegalValueException(String.format(JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT, "Event dates")); + } + + try { + LocalDate.parse(from, Deadline.FORMATTER); + LocalDate.parse(to, Deadline.FORMATTER); + } catch (DateTimeParseException e) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + return new Event(modelDescription.toString(), from, to, isDone); + } +} + diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index 989b6914e56..db399d67f79 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -16,6 +16,10 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; import seedu.address.model.wedding.Wedding; /** @@ -31,6 +35,7 @@ class JsonAdaptedPerson { private final String address; private final List tags = new ArrayList<>(); private final List weddings = new ArrayList<>(); + private final List tasks = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. @@ -39,7 +44,8 @@ class JsonAdaptedPerson { public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, @JsonProperty("tags") List tags, - @JsonProperty("weddings") List weddings) { + @JsonProperty("weddings") List weddings, + @JsonProperty("tasks") List tasks) { this.name = name; this.phone = phone; this.email = email; @@ -47,9 +53,13 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone if (tags != null) { this.tags.addAll(tags); } + if (tasks != null) { + this.tasks.addAll(tasks); + } if (weddings != null) { this.weddings.addAll(weddings); } + } /** @@ -66,6 +76,24 @@ public JsonAdaptedPerson(Person source) { weddings.addAll(source.getWeddings().stream() .map(JsonAdaptedWedding::new) .collect(Collectors.toList())); + tasks.addAll(source.getTasks().stream() + .map(this::mapToJsonAdaptedTask) + .collect(Collectors.toList())); + } + + /** + * Helper function to map a Task to its corresponding JsonAdaptedTask subclass. + */ + private JsonAdaptedTask mapToJsonAdaptedTask(Task task) { + if (task instanceof Todo) { + return new JsonAdaptedTodo((Todo) task); + } else if (task instanceof Deadline) { + return new JsonAdaptedDeadline((Deadline) task); + } else if (task instanceof Event) { + return new JsonAdaptedEvent((Event) task); + } else { + throw new IllegalArgumentException("Unknown task type"); + } } /** @@ -76,11 +104,16 @@ public JsonAdaptedPerson(Person source) { public Person toModelType() throws IllegalValueException { final List personTags = new ArrayList<>(); final List personWeddings = new ArrayList<>(); + final List personTasks = new ArrayList<>(); for (JsonAdaptedTag tag : tags) { personTags.add(tag.toModelType()); } + for (JsonAdaptedTask task : tasks) { + personTasks.add(task.toModelType()); + } + for (JsonAdaptedWedding wedding : weddings) { if (!Wedding.isValidWeddingName(wedding.getWeddingName())) { throw new IllegalValueException(Wedding.MESSAGE_CONSTRAINTS); @@ -119,8 +152,9 @@ public Person toModelType() throws IllegalValueException { final Set modelTags = new HashSet<>(personTags); final Set modelWeddings = new HashSet<>(personWeddings); + final Set modelTasks = new HashSet<>(personTasks); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags, modelWeddings); + return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags, modelWeddings, modelTasks); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..a327f749378 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,63 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; + +/** + * A Jackson-friendly abstract class for a task. + * This class serves as a base for JSON adaptations of specific task types + * (e.g., Todo, Deadline, Event), which are represented as subclasses. + */ +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, // Use the name of the class to differentiate types + include = JsonTypeInfo.As.PROPERTY, // Include type info as a property in the JSON + property = "type" // The name of the property in the JSON that will hold the type information +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = JsonAdaptedTodo.class, name = "todo"), + @JsonSubTypes.Type(value = JsonAdaptedDeadline.class, name = "deadline"), + @JsonSubTypes.Type(value = JsonAdaptedEvent.class, name = "event") +}) +public abstract class JsonAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + protected final String description; + protected final boolean isDone; + + /** + * Constructs a {@code JsonAdaptedTask} with the given description and completion status. + * + * @param description A string representing the task's description. + * @param isDone A boolean indicating if the task is completed. + */ + @JsonCreator + public JsonAdaptedTask(@JsonProperty("description") String description, + @JsonProperty("isDone") boolean isDone) { + this.description = description; + this.isDone = isDone; + } + + public abstract Task toModelType() throws IllegalValueException; + + /** + * Converts the description string into a {@code Description} object, validating it in the process. + * + * @throws IllegalValueException if the description is invalid. + */ + protected Description toModelDescription() throws IllegalValueException { + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Description")); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(description); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTodo.java b/src/main/java/seedu/address/storage/JsonAdaptedTodo.java new file mode 100644 index 00000000000..35200fd4d2a --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTodo.java @@ -0,0 +1,45 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; + +/** + * Jackson-friendly version of {@link Todo}. + */ +public class JsonAdaptedTodo extends JsonAdaptedTask { + + /** + * Constructs a {@code JsonAdaptedTodo} with the given {@code description}. + */ + @JsonCreator + public JsonAdaptedTodo(@JsonProperty("description") String description, + @JsonProperty("isDone") boolean isDone) { + super(description, isDone); + } + + /** + * Converts a given {@code Todo} into this class for Jackson use. + */ + public JsonAdaptedTodo(Todo source) { + super(source.getDescription(), source.getIsDone()); + } + + + + /** + * Converts this Jackson-friendly adapted todo object into the model's {@code Todo} object. + * + * @throws IllegalValueException if there are any data constraints violated in the adapted todo. + */ + @Override + public Task toModelType() throws IllegalValueException { + Description modelDescription = toModelDescription(); + return new Todo(modelDescription.toString(), isDone); + } +} + diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 4007dea370d..9feb8952c29 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; @@ -13,6 +14,10 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; import seedu.address.model.wedding.Wedding; /** @@ -24,22 +29,26 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; public static final String MESSAGE_DUPLICATE_TAG = "Tags list contains duplicate tag(s)."; public static final String MESSAGE_DUPLICATE_WEDDING = "Weddings list contains duplicate wedding(s)."; + public static final String MESSAGE_DUPLICATE_TASK = "Tasks list contains duplicate task(s)."; private final List persons = new ArrayList<>(); private final List tags = new ArrayList<>(); private final List weddings = new ArrayList<>(); + private final List tasks = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons, tags, and weddings. + * Constructs a {@code JsonSerializableAddressBook} with the given persons, tags, weddings and tasks. */ @JsonCreator public JsonSerializableAddressBook( @JsonProperty("persons") List persons, @JsonProperty("tags") List tags, - @JsonProperty("weddings") List weddings) { + @JsonProperty("weddings") List weddings, + @JsonProperty("tasks") List tasks) { this.persons.addAll(persons); this.tags.addAll(tags); this.weddings.addAll(weddings); + this.tasks.addAll(tasks); } /** @@ -51,6 +60,7 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); tags.addAll(source.getTagList().stream().map(JsonAdaptedTag::new).collect(Collectors.toList())); weddings.addAll(source.getWeddingList().stream().map(JsonAdaptedWedding::new).collect(Collectors.toList())); + tasks.addAll(source.getTaskList().stream().map(this::mapToJsonAdaptedTask).collect(Collectors.toList()));; } /** @@ -74,6 +84,13 @@ public AddressBook toModelType() throws IllegalValueException { } addressBook.addTag(tag); } + for (JsonAdaptedTask jsonAdaptedTask : tasks) { + Task task = jsonAdaptedTask.toModelType(); + if (addressBook.hasTask(task)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_TASK); + } + addressBook.addTask(task); + } for (JsonAdaptedWedding jsonAdaptedWedding : weddings) { Wedding wedding = jsonAdaptedWedding.toModelType(); if (addressBook.hasWedding(wedding)) { @@ -81,6 +98,59 @@ public AddressBook toModelType() throws IllegalValueException { } addressBook.addWedding(wedding); } + // load tags and weddings from people after loading weddings and tags, because if tag or wedding already exist, + // method will throw an error + for (JsonAdaptedPerson jsonAdaptedPerson : persons) { + Person person = jsonAdaptedPerson.toModelType(); + loadTags(addressBook, person); + loadWeddings(addressBook, person); + loadTasks(addressBook, person); + } return addressBook; } + + private void loadTags(AddressBook addressBook, Person person) { + Set tagList = person.getTags(); + for (Tag tag : tagList) { + if (addressBook.hasTag(tag) || !Tag.isValidTagName(tag.getTagName().toString())) { + continue; + } + addressBook.addTag(tag); + } + } + + private void loadWeddings(AddressBook addressBook, Person person) { + Set weddingList = person.getWeddings(); + for (Wedding wedding : weddingList) { + if (addressBook.hasWedding(wedding) || !Wedding.isValidWeddingName(wedding.getWeddingName().toString())) { + continue; + } + addressBook.addWedding(wedding); + } + } + + private void loadTasks(AddressBook addressBook, Person person) { + Set taskList = person.getTasks(); + for (Task task : taskList) { + if (addressBook.hasTask(task)) { + continue; + } + addressBook.addTask(task); + } + } + + /** + * Maps a given Task to its corresponding JsonAdaptedTask subclass. + */ + private JsonAdaptedTask mapToJsonAdaptedTask(Task task) { + if (task instanceof Todo) { + return new JsonAdaptedTodo((Todo) task); + } else if (task instanceof Deadline) { + return new JsonAdaptedDeadline((Deadline) task); + } else if (task instanceof Event) { + return new JsonAdaptedEvent((Event) task); + } else { + throw new IllegalArgumentException("Unknown task type"); + } + } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index cd8516cb4f0..bdc4ebcf1c7 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,6 +34,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; private WeddingListPanel weddingListPanel; + private TaskListPanel taskListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -115,6 +116,8 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); listPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + taskListPanel = new TaskListPanel(logic.getFilteredTaskList()); + weddingListPanel = new WeddingListPanel(logic.getFilteredWeddingList()); resultDisplay = new ResultDisplay(); @@ -211,6 +214,9 @@ public void switchView(SwitchView switchView) { case PERSON: changeToPersonView(); break; + case TASK: + changeToTaskView(); + break; case WEDDING: changeToWeddingView(); break; @@ -228,6 +234,15 @@ public void changeToWeddingView() { listPanelPlaceholder.getChildren().add(weddingListPanel.getRoot()); } + /** + * Changes the list panel to show the {@code Task} list. + */ + public void changeToTaskView() { + taskListPanel.updatePersonList(logic.getFilteredTaskList()); + listPanelPlaceholder.getChildren().clear(); + listPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + } + /** * Changes the list panel to show the {@code Person} list. */ diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 00000000000..bdffb08e12f --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,64 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; + +/** + * A UI component that displays information of a {@code Task}. + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label description; + @FXML + private Label id; + @FXML + private Label date; + @FXML + private Label isDone; + /** + * Creates a {@code TaskCard} with the given {@code Task} and index to display. + */ + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + description.setText(task.getDescription()); + + if (task instanceof Deadline) { + date.setText("By: " + ((Deadline) task).getBy().toString()); + } else if (task instanceof Event) { + date.setText(("From: " + ((Event) task).getFrom() + " to " + ((Event) task).getTo())); + } + + isDone.setText(task.getIsDone() ? "Completed" : "Incomplete"); + } + + public Label getDescriptionLabel() { + return description; + } + + public Label getIdLabel() { + return id; + } + + public Label getDateLabel() { + return date; + } + + public Label getIsDoneLabel() { + return isDone; + } +} + diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java new file mode 100644 index 00000000000..5dcb00021ad --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListPanel.java @@ -0,0 +1,55 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.task.Task; + +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView taskListView; + + /** + * Creates a {@code TaskListPanel} with the given {@code ObservableList}. + */ + public TaskListPanel(ObservableList taskList) { + super(FXML); + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + /** + * Updates the {@code TaskListView} with an updated list of tasks. + */ + public void updatePersonList(ObservableList taskList) { + taskListView.setItems(taskList); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Task} using a {@code TaskCard}. + */ + class TaskListViewCell extends ListCell { + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskCard(task, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 00000000000..b7808532b03 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 00000000000..9753448ffdb --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json index b95a76c2fb8..a7ca0ae1bb6 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json @@ -11,5 +11,6 @@ "address": "4th street" } ], "tags": [], - "weddings": [] + "weddings": [], + "tasks": [] } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json index 6281db4c10c..b5634c6b6d9 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json @@ -6,5 +6,6 @@ "address": "4th street" } ], "tags": [], - "weddings": [] + "weddings": [], + "tasks": [] } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidTagAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidTagAddressBook.json index bec5f5644c7..6bd597519d4 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidTagAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidTagAddressBook.json @@ -6,5 +6,6 @@ "address": "4th street" }], "tags": ["Valid Tag", " "], - "weddings": ["Valid Wedding"] + "weddings": ["Valid Wedding"], + "tasks": [] } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidTaskAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidTaskAddressBook.json new file mode 100644 index 00000000000..1dc8fced0d4 --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidTaskAddressBook.json @@ -0,0 +1,11 @@ +{ + "persons": [ { + "name": "Valid Person", + "phone": "9482424", + "email": "hans@example.com", + "address": "4th street" + }], + "tags": ["Valid Tag"], + "weddings": ["valid wedding"], + "tasks": [" "] +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidWeddingAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidWeddingAddressBook.json index 630f41d70d6..3c028bf4b86 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidWeddingAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidWeddingAddressBook.json @@ -6,5 +6,6 @@ "address": "4th street" }], "tags": ["Valid Tag"], - "weddings": [" "] + "weddings": [" "], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json index 6b1dbae9aae..6783df0832a 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json @@ -12,5 +12,6 @@ "address": "4th street" } ], "tags": ["florist"], - "weddings": [] + "weddings": [], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateTagAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateTagAddressBook.json index f895db65112..d8957c8dc01 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicateTagAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateTagAddressBook.json @@ -15,5 +15,6 @@ "guestList": [], "address": null, "date": null - }] + }], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateWeddingAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateWeddingAddressBook.json index 4e8df586ab3..c397d788746 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicateWeddingAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateWeddingAddressBook.json @@ -26,5 +26,6 @@ "address": null, "date": null } - ] + ], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json index 3278e110d29..752bd7986d6 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json @@ -6,5 +6,6 @@ "address": "4th street" } ], "tags": ["florist"], - "weddings": [] + "weddings": [], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidTagAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidTagAddressBook.json index e9ebc751cc8..b4ba8da2cf1 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidTagAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidTagAddressBook.json @@ -16,5 +16,6 @@ "address": null, "date": null } - ] + ], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidWeddingAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidWeddingAddressBook.json index 7269d9d5639..2a3e98102ac 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidWeddingAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidWeddingAddressBook.json @@ -15,5 +15,6 @@ "guestList" : [ ], "address" : null, "date" : null - }] + }], + "tasks": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json index 3db5529b021..f2ecbaa32fc 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json @@ -34,7 +34,12 @@ "phone" : "95352563", "email" : "heinz@example.com", "address" : "wall street", - "tags" : [ ] + "tags" : [ ], + "tasks": [{ + "type" : "todo", + "description" : "Buy cake", + "isDone" : false + }] }, { "name" : "Daniel Meier", "phone" : "87652533", @@ -87,5 +92,15 @@ "guestList": [], "address": null, "date": null - }] + }], + "tasks": [{ + "type" : "todo", + "description" : "Buy groceries", + "isDone" : false + }, { + "type" : "deadline", + "description" : "Submit report", + "isDone" : false, + "by" : "2024-12-31" + } ] } diff --git a/src/test/java/seedu/address/logic/MessagesTest.java b/src/test/java/seedu/address/logic/MessagesTest.java index 6124dda957e..feb6360b1f9 100644 --- a/src/test/java/seedu/address/logic/MessagesTest.java +++ b/src/test/java/seedu/address/logic/MessagesTest.java @@ -11,7 +11,9 @@ import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Task; import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.TypicalTasks; public class MessagesTest { @@ -62,4 +64,13 @@ public void format_tag_success() { String actualOutput = Messages.format(tag); assertEquals(expectedOutput, actualOutput); } + + @Test + public void format_task_success() { + Task task = TypicalTasks.TODO_TASK; + String expectedOutput = task.getDescription(); + + String actualOutput = Messages.format(task); + assertEquals(expectedOutput, actualOutput); + } } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index ddc4c39e880..994d12ae7e9 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -24,6 +24,7 @@ import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; import seedu.address.testutil.PersonBuilder; @@ -185,6 +186,36 @@ public ObservableList getFilteredTagList() { throw new AssertionError("This method should not be called."); } + @Override + public boolean hasTask(Task toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTask(Task toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTask(Task toDelete) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTask(Task target, Task editedTask) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTaskList() { + throw new AssertionError("This method should not be called."); + } + @Override public boolean hasWedding(Wedding toAdd) { throw new AssertionError("This method should not be called."); diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java index c0d589332de..2810f16e530 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java @@ -44,9 +44,14 @@ public void equals() { assertFalse(commandResult.equals(new CommandResult("feedback", false, true, SwitchView.NONE))); + // different switchView value -> returns false + assertFalse(commandResult.equals(new CommandResult("feedback", false, false, + SwitchView.TASK))); + // different switchView value -> returns false assertFalse(commandResult.equals(new CommandResult("feedback", false, false, SwitchView.WEDDING))); + assertFalse(commandResult.equals(new CommandResult("feedback", false, false, SwitchView.PERSON))); diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 1fefab814fc..cb87d322123 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -11,6 +11,7 @@ import static seedu.address.testutil.Assert.assertThrows; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -24,6 +25,8 @@ import seedu.address.model.person.keywordspredicate.WeddingContainsKeywordsPredicate; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Task; +import seedu.address.model.task.keywordspredicate.DescriptionContainsKeywordsPredicate; import seedu.address.model.wedding.Wedding; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.EditWeddingDescriptorBuilder; @@ -61,6 +64,8 @@ public class CommandTestUtil { public static final String VALID_WEDDING_AMY = VALID_NAME_AMY.split(" ")[0] + "'s Wedding"; public static final String VALID_WEDDING_BOB = VALID_NAME_BOB.split(" ")[0] + "'s Wedding"; public static final String VALID_WEDDING_CLIVE = VALID_NAME_CLIVE.split(" ")[0] + "'s Wedding"; + public static final String VALID_TASK_TODO = "todo: Different Task"; + public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -192,4 +197,18 @@ public static void showWeddingAtIndex(Model model, Index targetIndex) { assertEquals(1, model.getFilteredWeddingList().size()); } + + /** + * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showTaskAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredTaskList().size()); + + Task task = model.getFilteredTaskList().get(targetIndex.getZeroBased()); + final String[] splitDescription = task.getDescription().split("\\s+"); + model.updateFilteredTaskList(new DescriptionContainsKeywordsPredicate(Arrays.asList(splitDescription[0]))); + + assertEquals(1, model.getFilteredTaskList().size()); + } } diff --git a/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java b/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java index f9bcc6e26e7..d2e9ca0c6aa 100644 --- a/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/CreateTagCommandTest.java @@ -25,6 +25,7 @@ import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; public class CreateTagCommandTest { @@ -173,6 +174,36 @@ public void addTag(Tag toAdd) { throw new AssertionError("This method should not be called."); } + @Override + public boolean hasTask(Task toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTask(Task toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTask(Task toDelete) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTask(Task target, Task editedTask) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTaskList() { + throw new AssertionError("This method should not be called."); + } + @Override public boolean hasWedding(Wedding toAdd) { throw new AssertionError("This method should not be called."); diff --git a/src/test/java/seedu/address/logic/commands/CreateTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/CreateTaskCommandTest.java new file mode 100644 index 00000000000..3b42bc230f4 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CreateTaskCommandTest.java @@ -0,0 +1,112 @@ +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.assertThrows; +import static seedu.address.testutil.TypicalTasks.DEADLINE_TASK; +import static seedu.address.testutil.TypicalTasks.EVENT_TASK; +import static seedu.address.testutil.TypicalTasks.TODO_TASK; + +import java.util.HashSet; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.task.Task; + +/** + * Contains integration tests (interaction with the Model) for {@code CreateTaskCommand}. + */ +public class CreateTaskCommandTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(); + } + + @Test + public void execute_addValidTask_success() throws Exception { + // Prepare a set of tasks to add + HashSet tasksToAdd = new HashSet<>(); + tasksToAdd.add(TODO_TASK); + tasksToAdd.add(DEADLINE_TASK); + + // Create a CreateTaskCommand with valid tasks + CreateTaskCommand command = new CreateTaskCommand(tasksToAdd); + + String expectedMessage = String.format(CreateTaskCommand.MESSAGE_SUCCESS, tasksToAdd); + + CommandResult result = command.execute(model); + + assertEquals(expectedMessage, result.getFeedbackToUser()); + } + + @Test + public void execute_duplicateTask_throwsCommandException() { + // Add a task to the model to simulate a duplicate + model.addTask(TODO_TASK); + + // Prepare a set of tasks with a duplicate task + HashSet tasksToAdd = new HashSet<>(); + tasksToAdd.add(TODO_TASK); + + CreateTaskCommand command = new CreateTaskCommand(tasksToAdd); + + assertThrows(CommandException.class, () -> command.execute(model), CreateTaskCommand.MESSAGE_DUPLICATE_TASK); + } + @Test + public void getTaskToAdd_validTaskSet_returnsCorrectSet() { + // Prepare a set of tasks to add + HashSet tasksToAdd = new HashSet<>(); + tasksToAdd.add(TODO_TASK); + + CreateTaskCommand command = new CreateTaskCommand(tasksToAdd); + + assertEquals(tasksToAdd, command.getTaskToAdd()); + } + @Test + public void toString_validTaskSet_returnsCorrectString() { + // Prepare a set of tasks to add + HashSet tasksToAdd = new HashSet<>(); + tasksToAdd.add(TODO_TASK); + + CreateTaskCommand command = new CreateTaskCommand(tasksToAdd); + + String expectedString = "seedu.address.logic.commands.CreateTaskCommand{taskToAdd=[[T][ ] Buy groceries]}"; + assertEquals(expectedString, command.toString()); + } + + @Test + public void equals() { + HashSet taskSet1 = new HashSet<>(); + taskSet1.add(TODO_TASK); + + HashSet taskSet2 = new HashSet<>(); + taskSet2.add(EVENT_TASK); + + CreateTaskCommand addFirstTaskCommand = new CreateTaskCommand(taskSet1); + CreateTaskCommand addSecondTaskCommand = new CreateTaskCommand(taskSet2); + + // same object -> returns true + assertEquals(addFirstTaskCommand, addFirstTaskCommand); + + // same values -> returns true + CreateTaskCommand addFirstTaskCommandCopy = new CreateTaskCommand(taskSet1); + assertEquals(addFirstTaskCommand, addFirstTaskCommandCopy); + + // different types -> returns false + assertFalse(addFirstTaskCommand.equals(1)); + + // null -> returns false + assertFalse(addFirstTaskCommand.equals(null)); + + // different task sets -> returns false + assertFalse(addFirstTaskCommand.equals(addSecondTaskCommand)); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java new file mode 100644 index 00000000000..802f5e8a01c --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java @@ -0,0 +1,86 @@ +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.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.task.Task; +import seedu.address.testutil.TypicalTasks; + +public class DeleteTaskCommandTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(); + List taskList = TypicalTasks.getTypicalTasks(); + for (Task task : taskList) { + model.addTask(task); + } + } + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(INDEX_FIRST); + + CommandResult commandResult = deleteTaskCommand.execute(model); + + String expectedMessage = String.format(DeleteTaskCommand.MESSAGE_DELETE_TASK_SUCCESS, taskToDelete); + assertEquals(expectedMessage, commandResult.getFeedbackToUser()); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(outOfBoundIndex); + + assertThrows(CommandException.class, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX, () + -> deleteTaskCommand.execute(model)); + } + + @Test + public void toString_validIndex_returnsCorrectString() { + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(INDEX_FIRST); + System.out.println(deleteTaskCommand.toString()); + String expectedString = "seedu.address.logic.commands." + + "DeleteTaskCommand{targetIndex=seedu.address.commons.core.index.Index{zeroBasedIndex=0}}"; + assertEquals(expectedString, deleteTaskCommand.toString()); + } + + @Test + public void equals() { + DeleteTaskCommand deleteFirstCommand = new DeleteTaskCommand(INDEX_FIRST); + DeleteTaskCommand deleteSecondCommand = new DeleteTaskCommand(INDEX_SECOND); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteTaskCommand deleteFirstCommandCopy = new DeleteTaskCommand(INDEX_FIRST); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different task -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ListTasksCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTasksCommandTest.java new file mode 100644 index 00000000000..59bc783c51e --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListTasksCommandTest.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListTaskCommand. + */ +public class ListTasksCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + CommandResult actualCommandResult = new ListTasksCommand().execute(model); + assertCommandSuccess(new ListTasksCommand(), model, actualCommandResult, expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showTaskAtIndex(model, INDEX_FIRST); // Filters the task list to show only the first task + CommandResult actualCommandResult = new ListTasksCommand().execute(model); + assertCommandSuccess(new ListTasksCommand(), model, actualCommandResult, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/TagCommandTest.java b/src/test/java/seedu/address/logic/commands/TagCommandTest.java index afc030828d3..bbe1c666265 100644 --- a/src/test/java/seedu/address/logic/commands/TagCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/TagCommandTest.java @@ -49,7 +49,8 @@ public void execute_validTagsUnfilteredList_success() { personToEdit.getEmail(), personToEdit.getAddress(), updatedTags, - personToEdit.getWeddings()); + personToEdit.getWeddings(), + personToEdit.getTasks()); expectedModel.setPerson(personToEdit, editedPerson); CommandTestUtil.assertCommandSuccess(tagCommand, model, expectedMessage, expectedModel); @@ -64,6 +65,7 @@ public void execute_validMultipleTagsUnfilteredList_success() { new Email("test@example.com"), new Address("123, Test Street"), new HashSet<>(List.of(new Tag(new TagName("family")))), + new HashSet<>(), new HashSet<>() ); model.addTag(new Tag(new TagName("family"))); @@ -86,7 +88,8 @@ public void execute_validMultipleTagsUnfilteredList_success() { personWithTags.getEmail(), personWithTags.getAddress(), updatedTags, - personWithTags.getWeddings()); + personWithTags.getWeddings(), + personWithTags.getTasks()); expectedModel.setPerson(personWithTags, editedPerson); CommandTestUtil.assertCommandSuccess(tagCommand, model, expectedMessage, expectedModel); diff --git a/src/test/java/seedu/address/logic/commands/UntagCommandTest.java b/src/test/java/seedu/address/logic/commands/UntagCommandTest.java index 6f8781fc500..2c154ee9252 100644 --- a/src/test/java/seedu/address/logic/commands/UntagCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/UntagCommandTest.java @@ -49,7 +49,8 @@ public void execute_validTagsUnfilteredList_success() { personToEdit.getEmail(), personToEdit.getAddress(), updatedTags, - personToEdit.getWeddings()); + personToEdit.getWeddings(), + personToEdit.getTasks()); expectedModel.setPerson(personToEdit, editedPerson); CommandTestUtil.assertCommandSuccess(untagCommand, model, expectedMessage, expectedModel); @@ -64,6 +65,7 @@ public void execute_validMultipleTagsUnfilteredList_success() { new seedu.address.model.person.Email("test@example.com"), new seedu.address.model.person.Address("123, Test Street"), new HashSet<>(Arrays.asList(new Tag(new TagName("friends")), new Tag(new TagName("owesMoney")))), + new HashSet<>(), new HashSet<>() ); model.setPerson(model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()), personWithTags); @@ -83,7 +85,8 @@ public void execute_validMultipleTagsUnfilteredList_success() { personWithTags.getEmail(), personWithTags.getAddress(), updatedTags, - personWithTags.getWeddings()); + personWithTags.getWeddings(), + personWithTags.getTasks()); expectedModel.setPerson(personWithTags, editedPerson); CommandTestUtil.assertCommandSuccess(untagCommand, model, expectedMessage, expectedModel); @@ -135,7 +138,8 @@ public void execute_personWithoutTags_failure() { new seedu.address.model.person.Address("123, Test Street"), new HashSet<>(), // No tags new HashSet<>(Arrays.asList(new Wedding(new WeddingName("Jiazhen's Wedding")), - new Wedding(new WeddingName("Wedding 29th August")))) + new Wedding(new WeddingName("Wedding 29th August")))), + new HashSet<>() ); model.setPerson(model.getFilteredPersonList().get(INDEX_SECOND.getZeroBased()), personWithoutTags); diff --git a/src/test/java/seedu/address/logic/commands/wedding/AssignWeddingCommandTest.java b/src/test/java/seedu/address/logic/commands/wedding/AssignWeddingCommandTest.java index 4296330dda0..1e9d6fa5086 100644 --- a/src/test/java/seedu/address/logic/commands/wedding/AssignWeddingCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/wedding/AssignWeddingCommandTest.java @@ -46,7 +46,8 @@ public void assignWedding_success() { personToEdit.getEmail(), personToEdit.getAddress(), personToEdit.getTags(), - updatedWeddings); + updatedWeddings, + personToEdit.getTasks()); expectedModel.setPerson(personToEdit, editedPerson); CommandTestUtil.assertCommandSuccess(assignWeddingCommand, model, expectedMessage, expectedModel); diff --git a/src/test/java/seedu/address/logic/commands/wedding/CreateWeddingCommandTest.java b/src/test/java/seedu/address/logic/commands/wedding/CreateWeddingCommandTest.java index 75a67216c33..96694c8a69b 100644 --- a/src/test/java/seedu/address/logic/commands/wedding/CreateWeddingCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/wedding/CreateWeddingCommandTest.java @@ -25,6 +25,7 @@ import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; public class CreateWeddingCommandTest { @@ -178,6 +179,36 @@ public void updateFilteredTagList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + @Override + public boolean hasTask(Task toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addTask(Task toAdd) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTask(Task toDelete) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTask(Task target, Task editedTask) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTaskList() { + throw new AssertionError("This method should not be called."); + } + @Override public boolean hasWedding(Wedding toAdd) { throw new AssertionError("This method should not be called."); diff --git a/src/test/java/seedu/address/logic/commands/wedding/UnassignWeddingCommandTest.java b/src/test/java/seedu/address/logic/commands/wedding/UnassignWeddingCommandTest.java index 2a56a2dbcef..aaa12c1d36f 100644 --- a/src/test/java/seedu/address/logic/commands/wedding/UnassignWeddingCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/wedding/UnassignWeddingCommandTest.java @@ -46,7 +46,8 @@ public void unassignWedding_success() { personToEdit.getEmail(), personToEdit.getAddress(), personToEdit.getTags(), - updatedWeddings); + updatedWeddings, + personToEdit.getTasks()); expectedModel.setPerson(personToEdit, editedPerson); CommandTestUtil.assertCommandSuccess(unassignWeddingCommand, model, expectedMessage, expectedModel); diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index b123fcfaf38..a02800d46f4 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -17,13 +17,16 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.CreateTagCommand; +import seedu.address.logic.commands.CreateTaskCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.DeleteTagCommand; +import seedu.address.logic.commands.DeleteTaskCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListTasksCommand; import seedu.address.logic.commands.TagCommand; import seedu.address.logic.commands.UntagCommand; import seedu.address.logic.commands.findcommand.FindCommand; @@ -38,11 +41,13 @@ import seedu.address.model.person.keywordspredicate.NameContainsKeywordsPredicate; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; import seedu.address.model.wedding.WeddingName; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; +import seedu.address.testutil.TypicalTasks; public class AddressBookParserTest { @@ -195,6 +200,33 @@ public void parseCommand_listWeddings() throws Exception { assertTrue(parser.parseCommand(ListWeddingsCommand.COMMAND_WORD + " 3") instanceof ListWeddingsCommand); } + @Test + public void parseCommand_listTask() throws Exception { + assertTrue(parser.parseCommand(ListTasksCommand.COMMAND_WORD) instanceof ListTasksCommand); + assertTrue(parser.parseCommand(ListTasksCommand.COMMAND_WORD + " 3") instanceof ListTasksCommand); + } + + @Test + public void parseCommand_createTask() throws Exception { + String userInput = CreateTaskCommand.COMMAND_WORD + + " tk/todo Buy groceries tk/deadline Submit report /by 2024-12-31" + + " tk/event Project meeting /from 2024-10-10 /to 2024-10-11"; + HashSet tasksToAdd = new HashSet<>(TypicalTasks.getTypicalTasks()); + + CreateTaskCommand expectedCommand = new CreateTaskCommand(tasksToAdd); + CreateTaskCommand command = (CreateTaskCommand) parser.parseCommand(userInput); + + assertEquals(expectedCommand, command); + } + + @Test + public void parseCommand_deleteTask() throws Exception { + DeleteTaskCommand command = (DeleteTaskCommand) parser.parseCommand( + DeleteTaskCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); + assertEquals(new DeleteTaskCommand(INDEX_FIRST), command); + } + + @Test public void parseCommand_unrecognisedInput_throwsParseException() { assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () diff --git a/src/test/java/seedu/address/logic/parser/CreateTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CreateTaskCommandParserTest.java new file mode 100644 index 00000000000..349e191e687 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/CreateTaskCommandParserTest.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalTasks.INVALID_TASK_DESC; +import static seedu.address.testutil.TypicalTasks.TASK_DESC_DEADLINE; +import static seedu.address.testutil.TypicalTasks.TASK_DESC_EVENT; +import static seedu.address.testutil.TypicalTasks.TASK_DESC_TODO; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.CreateTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.Task; +import seedu.address.testutil.TypicalTasks; + +public class CreateTaskCommandParserTest { + + private final CreateTaskCommandParser parser = new CreateTaskCommandParser(); + + @Test + public void parse_allFieldsPresent_success() throws Exception { + // CreateTaskCommand with all valid task descriptions + Set expectedTasks = Set.copyOf(TypicalTasks.getTypicalTasks()); + + CreateTaskCommand expectedCommand = new CreateTaskCommand(new HashSet<>(expectedTasks)); + + assertEquals(expectedCommand, parser.parse(TASK_DESC_TODO + TASK_DESC_DEADLINE + TASK_DESC_EVENT)); + } + + @Test + public void parse_incompleteDescription_failure() { + // Invalid task with incomplete description + assertThrows(ParseException.class, Messages.MESSAGE_INCOMPLETE_TASK_DESCRIPTION, () + -> parser.parse(INVALID_TASK_DESC)); + } + + @Test + public void parse_invalidValue_failure() { + // Invalid command format (empty argument) + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateTaskCommand.MESSAGE_USAGE), () + -> parser.parse("")); + + // Missing task descriptions (no task prefix) + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateTaskCommand.MESSAGE_USAGE), () + -> parser.parse("This is a random string")); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java new file mode 100644 index 00000000000..5826971b016 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteTaskCommandParserTest.java @@ -0,0 +1,49 @@ +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.TypicalIndexes.INDEX_FIRST; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.DeleteTaskCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteTaskCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteTaskCommand, and therefore we test only one of them. + */ +public class DeleteTaskCommandParserTest { + + private DeleteTaskCommandParser parser = new DeleteTaskCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteTaskCommand() { + assertParseSuccess(parser, "1", new DeleteTaskCommand(INDEX_FIRST)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_extraArgs_throwsParseException() { + assertParseFailure(parser, "1 abc", + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_negativeIndex_throwsParseException() { + assertParseFailure(parser, "-1", + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_zeroIndex_throwsParseException() { + assertParseFailure(parser, "0", + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 2b618430c88..ac44883f9e5 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -20,6 +20,10 @@ import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; public class ParserUtilTest { private static final String INVALID_NAME = "R@chel"; @@ -37,6 +41,28 @@ public class ParserUtilTest { private static final String VALID_TAG_1_NAME = "florist"; private static final String VALID_TAG_2_NAME = "photographer"; + private static final String INVALID_TASK_TYPE = "unknownTask"; + private static final String INVALID_TODO_DESCRIPTION = "todo"; // No description + private static final String INVALID_DEADLINE_FORMAT = "deadline Submit assignment /by"; + private static final String INVALID_EVENT_FORMAT = "event Conference /from 2024-10-01"; + + private static final String VALID_TODO_DESCRIPTION = "todo Buy groceries"; + private static final String VALID_DEADLINE_DESCRIPTION = "deadline Submit assignment /by 2024-12-31"; + private static final String VALID_EVENT_DESCRIPTION = "event Conference /from 2024-10-01 /to 2024-10-05"; + + private static final String VALID_EVENT_START_DATE = "2024-10-01"; + private static final String VALID_EVENT_END_DATE = "2024-10-05"; + private static final String VALID_DEADLINE_DATE = "2024-12-31"; + + private static final String INVALID_DEADLINE_DATE_MONTH = "deadline Submit assignment /by 2024-13-31"; + private static final String INVALID_DEADLINE_DATE_DAY = "deadline Submit assignment /by 2024-12-32"; + private static final String INVALID_DEADLINE_DATE_STRING = "deadline Submit assignment /by not-a-date"; + + private static final String INVALID_EVENT_DATE_MONTH = "event Conference /from 2024-13-01 /to 2024-12-31"; + private static final String INVALID_EVENT_DATE_DAY = "event Conference /from 2024-12-01 /to 2024-12-32"; + private static final String INVALID_EVENT_DATE_STRING = "event Conference /from 2024-12-01 /to not-a-date"; + private static final String INVALID_EVENT_DATE_ORDER = "event Conference /from 2024-12-31 /to 2024-12-01"; + private static final String WHITESPACE = " \t\r\n"; @Test @@ -198,4 +224,101 @@ public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { assertEquals(expectedTagSet, actualTagSet); } + /* + *======================================================================================= + */ + @Test + public void parseTask_invalidTaskType_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_TASK_TYPE)); + } + + @Test + public void parseTask_invalidTodoDescription_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_TODO_DESCRIPTION)); + } + + @Test + public void parseTask_invalidDeadlineFormat_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_DEADLINE_FORMAT)); + } + + @Test + public void parseTask_invalidEventFormat_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_EVENT_FORMAT)); + } + + @Test + public void parseTask_validTodoDescription_returnsTodo() throws Exception { + Todo expectedTodo = new Todo("Buy groceries"); + Task actualTask = ParserUtil.parseTask(VALID_TODO_DESCRIPTION); + assertEquals(expectedTodo, actualTask); + } + + @Test + public void parseTask_validDeadlineDescription_returnsDeadline() throws Exception { + Deadline expectedDeadline = new Deadline("Submit assignment", VALID_DEADLINE_DATE); + Task actualTask = ParserUtil.parseTask(VALID_DEADLINE_DESCRIPTION); + assertEquals(expectedDeadline, actualTask); + } + + @Test + public void parseTask_validEventDescription_returnsEvent() throws Exception { + Event expectedEvent = new Event("Conference", VALID_EVENT_START_DATE, VALID_EVENT_END_DATE); + Task actualTask = ParserUtil.parseTask(VALID_EVENT_DESCRIPTION); + assertEquals(expectedEvent, actualTask); + } + + @Test + public void parseTasks_collectionWithValidTasks_returnsTaskSet() throws Exception { + Set actualTaskSet = ParserUtil.parseTasks( + Arrays.asList(VALID_TODO_DESCRIPTION, VALID_DEADLINE_DESCRIPTION, VALID_EVENT_DESCRIPTION)); + + Set expectedTaskSet = new HashSet<>( + Arrays.asList( + new Todo("Buy groceries"), + new Deadline("Submit assignment", VALID_DEADLINE_DATE), + new Event("Conference", VALID_EVENT_START_DATE, VALID_EVENT_END_DATE) + ) + ); + assertEquals(expectedTaskSet, actualTaskSet); + } + + @Test + public void parseTasks_emptyCollection_returnsEmptySet() throws Exception { + Set taskSet = ParserUtil.parseTasks(Arrays.asList()); + assertEquals(new HashSet<>(), taskSet); + } + + @Test + public void parseTasks_collectionWithInvalidTask_throwsParseException() { + assertThrows(ParseException.class, () -> + ParserUtil.parseTasks(Arrays.asList(VALID_TODO_DESCRIPTION, INVALID_DEADLINE_FORMAT))); + } + + @Test + public void parseTask_invalidDeadlineDate_throwsParseException() { + // Invalid month + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_DEADLINE_DATE_MONTH)); + + // Invalid day + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_DEADLINE_DATE_DAY)); + + // Invalid date string + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_DEADLINE_DATE_STRING)); + } + + @Test + public void parseTask_invalidEventDates_throwsParseException() { + // Invalid month + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_EVENT_DATE_MONTH)); + + // Invalid day + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_EVENT_DATE_DAY)); + + // Invalid date string + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_EVENT_DATE_STRING)); + + // "from" date is after "to" date + assertThrows(ParseException.class, () -> ParserUtil.parseTask(INVALID_EVENT_DATE_ORDER)); + } } diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index ac39fe90f7d..94d3ef0caeb 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -9,6 +9,7 @@ import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import static seedu.address.testutil.TypicalTags.FLORIST; +import static seedu.address.testutil.TypicalTasks.TODO_TASK; import static seedu.address.testutil.TypicalWeddings.AMY_WEDDING; import java.util.Arrays; @@ -23,6 +24,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.wedding.Wedding; import seedu.address.testutil.PersonBuilder; @@ -55,7 +57,8 @@ public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { List newPersons = Arrays.asList(ALICE, editedAlice); List tags = Arrays.asList(FLORIST); List weddings = Arrays.asList(AMY_WEDDING); - AddressBookStub newData = new AddressBookStub(newPersons, tags, weddings); + List tasks = Arrays.asList(TODO_TASK); + AddressBookStub newData = new AddressBookStub(newPersons, tags, weddings, tasks); assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); } @@ -102,11 +105,14 @@ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); private final ObservableList tags = FXCollections.observableArrayList(); private final ObservableList weddings = FXCollections.observableArrayList(); + private final ObservableList tasks = FXCollections.observableArrayList(); - AddressBookStub(Collection persons, Collection tags, Collection weddings) { + AddressBookStub(Collection persons, Collection tags, + Collection weddings, Collection tasks) { this.persons.setAll(persons); this.tags.setAll(tags); this.weddings.setAll(weddings); + this.tasks.setAll(tasks); } @Override @@ -123,6 +129,11 @@ public ObservableList getTagList() { public ObservableList getWeddingList() { return weddings; } + + @Override + public ObservableList getTaskList() { + return tasks; + } } } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 4ed9c40a04d..d436abefed8 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -8,6 +8,7 @@ import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BENSON; import static seedu.address.testutil.TypicalTags.FLORIST; +import static seedu.address.testutil.TypicalTasks.TODO_TASK; import java.nio.file.Path; import java.nio.file.Paths; @@ -19,6 +20,10 @@ import seedu.address.model.person.keywordspredicate.NameContainsKeywordsPredicate; import seedu.address.model.tag.Tag; import seedu.address.model.tag.TagName; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; +import seedu.address.model.wedding.Wedding; +import seedu.address.model.wedding.WeddingName; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { @@ -135,6 +140,35 @@ public void updateFilteredTagList_nullPredicate_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> modelManager.updateFilteredTagList(null)); } + @Test + public void setTag_validTag_replacesTagSuccessfully() { + modelManager.addTag(FLORIST); + Tag editedTag = new Tag(new TagName("newFlorist")); + modelManager.setTag(FLORIST, editedTag); + assertTrue(modelManager.hasTag(editedTag)); + assertFalse(modelManager.hasTag(FLORIST)); + } + + @Test + public void setTask_validTask_replacesTaskSuccessfully() { + modelManager.addTask(TODO_TASK); + Task editedTask = new Todo("New task description"); + modelManager.setTask(TODO_TASK, editedTask); + assertTrue(modelManager.hasTask(editedTask)); + assertFalse(modelManager.hasTask(TODO_TASK)); + } + + @Test + public void setWedding_validWedding_replacesWeddingSuccessfully() { + Wedding wedding = new Wedding(new WeddingName("Old Wedding")); + Wedding editedWedding = new Wedding(new WeddingName("New Wedding")); + modelManager.addWedding(wedding); + modelManager.setWedding(wedding, editedWedding); + assertTrue(modelManager.hasWedding(editedWedding)); + assertFalse(modelManager.hasWedding(wedding)); + } + + @Test public void equals() { AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java index 91f778b01aa..9b8cf9bf26e 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/address/model/person/PersonTest.java @@ -8,6 +8,8 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_TODO; +import static seedu.address.logic.commands.CommandTestUtil.VALID_WEDDING_AMY; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; @@ -88,13 +90,42 @@ public void equals() { // different tags -> returns false editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); assertFalse(ALICE.equals(editedAlice)); + + // different weddings -> returns false + editedAlice = new PersonBuilder(ALICE).withWeddings(VALID_WEDDING_AMY).build(); + assertFalse(ALICE.equals(editedAlice)); + + // different tasks -> returns false + editedAlice = new PersonBuilder(ALICE).withTasks(VALID_TASK_TODO).build(); + assertFalse(ALICE.equals(editedAlice)); + } + + @Test + public void hashCodeTest() { + Person person1 = new PersonBuilder(ALICE).build(); + Person person2 = new PersonBuilder(ALICE).build(); + + // same person -> returns same hashCode + assertEquals(person1.hashCode(), person2.hashCode()); + + // different name -> returns different hashCode + Person personWithDifferentName = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); + assertFalse(person1.hashCode() == personWithDifferentName.hashCode()); + + // different weddings -> returns different hashCode + Person personWithDifferentWeddings = new PersonBuilder(ALICE).withWeddings(VALID_WEDDING_AMY).build(); + assertFalse(person1.hashCode() == personWithDifferentWeddings.hashCode()); + + // different tasks -> returns different hashCode + Person personWithDifferentTasks = new PersonBuilder(ALICE).withTasks(VALID_TASK_TODO).build(); + assertFalse(person1.hashCode() == personWithDifferentTasks.hashCode()); } @Test public void toStringMethod() { String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone() + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() - + ", weddings=" + ALICE.getWeddings() + "}"; + + ", weddings=" + ALICE.getWeddings() + ", tasks=" + ALICE.getTasks() + "}"; assertEquals(expected, ALICE.toString()); } } diff --git a/src/test/java/seedu/address/model/task/DateTest.java b/src/test/java/seedu/address/model/task/DateTest.java new file mode 100644 index 00000000000..98c42fd96f9 --- /dev/null +++ b/src/test/java/seedu/address/model/task/DateTest.java @@ -0,0 +1,99 @@ +package seedu.address.model.task; + +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 java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +public class DateTest { + + private static final String VALID_DATE = "2023-10-20"; + private static final String INVALID_DATE = "2023-15-45"; + private static final String VALID_DATE_FORMATTED = "Oct 20 2023"; + private static final String DIFFERENT_DATE = "2023-11-20"; + private static final DateTimeFormatter CUSTOM_FORMATTER = DateTimeFormatter.ofPattern("MMM dd yyyy"); + + @Test + public void constructor_validDate_success() { + Date date = new Date(VALID_DATE); + assertEquals(VALID_DATE, date.toString()); + } + + @Test + public void constructor_invalidDate_throwsDateTimeParseException() { + // Test if the constructor throws an exception when an invalid date is passed + try { + new Date(INVALID_DATE); + } catch (Exception e) { + assertTrue(e instanceof java.time.format.DateTimeParseException); + } + } + + @Test + public void isValidDate_validDate_returnsTrue() { + assertTrue(Date.isValidDate(VALID_DATE)); + } + + @Test + public void isValidDate_invalidDate_returnsFalse() { + assertFalse(Date.isValidDate(INVALID_DATE)); + } + + @Test + public void format_customFormatter_returnsFormattedDate() { + Date date = new Date(VALID_DATE); + String formattedDate = date.format(CUSTOM_FORMATTER); + assertEquals(VALID_DATE_FORMATTED, formattedDate); + } + + @Test + public void getDate_validDate_returnsLocalDate() { + Date date = new Date(VALID_DATE); + LocalDate expectedLocalDate = LocalDate.parse(VALID_DATE); + assertEquals(expectedLocalDate, date.getDate()); + } + + @Test + public void equals_nonDateObject_returnsFalse() { + Date date = new Date(VALID_DATE); + assertFalse(date.equals("Some String")); + } + + @Test + public void equals_sameDate_returnsTrue() { + Date date1 = new Date(VALID_DATE); + Date date2 = new Date(VALID_DATE); + assertTrue(date1.equals(date2)); + } + + @Test + public void equals_self_returnsTrue() { + Date date1 = new Date(VALID_DATE); + assertTrue(date1.equals(date1)); + } + + @Test + public void equals_differentDate_returnsFalse() { + Date date1 = new Date(VALID_DATE); + Date date2 = new Date("2023-11-20"); // Different date + assertFalse(date1.equals(date2)); + } + + @Test + public void hashCode_sameDate_returnsSameHashCode() { + Date date1 = new Date(VALID_DATE); + Date date2 = new Date(VALID_DATE); + assertEquals(date1.hashCode(), date2.hashCode()); + } + + @Test + public void hashCode_differentDate_returnsDifferentHashCode() { + Date date1 = new Date(VALID_DATE); + Date date2 = new Date("2023-11-20"); + assertFalse(date1.hashCode() == date2.hashCode()); + } +} diff --git a/src/test/java/seedu/address/model/task/DeadlineTest.java b/src/test/java/seedu/address/model/task/DeadlineTest.java new file mode 100644 index 00000000000..dd6b4a1253e --- /dev/null +++ b/src/test/java/seedu/address/model/task/DeadlineTest.java @@ -0,0 +1,92 @@ +package seedu.address.model.task; + +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 java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +public class DeadlineTest { + + private static final String VALID_DESCRIPTION = "Submit assignment"; + private static final String VALID_BY_DATE = "2023-12-31"; + private static final String VALID_BY_DATE_FORMATTED = "Dec 31 2023"; + private static final Description DESCRIPTION_OBJ = new Description(VALID_DESCRIPTION); + private static final DateTimeFormatter CUSTOM_FORMATTER = DateTimeFormatter.ofPattern("MMM dd yyyy"); + + @Test + public void constructor_validStringDescriptionAndByDate_success() { + Deadline deadline = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + assertEquals("[D][ ] Submit assignment (by: Dec 31 2023)", deadline.toString()); + } + + @Test + public void constructor_validDescriptionObject_success() { + Deadline deadline = new Deadline(DESCRIPTION_OBJ.toString(), VALID_BY_DATE); + assertEquals("[D][ ] Submit assignment (by: Dec 31 2023)", deadline.toString()); + } + + @Test + public void constructor_withDoneStatus_success() { + Deadline deadline = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE, true); + assertEquals("[D][X] Submit assignment (by: Dec 31 2023)", deadline.toString()); + } + + @Test + public void getBy_returnsCorrectDate() { + Deadline deadline = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + assertEquals(VALID_BY_DATE, deadline.getBy().toString()); + } + + @Test + public void markAsDone_marksTaskAsDone() { + Deadline deadline = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + deadline.markAsDone(); + assertTrue(deadline.getIsDone()); + assertEquals("[D][X] Submit assignment (by: Dec 31 2023)", deadline.toString()); + } + + @Test + public void markAsUndone_marksTaskAsUndone() { + Deadline deadline = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE, true); + deadline.markAsUndone(); + assertFalse(deadline.getIsDone()); + assertEquals("[D][ ] Submit assignment (by: Dec 31 2023)", deadline.toString()); + } + + @Test + public void equals_sameDeadline_returnsTrue() { + Deadline deadline1 = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + Deadline deadline2 = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + assertTrue(deadline1.equals(deadline2)); + } + + @Test + public void equals_differentDeadline_returnsFalse() { + Deadline deadline1 = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + Deadline deadline2 = new Deadline("Complete project", "2023-11-30"); + assertFalse(deadline1.equals(deadline2)); + } + + @Test + public void hashCode_sameDeadline_returnsSameHashCode() { + Deadline deadline1 = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + Deadline deadline2 = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + assertEquals(deadline1.hashCode(), deadline2.hashCode()); + } + + @Test + public void hashCode_differentDeadline_returnsDifferentHashCode() { + Deadline deadline1 = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + Deadline deadline2 = new Deadline("Complete project", "2023-11-30"); + assertFalse(deadline1.hashCode() == deadline2.hashCode()); + } + + @Test + public void toString_correctlyFormatsDeadline() { + Deadline deadline = new Deadline(VALID_DESCRIPTION, VALID_BY_DATE); + assertEquals("[D][ ] Submit assignment (by: Dec 31 2023)", deadline.toString()); + } +} diff --git a/src/test/java/seedu/address/model/task/DescriptionTest.java b/src/test/java/seedu/address/model/task/DescriptionTest.java new file mode 100644 index 00000000000..e9293fcf67b --- /dev/null +++ b/src/test/java/seedu/address/model/task/DescriptionTest.java @@ -0,0 +1,95 @@ +package seedu.address.model.task; + +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 org.junit.jupiter.api.Test; + +public class DescriptionTest { + + private static final String VALID_DESCRIPTION = "Buy groceries"; + private static final String INVALID_DESCRIPTION_EMPTY = ""; + private static final String INVALID_DESCRIPTION_BLANK = " "; + private static final String VALID_DESCRIPTION_WITH_WHITESPACE = " Clean the house "; + + @Test + public void constructor_validDescription_success() { + Description description = new Description(VALID_DESCRIPTION); + assertEquals(VALID_DESCRIPTION, description.toString()); + } + + @Test + public void constructor_invalidDescription_throwsIllegalArgumentException() { + // Test with empty description + try { + new Description(INVALID_DESCRIPTION_EMPTY); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + + // Test with blank description (only spaces) + try { + new Description(INVALID_DESCRIPTION_BLANK); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void isValidDescription_validDescription_returnsTrue() { + assertTrue(Description.isValidDescription(VALID_DESCRIPTION)); + } + + @Test + public void isValidDescription_invalidDescription_returnsFalse() { + assertFalse(Description.isValidDescription(INVALID_DESCRIPTION_EMPTY)); + assertFalse(Description.isValidDescription(INVALID_DESCRIPTION_BLANK)); + } + + @Test + public void equals_sameDescription_returnsTrue() { + Description description1 = new Description(VALID_DESCRIPTION); + Description description2 = new Description(VALID_DESCRIPTION); + assertTrue(description1.equals(description2)); + } + + @Test + public void equals_differentDescription_returnsFalse() { + Description description1 = new Description(VALID_DESCRIPTION); + Description description2 = new Description("Go for a run"); + assertFalse(description1.equals(description2)); + } + + @Test + public void equals_nullDescription_returnsFalse() { + Description description = new Description(VALID_DESCRIPTION); + assertFalse(description.equals(null)); + } + + @Test + public void equals_differentObjectType_returnsFalse() { + Description description = new Description(VALID_DESCRIPTION); + assertFalse(description.equals("some string")); + } + + @Test + public void hashCode_sameDescription_returnsSameHashCode() { + Description description1 = new Description(VALID_DESCRIPTION); + Description description2 = new Description(VALID_DESCRIPTION); + assertEquals(description1.hashCode(), description2.hashCode()); + } + + @Test + public void hashCode_differentDescription_returnsDifferentHashCode() { + Description description1 = new Description(VALID_DESCRIPTION); + Description description2 = new Description("Go for a run"); + assertFalse(description1.hashCode() == description2.hashCode()); + } + + @Test + public void toString_trimsWhitespace() { + Description description = new Description(VALID_DESCRIPTION_WITH_WHITESPACE.trim()); + assertEquals("Clean the house", description.toString()); + } +} diff --git a/src/test/java/seedu/address/model/task/EventTest.java b/src/test/java/seedu/address/model/task/EventTest.java new file mode 100644 index 00000000000..48c09d5a1d1 --- /dev/null +++ b/src/test/java/seedu/address/model/task/EventTest.java @@ -0,0 +1,102 @@ +package seedu.address.model.task; + +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 org.junit.jupiter.api.Test; + +public class EventTest { + + private static final String VALID_DESCRIPTION = "Team meeting"; + private static final String VALID_FROM_DATE = "2023-10-01"; + private static final String VALID_TO_DATE = "2023-10-02"; + private static final Description DESCRIPTION_OBJ = new Description(VALID_DESCRIPTION); + + @Test + public void constructor_validStringDescriptionAndDates_success() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + assertEquals("[E][ ] Team meeting (from: Oct 01 2023 to: Oct 02 2023)", event.toString()); + } + + @Test + public void constructor_validDescriptionObject_success() { + Event event = new Event(DESCRIPTION_OBJ.toString(), VALID_FROM_DATE, VALID_TO_DATE); + assertEquals("[E][ ] Team meeting (from: Oct 01 2023 to: Oct 02 2023)", event.toString()); + } + + @Test + public void constructor_withDoneStatus_success() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE, true); + assertEquals("[E][X] Team meeting (from: Oct 01 2023 to: Oct 02 2023)", event.toString()); + } + + @Test + public void getFrom_returnsCorrectDate() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + assertEquals(VALID_FROM_DATE, event.getFrom().toString()); + } + + @Test + public void getTo_returnsCorrectDate() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + assertEquals(VALID_TO_DATE, event.getTo().toString()); + } + + @Test + public void markAsDone_marksTaskAsDone() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + event.markAsDone(); + assertTrue(event.getIsDone()); + assertEquals("[E][X] Team meeting (from: Oct 01 2023 to: Oct 02 2023)", event.toString()); + } + + @Test + public void markAsUndone_marksTaskAsUndone() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE, true); + event.markAsUndone(); + assertFalse(event.getIsDone()); + assertEquals("[E][ ] Team meeting (from: Oct 01 2023 to: Oct 02 2023)", event.toString()); + } + + @Test + public void equals_sameEvent_returnsTrue() { + Event event1 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + Event event2 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + assertTrue(event1.equals(event2)); + } + + @Test + public void equals_nonEvent_returnsFalse() { + Event event1 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + Todo event2 = new Todo(VALID_DESCRIPTION); + assertFalse(event1.equals(event2)); + } + + @Test + public void equals_differentEvent_returnsFalse() { + Event event1 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + Event event2 = new Event("Workshop", "2023-10-05", "2023-10-06"); + assertFalse(event1.equals(event2)); + } + + @Test + public void hashCode_sameEvent_returnsSameHashCode() { + Event event1 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + Event event2 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + assertEquals(event1.hashCode(), event2.hashCode()); + } + + @Test + public void hashCode_differentEvent_returnsDifferentHashCode() { + Event event1 = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + Event event2 = new Event("Workshop", "2023-10-05", "2023-10-06"); + assertFalse(event1.hashCode() == event2.hashCode()); + } + + @Test + public void toString_correctlyFormatsEvent() { + Event event = new Event(VALID_DESCRIPTION, VALID_FROM_DATE, VALID_TO_DATE); + assertEquals("[E][ ] Team meeting (from: Oct 01 2023 to: Oct 02 2023)", event.toString()); + } +} diff --git a/src/test/java/seedu/address/model/task/ParsedTaskTest.java b/src/test/java/seedu/address/model/task/ParsedTaskTest.java new file mode 100644 index 00000000000..0ec63aaf102 --- /dev/null +++ b/src/test/java/seedu/address/model/task/ParsedTaskTest.java @@ -0,0 +1,73 @@ +package seedu.address.model.task; + +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 org.junit.jupiter.api.Test; + +public class ParsedTaskTest { + + private static final String VALID_TASK_TYPE = "todo"; + private static final String VALID_TASK_DETAILS = "Buy groceries"; + private static final String DIFFERENT_TASK_TYPE = "event"; + private static final String DIFFERENT_TASK_DETAILS = "Team meeting"; + + @Test + public void constructor_validTaskTypeAndDetails_success() { + ParsedTask parsedTask = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertEquals(VALID_TASK_TYPE, parsedTask.getTaskType()); + assertEquals(VALID_TASK_DETAILS, parsedTask.getTaskDetails()); + } + + @Test + public void getTaskType_returnsCorrectTaskType() { + ParsedTask parsedTask = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertEquals(VALID_TASK_TYPE, parsedTask.getTaskType()); + } + + @Test + public void getTaskDetails_returnsCorrectTaskDetails() { + ParsedTask parsedTask = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertEquals(VALID_TASK_DETAILS, parsedTask.getTaskDetails()); + } + + @Test + public void equals_sameParsedTask_returnsTrue() { + ParsedTask parsedTask1 = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + ParsedTask parsedTask2 = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertTrue(parsedTask1.equals(parsedTask2)); + } + + @Test + public void equals_differentParsedTask_returnsFalse() { + ParsedTask parsedTask1 = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + ParsedTask parsedTask2 = new ParsedTask(DIFFERENT_TASK_TYPE, DIFFERENT_TASK_DETAILS); + assertFalse(parsedTask1.equals(parsedTask2)); + } + + @Test + public void equals_nullParsedTask_returnsFalse() { + ParsedTask parsedTask = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertFalse(parsedTask.equals(null)); + } + + @Test + public void equals_differentObjectType_returnsFalse() { + ParsedTask parsedTask = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertFalse(parsedTask.equals("some string")); + } + @Test + public void hashCode_sameParsedTask_returnsSameHashCode() { + ParsedTask parsedTask1 = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + ParsedTask parsedTask2 = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + assertEquals(parsedTask1.hashCode(), parsedTask2.hashCode()); + } + + @Test + public void hashCode_differentParsedTask_returnsDifferentHashCode() { + ParsedTask parsedTask1 = new ParsedTask(VALID_TASK_TYPE, VALID_TASK_DETAILS); + ParsedTask parsedTask2 = new ParsedTask(DIFFERENT_TASK_TYPE, DIFFERENT_TASK_DETAILS); + assertFalse(parsedTask1.hashCode() == parsedTask2.hashCode()); + } +} diff --git a/src/test/java/seedu/address/model/task/TaskTest.java b/src/test/java/seedu/address/model/task/TaskTest.java new file mode 100644 index 00000000000..cc757c66975 --- /dev/null +++ b/src/test/java/seedu/address/model/task/TaskTest.java @@ -0,0 +1,107 @@ +package seedu.address.model.task; + +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TaskTest { + + private Task task1; + private Task task2; + + @BeforeEach + public void setUp() { + task1 = new Task("Complete project"); + task2 = new Task(new Description("Submit report")); + } + + @Test + public void constructor_validStringDescription_success() { + Task task = new Task("Buy groceries"); + assertEquals("[ ] Buy groceries", task.toString()); + } + + @Test + public void constructor_validDescriptionObject_success() { + Task task = new Task(new Description("Clean the house")); + assertEquals("[ ] Clean the house", task.toString()); + } + + @Test + public void markAsDone_marksTaskAsDone() { + task1.markAsDone(); + assertTrue(task1.getIsDone()); + assertEquals("[X] Complete project", task1.toString()); + } + + @Test + public void markAsUndone_marksTaskAsNotDone() { + task1.markAsDone(); + task1.markAsUndone(); + assertFalse(task1.getIsDone()); + assertEquals("[ ] Complete project", task1.toString()); + } + + @Test + public void hasKeywordInPartialDescription_keywordFound_returnsTrue() { + assertTrue(task1.hasKeywordInPartialDescription("project")); + assertTrue(task2.hasKeywordInPartialDescription("Submit")); + assertTrue(task2.hasKeywordInPartialDescription("report")); + } + + @Test + public void hasKeywordInPartialDescription_keywordNotFound_returnsFalse() { + assertFalse(task1.hasKeywordInPartialDescription("report")); + assertFalse(task2.hasKeywordInPartialDescription("project")); + } + + @Test + public void isSameTask_sameDescription_returnsTrue() { + Task task3 = new Task("Complete project"); + assertTrue(task1.isSameTask(task3)); + } + + @Test + public void isSameTask_differentDescription_returnsFalse() { + assertFalse(task1.isSameTask(task2)); + } + + @Test + public void equals_sameObject_returnsTrue() { + assertTrue(task1.equals(task1)); + } + + @Test + public void equals_sameValues_returnsTrue() { + Task task3 = new Task("Complete project"); + assertTrue(task1.equals(task3)); + } + + @Test + public void equals_differentDescription_returnsFalse() { + assertFalse(task1.equals(task2)); + } + + @Test + public void equals_differentStatus_returnsFalse() { + task1.markAsDone(); + Task task3 = new Task("Complete project"); // Task with same description but not done + assertFalse(task1.equals(task3)); + } + + @Test + public void hashCode_sameValues_returnsSameHashCode() { + Task task3 = new Task("Complete project"); + assertEquals(task1.hashCode(), task3.hashCode()); // Same description and status + } + + @Test + public void toString_correctFormatting() { + assertEquals("[ ] Complete project", task1.toString()); + task1.markAsDone(); + assertEquals("[X] Complete project", task1.toString()); + } +} diff --git a/src/test/java/seedu/address/model/task/TodoTest.java b/src/test/java/seedu/address/model/task/TodoTest.java new file mode 100644 index 00000000000..06f524c86b6 --- /dev/null +++ b/src/test/java/seedu/address/model/task/TodoTest.java @@ -0,0 +1,88 @@ +package seedu.address.model.task; + +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 org.junit.jupiter.api.Test; + +public class TodoTest { + + private static final String VALID_DESCRIPTION = "Buy groceries"; + private static final String VALID_DESCRIPTION_TRIMMED = "Clean the house"; + private static final Description DESCRIPTION_OBJ = new Description("Complete homework"); + + @Test + public void constructor_validStringDescription_success() { + Todo todo = new Todo(VALID_DESCRIPTION); + assertEquals("[T][ ] Buy groceries", todo.toString()); + } + + @Test + public void constructor_validDescriptionObject_success() { + Todo todo = new Todo(DESCRIPTION_OBJ); + assertEquals("[T][ ] Complete homework", todo.toString()); + } + + @Test + public void constructor_withDoneStatus_success() { + Todo todo = new Todo(VALID_DESCRIPTION, true); + assertEquals("[T][X] Buy groceries", todo.toString()); + } + + @Test + public void constructor_withDescriptionObjectAndDoneStatus_success() { + Todo todo = new Todo(DESCRIPTION_OBJ, true); + assertEquals("[T][X] Complete homework", todo.toString()); + } + + @Test + public void markAsDone_marksTaskAsDone() { + Todo todo = new Todo(VALID_DESCRIPTION); + todo.markAsDone(); + assertTrue(todo.getIsDone()); + assertEquals("[T][X] Buy groceries", todo.toString()); + } + + @Test + public void markAsUndone_marksTaskAsUndone() { + Todo todo = new Todo(VALID_DESCRIPTION, true); + todo.markAsUndone(); + assertFalse(todo.getIsDone()); + assertEquals("[T][ ] Buy groceries", todo.toString()); + } + + @Test + public void equals_sameTodo_returnsTrue() { + Todo todo1 = new Todo(VALID_DESCRIPTION); + Todo todo2 = new Todo(VALID_DESCRIPTION); + assertTrue(todo1.equals(todo2)); + } + + @Test + public void equals_differentTodo_returnsFalse() { + Todo todo1 = new Todo(VALID_DESCRIPTION); + Todo todo2 = new Todo("Complete homework"); + assertFalse(todo1.equals(todo2)); + } + + @Test + public void hashCode_sameTodo_returnsSameHashCode() { + Todo todo1 = new Todo(VALID_DESCRIPTION); + Todo todo2 = new Todo(VALID_DESCRIPTION); + assertEquals(todo1.hashCode(), todo2.hashCode()); + } + + @Test + public void hashCode_differentTodo_returnsDifferentHashCode() { + Todo todo1 = new Todo(VALID_DESCRIPTION); + Todo todo2 = new Todo("Complete homework"); + assertFalse(todo1.hashCode() == todo2.hashCode()); + } + + @Test + public void toString_trimsWhitespaceCorrectly() { + Todo todo = new Todo(VALID_DESCRIPTION_TRIMMED.trim()); + assertEquals("[T][ ] Clean the house", todo.toString()); + } +} diff --git a/src/test/java/seedu/address/model/task/UniqueTaskListTest.java b/src/test/java/seedu/address/model/task/UniqueTaskListTest.java new file mode 100644 index 00000000000..da11f9d5be1 --- /dev/null +++ b/src/test/java/seedu/address/model/task/UniqueTaskListTest.java @@ -0,0 +1,202 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; + +public class UniqueTaskListTest { + private static final Task TODO_TASK = new Todo("Buy groceries"); + private static final Task EVENT_TASK = new Event("Team meeting", "2023-10-01", "2023-10-02"); + private final UniqueTaskList uniqueTaskList = new UniqueTaskList(); + + + @Test + public void contains_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.contains(null)); + } + + @Test + public void contains_taskNotInList_returnsFalse() { + assertFalse(uniqueTaskList.contains(TODO_TASK)); + } + + @Test + public void contains_taskInList_returnsTrue() { + uniqueTaskList.add(TODO_TASK); + assertTrue(uniqueTaskList.contains(TODO_TASK)); + } + + @Test + public void contains_taskWithSameIdentityFieldsInList_returnsTrue() { + uniqueTaskList.add(TODO_TASK); + Task todoTaskCopy = new Todo("Buy groceries"); // Same description as TODO_TASK + assertTrue(uniqueTaskList.contains(todoTaskCopy)); + } + + @Test + public void add_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.add(null)); + } + + @Test + public void add_duplicateTask_throwsDuplicateTaskException() { + uniqueTaskList.add(TODO_TASK); + assertThrows(DuplicateTaskException.class, () -> uniqueTaskList.add(TODO_TASK)); + } + + @Test + public void setTask_nullTargetTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.setTask(null, TODO_TASK)); + } + + @Test + public void setTask_nullEditedTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.setTask(TODO_TASK, null)); + } + + @Test + public void setTask_targetTaskNotInList_throwsTaskNotFoundException() { + assertThrows(TaskNotFoundException.class, () -> uniqueTaskList.setTask(TODO_TASK, TODO_TASK)); + } + + @Test + public void setTask_editedTaskIsSameTask_success() { + uniqueTaskList.add(TODO_TASK); + uniqueTaskList.setTask(TODO_TASK, TODO_TASK); + UniqueTaskList expectedTaskList = new UniqueTaskList(); + expectedTaskList.add(TODO_TASK); + assertEquals(expectedTaskList, uniqueTaskList); + } + + @Test + public void setTask_editedTaskHasSameIdentity_success() { + uniqueTaskList.add(TODO_TASK); + Task editedTodoTask = new Todo("Buy groceries"); + uniqueTaskList.setTask(TODO_TASK, editedTodoTask); + UniqueTaskList expectedTaskList = new UniqueTaskList(); + expectedTaskList.add(editedTodoTask); + assertEquals(expectedTaskList, uniqueTaskList); + } + + @Test + public void setTask_editedTaskHasDifferentIdentity_success() { + uniqueTaskList.add(TODO_TASK); + uniqueTaskList.setTask(TODO_TASK, EVENT_TASK); + UniqueTaskList expectedTaskList = new UniqueTaskList(); + expectedTaskList.add(EVENT_TASK); + assertEquals(expectedTaskList, uniqueTaskList); + } + + @Test + public void setTask_editedTaskHasNonUniqueIdentity_throwsDuplicateTaskException() { + uniqueTaskList.add(TODO_TASK); + uniqueTaskList.add(EVENT_TASK); + assertThrows(DuplicateTaskException.class, () -> uniqueTaskList.setTask(TODO_TASK, EVENT_TASK)); + } + + @Test + public void remove_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.remove(null)); + } + + @Test + public void remove_taskDoesNotExist_throwsTaskNotFoundException() { + assertThrows(TaskNotFoundException.class, () -> uniqueTaskList.remove(TODO_TASK)); + } + + @Test + public void remove_existingTask_removesTask() { + uniqueTaskList.add(TODO_TASK); + uniqueTaskList.remove(TODO_TASK); + UniqueTaskList expectedTaskList = new UniqueTaskList(); + assertEquals(expectedTaskList, uniqueTaskList); + } + + @Test + public void setTasks_nullUniqueTaskList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.setTasks((UniqueTaskList) null)); + } + + @Test + public void setTasks_uniqueTaskList_replacesOwnListWithProvidedUniqueTaskList() { + uniqueTaskList.add(TODO_TASK); + UniqueTaskList expectedTaskList = new UniqueTaskList(); + expectedTaskList.add(EVENT_TASK); + uniqueTaskList.setTasks(expectedTaskList); + assertEquals(expectedTaskList, uniqueTaskList); + } + + @Test + public void setTasks_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.setTasks((List) null)); + } + + @Test + public void setTasks_list_replacesOwnListWithProvidedList() { + uniqueTaskList.add(TODO_TASK); + List taskList = Collections.singletonList(EVENT_TASK); + uniqueTaskList.setTasks(taskList); + UniqueTaskList expectedTaskList = new UniqueTaskList(); + expectedTaskList.add(EVENT_TASK); + assertEquals(expectedTaskList, uniqueTaskList); + } + + @Test + public void setTasks_listWithDuplicateTasks_throwsDuplicateTaskException() { + List listWithDuplicateTasks = Arrays.asList(TODO_TASK, TODO_TASK); + assertThrows(DuplicateTaskException.class, () -> uniqueTaskList.setTasks(listWithDuplicateTasks)); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + uniqueTaskList.add(TODO_TASK); + assertThrows(UnsupportedOperationException.class, () -> + uniqueTaskList.asUnmodifiableObservableList().remove(0)); + } + + @Test + public void hashCode_sameInternalList_returnsSameHashCode() { + uniqueTaskList.add(TODO_TASK); + UniqueTaskList anotherUniqueTaskList = new UniqueTaskList(); + anotherUniqueTaskList.add(TODO_TASK); + assertEquals(uniqueTaskList.hashCode(), anotherUniqueTaskList.hashCode()); + } + + @Test + public void hashCode_differentInternalList_returnsDifferentHashCode() { + uniqueTaskList.add(TODO_TASK); + UniqueTaskList anotherUniqueTaskList = new UniqueTaskList(); + anotherUniqueTaskList.add(EVENT_TASK); + assertFalse(uniqueTaskList.hashCode() == anotherUniqueTaskList.hashCode()); + } + + @Test + public void toString_returnsCorrectStringRepresentation() { + uniqueTaskList.add(TODO_TASK); + assertEquals(Collections.singletonList(TODO_TASK).toString(), uniqueTaskList.toString()); + } + + @Test + public void iterator_iterateOverList_returnsCorrectOrder() { + uniqueTaskList.add(TODO_TASK); + uniqueTaskList.add(EVENT_TASK); + List taskList = Arrays.asList(TODO_TASK, EVENT_TASK); + Iterator iterator = uniqueTaskList.iterator(); + for (Task task : taskList) { + assertTrue(iterator.hasNext()); + assertEquals(task, iterator.next()); + } + } +} + diff --git a/src/test/java/seedu/address/model/task/keywordspredicate/DescriptionContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/task/keywordspredicate/DescriptionContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..c25e2735069 --- /dev/null +++ b/src/test/java/seedu/address/model/task/keywordspredicate/DescriptionContainsKeywordsPredicateTest.java @@ -0,0 +1,83 @@ +package seedu.address.model.task.keywordspredicate; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.task.Task; +import seedu.address.testutil.TypicalTasks; + +public class DescriptionContainsKeywordsPredicateTest { + + private static final Task TASK_BUY_GROCERIES = TypicalTasks.TODO_TASK; + private static final Task TASK_SUBMIT_ASSIGNMENT = TypicalTasks.SUBMIT_ASSIGNMENT_TASK; + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("groceries"); + List secondPredicateKeywordList = Arrays.asList("groceries", "assignment"); + + DescriptionContainsKeywordsPredicate firstPredicate = + new DescriptionContainsKeywordsPredicate(firstPredicateKeywordList); + DescriptionContainsKeywordsPredicate secondPredicate = + new DescriptionContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + DescriptionContainsKeywordsPredicate firstPredicateCopy = + new DescriptionContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different predicate -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_descriptionContainsKeywords_returnsTrue() { + // One keyword + DescriptionContainsKeywordsPredicate predicate = + new DescriptionContainsKeywordsPredicate(Collections.singletonList("groceries")); + assertTrue(predicate.test(TASK_BUY_GROCERIES)); + + // Multiple keywords + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("groceries", "assignment")); + assertTrue(predicate.test(TASK_BUY_GROCERIES)); + + // Only one matching keyword + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("assignment", "groceries")); + assertTrue(predicate.test(TASK_SUBMIT_ASSIGNMENT)); + + // Mixed-case keywords + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("GrOCeRiEs", "ASSIGNMENT")); + assertTrue(predicate.test(TASK_BUY_GROCERIES)); + } + + @Test + public void test_descriptionDoesNotContainKeywords_returnsFalse() { + // Zero keywords + DescriptionContainsKeywordsPredicate predicate = + new DescriptionContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(TASK_BUY_GROCERIES)); + + // Non-matching keyword + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("assignment")); + assertFalse(predicate.test(TASK_BUY_GROCERIES)); + + // Keywords match part of the description but not fully + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("buy", "grocer")); + assertFalse(predicate.test(TASK_SUBMIT_ASSIGNMENT)); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedDeadlineTest.java b/src/test/java/seedu/address/storage/JsonAdaptedDeadlineTest.java new file mode 100644 index 00000000000..e2985293398 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedDeadlineTest.java @@ -0,0 +1,55 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Description; + +public class JsonAdaptedDeadlineTest { + + private static final String VALID_DESCRIPTION = "Submit assignment"; + private static final String VALID_BY = "2023-12-31"; + private static final boolean IS_DONE = false; + + private static final String INVALID_DESCRIPTION = ""; + private static final String INVALID_BY = "32-12-2023"; + + @Test + public void toModelType_validDeadlineDetails_returnsDeadline() throws Exception { + JsonAdaptedDeadline jsonAdaptedDeadline = new JsonAdaptedDeadline(VALID_DESCRIPTION, IS_DONE, VALID_BY); + Deadline expectedDeadline = new Deadline(VALID_DESCRIPTION, VALID_BY, IS_DONE); + assertEquals(expectedDeadline, jsonAdaptedDeadline.toModelType()); + } + + @Test + public void toModelType_invalidDescription_throwsIllegalValueException() { + JsonAdaptedDeadline jsonAdaptedDeadline = new JsonAdaptedDeadline(INVALID_DESCRIPTION, IS_DONE, VALID_BY); + String expectedMessage = Description.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedDeadline::toModelType); + } + + @Test + public void toModelType_invalidByDate_throwsIllegalValueException() { + JsonAdaptedDeadline jsonAdaptedDeadline = new JsonAdaptedDeadline(VALID_DESCRIPTION, IS_DONE, INVALID_BY); + String expectedMessage = seedu.address.model.task.Date.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedDeadline::toModelType); + } + + @Test + public void toModelType_nullByDate_throwsIllegalValueException() { + JsonAdaptedDeadline jsonAdaptedDeadline = new JsonAdaptedDeadline(VALID_DESCRIPTION, IS_DONE, null); + String expectedMessage = String.format(JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT, "Deadline"); + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedDeadline::toModelType); + } + + @Test + public void toModelType_validDeadlineWithDone_returnsCorrectly() throws Exception { + JsonAdaptedDeadline jsonAdaptedDeadline = new JsonAdaptedDeadline(VALID_DESCRIPTION, true, VALID_BY); + Deadline expectedDeadline = new Deadline(VALID_DESCRIPTION, VALID_BY, true); + assertEquals(expectedDeadline, jsonAdaptedDeadline.toModelType()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java b/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java new file mode 100644 index 00000000000..4ef009722fb --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java @@ -0,0 +1,71 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Description; +import seedu.address.model.task.Event; + +public class JsonAdaptedEventTest { + + private static final String VALID_DESCRIPTION = "Team meeting"; + private static final String VALID_FROM = "2023-10-01"; + private static final String VALID_TO = "2023-10-02"; + private static final boolean IS_DONE = false; + + private static final String INVALID_DESCRIPTION = ""; + private static final String INVALID_FROM = "invalid-date"; + private static final String INVALID_TO = "invalid-date"; + + @Test + public void toModelType_validEventDetails_returnsEvent() throws Exception { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(VALID_DESCRIPTION, IS_DONE, VALID_FROM, VALID_TO); + Event expectedEvent = new Event(VALID_DESCRIPTION, VALID_FROM, VALID_TO, IS_DONE); + assertEquals(expectedEvent, jsonAdaptedEvent.toModelType()); + } + + @Test + public void toModelType_invalidDescription_throwsIllegalValueException() { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(INVALID_DESCRIPTION, IS_DONE, VALID_FROM, VALID_TO); + String expectedMessage = Description.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedEvent::toModelType); + } + + @Test + public void toModelType_invalidFromDate_throwsIllegalValueException() { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(VALID_DESCRIPTION, IS_DONE, INVALID_FROM, VALID_TO); + String expectedMessage = seedu.address.model.task.Date.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedEvent::toModelType); + } + + @Test + public void toModelType_invalidToDate_throwsIllegalValueException() { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(VALID_DESCRIPTION, IS_DONE, VALID_FROM, INVALID_TO); + String expectedMessage = seedu.address.model.task.Date.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedEvent::toModelType); + } + + @Test + public void toModelType_nullFromDate_throwsIllegalValueException() { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(VALID_DESCRIPTION, IS_DONE, null, VALID_TO); + String expectedMessage = String.format(JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT, "Event dates"); + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedEvent::toModelType); + } + + @Test + public void toModelType_nullToDate_throwsIllegalValueException() { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(VALID_DESCRIPTION, IS_DONE, VALID_FROM, null); + String expectedMessage = String.format(JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT, "Event dates"); + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedEvent::toModelType); + } + + @Test + public void toModelType_validEventWithDone_returnsCorrectly() throws Exception { + JsonAdaptedEvent jsonAdaptedEvent = new JsonAdaptedEvent(VALID_DESCRIPTION, true, VALID_FROM, VALID_TO); + Event expectedEvent = new Event(VALID_DESCRIPTION, VALID_FROM, VALID_TO, true); + assertEquals(expectedEvent, jsonAdaptedEvent.toModelType()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java index 48f6ee04866..9a3646dcbc3 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java @@ -4,6 +4,7 @@ import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.CARL; import static seedu.address.testutil.TypicalPersons.CLIVE; import java.util.ArrayList; @@ -17,6 +18,9 @@ import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Todo; public class JsonAdaptedPersonTest { private static final String INVALID_NAME = "R@chel"; @@ -34,6 +38,21 @@ public class JsonAdaptedPersonTest { private static final List VALID_WEDDINGS = BENSON.getWeddings().stream() .map(JsonAdaptedWedding::new) .collect(Collectors.toList()); + + private static final List VALID_TASKS = CARL.getTasks().stream() + .map(task -> { + if (task instanceof Todo) { + return new JsonAdaptedTodo((Todo) task); + } else if (task instanceof Deadline) { + return new JsonAdaptedDeadline((Deadline) task); + } else if (task instanceof Event) { + return new JsonAdaptedEvent((Event) task); + } else { + throw new IllegalArgumentException("Unknown task type"); + } + }) + .collect(Collectors.toList()); + private static final String BLANK_ADDRESS = ""; private static final String CLIVE_NAME = CLIVE.getName().toString(); private static final String CLIVE_PHONE = CLIVE.getPhone().toString(); @@ -45,6 +64,20 @@ public class JsonAdaptedPersonTest { .map(JsonAdaptedWedding::new) .collect(Collectors.toList()); + private static final List CLIVE_TASKS = CLIVE.getTasks().stream() + .map(task -> { + if (task instanceof Todo) { + return new JsonAdaptedTodo((Todo) task); + } else if (task instanceof Deadline) { + return new JsonAdaptedDeadline((Deadline) task); + } else if (task instanceof Event) { + return new JsonAdaptedEvent((Event) task); + } else { + throw new IllegalArgumentException("Unknown task type"); + } + }) + .collect(Collectors.toList()); + @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON); @@ -55,7 +88,7 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception { public void toModelType_invalidName_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS, - VALID_WEDDINGS); + VALID_WEDDINGS, VALID_TASKS); String expectedMessage = Name.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -63,7 +96,7 @@ public void toModelType_invalidName_throwsIllegalValueException() { @Test public void toModelType_nullName_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, - VALID_TAGS, VALID_WEDDINGS); + VALID_TAGS, VALID_WEDDINGS, VALID_TASKS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -72,7 +105,7 @@ public void toModelType_nullName_throwsIllegalValueException() { public void toModelType_invalidPhone_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS, - VALID_WEDDINGS); + VALID_WEDDINGS, VALID_TASKS); String expectedMessage = Phone.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -80,7 +113,7 @@ public void toModelType_invalidPhone_throwsIllegalValueException() { @Test public void toModelType_nullPhone_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, - VALID_TAGS, VALID_WEDDINGS); + VALID_TAGS, VALID_WEDDINGS, VALID_TASKS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -89,7 +122,7 @@ public void toModelType_nullPhone_throwsIllegalValueException() { public void toModelType_invalidEmail_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS, - VALID_WEDDINGS); + VALID_WEDDINGS, VALID_TASKS); String expectedMessage = Email.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -97,7 +130,7 @@ public void toModelType_invalidEmail_throwsIllegalValueException() { @Test public void toModelType_nullEmail_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS, - VALID_WEDDINGS); + VALID_WEDDINGS, VALID_TASKS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -105,14 +138,15 @@ public void toModelType_nullEmail_throwsIllegalValueException() { @Test public void toModelType_blankAddress_returnsPerson() throws IllegalValueException { JsonAdaptedPerson person = - new JsonAdaptedPerson(CLIVE_NAME, CLIVE_PHONE, CLIVE_EMAIL, BLANK_ADDRESS, CLIVE_TAGS, CLIVE_WEDDINGS); + new JsonAdaptedPerson(CLIVE_NAME, CLIVE_PHONE, CLIVE_EMAIL, BLANK_ADDRESS, + CLIVE_TAGS, CLIVE_WEDDINGS, CLIVE_TASKS); assertEquals(CLIVE, person.toModelType()); } @Test public void toModelType_nullAddress_throwsIllegalValueException() { JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS, - VALID_WEDDINGS); + VALID_WEDDINGS, CLIVE_TASKS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -122,7 +156,8 @@ public void toModelType_invalidTags_throwsIllegalValueException() { List invalidTags = new ArrayList<>(VALID_TAGS); invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags, VALID_WEDDINGS); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags, + VALID_WEDDINGS, CLIVE_TASKS); assertThrows(IllegalValueException.class, person::toModelType); } diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java new file mode 100644 index 00000000000..796d624b53f --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java @@ -0,0 +1,80 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Date; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Description; +import seedu.address.model.task.Event; +import seedu.address.model.task.Todo; + +public class JsonAdaptedTaskTest { + + private static final String INVALID_DESCRIPTION = ""; + private static final String INVALID_DEADLINE_DATE = "32-12-2023"; + private static final String INVALID_EVENT_DATE = "invalid-date"; + + private static final String VALID_TODO_DESCRIPTION = "Buy groceries"; + private static final String VALID_DEADLINE_DESCRIPTION = "Submit assignment"; + private static final String VALID_DEADLINE_DATE = "2023-12-31"; + private static final String VALID_EVENT_DESCRIPTION = "Team meeting"; + private static final String VALID_EVENT_START_DATE = "2023-10-01"; + private static final String VALID_EVENT_END_DATE = "2023-10-02"; + + @Test + public void toModelType_validTodoDetails_returnsTodo() throws Exception { + JsonAdaptedTodo todo = new JsonAdaptedTodo(VALID_TODO_DESCRIPTION, false); + Todo expectedTodo = new Todo(VALID_TODO_DESCRIPTION); + assertEquals(expectedTodo, todo.toModelType()); + } + + @Test + public void toModelType_invalidTodoDescription_throwsIllegalValueException() { + JsonAdaptedTodo todo = new JsonAdaptedTodo(INVALID_DESCRIPTION, false); + String expectedMessage = Description.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, todo::toModelType); + } + + @Test + public void toModelType_validDeadlineDetails_returnsDeadline() throws Exception { + JsonAdaptedDeadline deadline = new JsonAdaptedDeadline(VALID_DEADLINE_DESCRIPTION, + false, VALID_DEADLINE_DATE); + Deadline expectedDeadline = new Deadline(VALID_DEADLINE_DESCRIPTION, VALID_DEADLINE_DATE); + assertEquals(expectedDeadline, deadline.toModelType()); + } + @Test + public void toModelType_invalidDeadlineDate_throwsIllegalValueException() { + JsonAdaptedDeadline deadline = new JsonAdaptedDeadline(VALID_DEADLINE_DESCRIPTION, + false, INVALID_DEADLINE_DATE); + String expectedMessage = Date.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, deadline::toModelType); + } + + @Test + public void toModelType_validEventDetails_returnsEvent() throws Exception { + JsonAdaptedEvent event = new JsonAdaptedEvent(VALID_EVENT_DESCRIPTION, + false, VALID_EVENT_START_DATE, VALID_EVENT_END_DATE); + Event expectedEvent = new Event(VALID_EVENT_DESCRIPTION, VALID_EVENT_START_DATE, VALID_EVENT_END_DATE); + assertEquals(expectedEvent, event.toModelType()); + } + + @Test + public void toModelType_invalidEventDates_throwsIllegalValueException() { + JsonAdaptedEvent event = new JsonAdaptedEvent(VALID_EVENT_DESCRIPTION, + false, INVALID_EVENT_DATE, INVALID_EVENT_DATE); + String expectedMessage = Date.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + JsonAdaptedTodo todo = new JsonAdaptedTodo(null, false); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Description"); + assertThrows(IllegalValueException.class, expectedMessage, todo::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTodoTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTodoTest.java new file mode 100644 index 00000000000..c2ddc2c9b8f --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTodoTest.java @@ -0,0 +1,45 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Description; +import seedu.address.model.task.Todo; + +public class JsonAdaptedTodoTest { + + private static final String VALID_DESCRIPTION = "Buy groceries"; + private static final String INVALID_DESCRIPTION = ""; // Invalid due to being empty + private static final boolean IS_DONE = false; + + @Test + public void toModelType_validTodoDetails_returnsTodo() throws Exception { + JsonAdaptedTodo jsonAdaptedTodo = new JsonAdaptedTodo(VALID_DESCRIPTION, IS_DONE); + Todo expectedTodo = new Todo(VALID_DESCRIPTION); + assertEquals(expectedTodo, jsonAdaptedTodo.toModelType()); + } + + @Test + public void toModelType_invalidTodoDescription_throwsIllegalValueException() { + JsonAdaptedTodo jsonAdaptedTodo = new JsonAdaptedTodo(INVALID_DESCRIPTION, IS_DONE); + String expectedMessage = Description.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedTodo::toModelType); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + JsonAdaptedTodo jsonAdaptedTodo = new JsonAdaptedTodo(null, IS_DONE); + String expectedMessage = String.format(JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT, "Description"); + assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedTodo::toModelType); + } + + @Test + public void toModelType_validTodoWithDone_returnsCorrectly() throws Exception { + JsonAdaptedTodo jsonAdaptedTodo = new JsonAdaptedTodo(VALID_DESCRIPTION, true); + Todo expectedTodo = new Todo(VALID_DESCRIPTION, true); + assertEquals(expectedTodo, jsonAdaptedTodo.toModelType()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java index 70d3dc93478..32da626798c 100644 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java @@ -79,6 +79,7 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception { // Save in new file and read back jsonAddressBookStorage.saveAddressBook(original, filePath); ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); + assertEquals(original, new AddressBook(readBack)); // Modify data, overwrite exiting file, and read back diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java index 8b61ff4f90f..4149796ddbb 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java @@ -20,9 +20,12 @@ public class JsonSerializableAddressBookTest { private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); private static final Path INVALID_TAG_FILE = TEST_DATA_FOLDER.resolve("invalidTagAddressBook.json"); private static final Path INVALID_WEDDING_FILE = TEST_DATA_FOLDER.resolve("invalidWeddingAddressBook.json"); + + private static final Path INVALID_TASK_FILE = TEST_DATA_FOLDER.resolve("invalidTaskAddressBook.json"); private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); private static final Path DUPLICATE_TAG_FILE = TEST_DATA_FOLDER.resolve("duplicateTagAddressBook.json"); private static final Path DUPLICATE_WEDDING_FILE = TEST_DATA_FOLDER.resolve("duplicateWeddingAddressBook.json"); + private static final Path DUPLICATE_TASK_FILE = TEST_DATA_FOLDER.resolve("duplicateTaskAddressBook.json"); @Test public void toModelType_typicalPersonsFile_success() throws Exception { @@ -30,6 +33,8 @@ public void toModelType_typicalPersonsFile_success() throws Exception { JsonSerializableAddressBook.class).get(); AddressBook addressBookFromFile = dataFromFile.toModelType(); AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); + System.out.println(addressBookFromFile); + System.out.println(typicalPersonsAddressBook); assertEquals(addressBookFromFile, typicalPersonsAddressBook); } diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index 3a1a9802f8d..e962eb0cdd7 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -9,6 +9,7 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; import seedu.address.model.util.SampleDataUtil; import seedu.address.model.wedding.Wedding; @@ -32,6 +33,8 @@ public class PersonBuilder { private Set tags; private Set weddings; + private Set tasks; + /** * Creates a {@code PersonBuilder} with the default details. */ @@ -42,6 +45,7 @@ public PersonBuilder() { address = new Address(DEFAULT_ADDRESS); tags = new HashSet<>(); weddings = new HashSet<>(); + tasks = new HashSet<>(); } /** @@ -54,6 +58,7 @@ public PersonBuilder(Person personToCopy) { address = personToCopy.getAddress(); tags = new HashSet<>(personToCopy.getTags()); weddings = new HashSet<>(personToCopy.getWeddings()); + tasks = new HashSet<>(personToCopy.getTasks()); } /** @@ -104,8 +109,18 @@ public PersonBuilder withEmail(String email) { return this; } + /** + * Sets the {@code Task} objects to the {@code Person} that we are building. + * You can either modify this method to accept specific tasks like Todo, Deadline, or Event + * or adapt the SampleDataUtil to properly convert them. + */ + public PersonBuilder withTasks(String ... tasks) { + this.tasks = SampleDataUtil.getTaskSet(tasks); + return this; + } + public Person build() { - return new Person(name, phone, email, address, tags, weddings); + return new Person(name, phone, email, address, tags, weddings, tasks); } } diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index aee1f013989..f4eee2fa8eb 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -25,6 +25,8 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_NEIGHBOR; import static seedu.address.testutil.TypicalTags.FLORIST; import static seedu.address.testutil.TypicalTags.PHOTOGRAPHER; +import static seedu.address.testutil.TypicalTasks.DEADLINE_TASK; +import static seedu.address.testutil.TypicalTasks.TODO_TASK; import static seedu.address.testutil.TypicalWeddings.AMY_WEDDING; import static seedu.address.testutil.TypicalWeddings.BOB_WEDDING; @@ -49,7 +51,7 @@ public class TypicalPersons { .withEmail("johnd@example.com").withPhone("98765432") .withTags("owesMoney", "friends").withWeddings("Wedding 2", "Carla's Wedding").build(); public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); + .withEmail("heinz@example.com").withAddress("wall street").withTasks("todo: Buy cake").build(); public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") @@ -83,7 +85,7 @@ public class TypicalPersons { private TypicalPersons() {} // prevents instantiation /** - * Returns an {@code AddressBook} with all the typical persons. + * Returns an {@code AddressBook} with all the typical tasks. */ public static AddressBook getTypicalAddressBook() { AddressBook ab = new AddressBook(); @@ -91,6 +93,8 @@ public static AddressBook getTypicalAddressBook() { ab.addTag(PHOTOGRAPHER); ab.addWedding(AMY_WEDDING); ab.addWedding(BOB_WEDDING); + ab.addTask(TODO_TASK); + ab.addTask(DEADLINE_TASK); for (Person person : getTypicalPersons()) { ab.addPerson(person); diff --git a/src/test/java/seedu/address/testutil/TypicalTasks.java b/src/test/java/seedu/address/testutil/TypicalTasks.java new file mode 100644 index 00000000000..789a3ccf1f6 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalTasks.java @@ -0,0 +1,64 @@ +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Event; +import seedu.address.model.task.Task; +import seedu.address.model.task.Todo; + +/** + * A utility class containing a list of {@code Task} objects to be used in tests. + */ +public class TypicalTasks { + + // Strings for Task Descriptions + public static final String VALID_TODO_DESCRIPTION = "Buy groceries"; + public static final String VALID_DEADLINE_DESCRIPTION = "Submit report"; + public static final String VALID_EVENT_DESCRIPTION = "Project meeting"; + public static final String VALID_SUBMIT_ASSIGNMENT_DESCRIPTION = "Submit assignment"; + + // Dates for Deadline and Event tasks + public static final String VALID_DEADLINE_DATE = "2024-12-31"; + public static final String VALID_EVENT_START_DATE = "2024-10-10"; + public static final String VALID_EVENT_END_DATE = "2024-10-11"; + + public static final String TASK_DESC_TODO = " tk/todo Buy groceries"; + public static final String TASK_DESC_DEADLINE = " tk/deadline Submit report /by 2024-12-31"; + public static final String TASK_DESC_EVENT = " tk/event Project meeting /from 2024-10-10 /to 2024-10-11"; + + public static final String TASK_DESC_BUY_GROCERIES = " tk/todo Buy groceries"; + public static final String TASK_DESC_SUBMIT_ASSIGNMENT = " tk/deadline Submit assignment /by 2023-12-31"; + public static final String TASK_DESC_TEAM_MEETING = " tk/event Team meeting /from 2024-10-01 /to 2024-10-02"; + + // Invalid Task Descriptions + public static final String INVALID_TASK_DESC = " tk/event "; // incomplete event details + + // Sample Tasks + public static final Todo TODO_TASK = new Todo(VALID_TODO_DESCRIPTION); + public static final Deadline DEADLINE_TASK = new Deadline(VALID_DEADLINE_DESCRIPTION, VALID_DEADLINE_DATE); + public static final Event EVENT_TASK = new Event(VALID_EVENT_DESCRIPTION, + VALID_EVENT_START_DATE, VALID_EVENT_END_DATE); + public static final Todo SUBMIT_ASSIGNMENT_TASK = new Todo(VALID_SUBMIT_ASSIGNMENT_DESCRIPTION); // New Task + + + /** + * Returns an {@code AddressBook} with all the typical persons. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + + for (Task task : getTypicalTasks()) { + ab.addTask(task); + } + return ab; + } + + // Returns a list of all typical tasks + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(TODO_TASK, DEADLINE_TASK, EVENT_TASK)); + } +}